]> git.mxchange.org Git - simgear.git/blob - simgear/hla/HLAObjectInstance.cxx
eb27f2523aac644bc43e6ac34b8bad636b19492b
[simgear.git] / simgear / hla / HLAObjectInstance.cxx
1 // Copyright (C) 2009 - 2012  Mathias Froehlich - Mathias.Froehlich@web.de
2 //
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Library General Public
5 // License as published by the Free Software Foundation; either
6 // version 2 of the License, or (at your option) any later version.
7 //
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 // Library General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with this program; if not, write to the Free Software
15 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
16 //
17
18 #ifdef HAVE_CONFIG_H
19 #  include <simgear_config.h>
20 #endif
21
22 #include <simgear/compiler.h>
23
24 #include "HLAObjectInstance.hxx"
25
26 #include <algorithm>
27 #include "simgear/debug/logstream.hxx"
28 #include "HLAArrayDataElement.hxx"
29 #include "HLABasicDataElement.hxx"
30 #include "HLADataElement.hxx"
31 #include "HLAEnumeratedDataElement.hxx"
32 #include "HLAFederate.hxx"
33 #include "HLAFixedRecordDataElement.hxx"
34 #include "HLAObjectClass.hxx"
35 #include "HLAVariantRecordDataElement.hxx"
36 #include "RTIObjectClass.hxx"
37 #include "RTIObjectInstance.hxx"
38
39 namespace simgear {
40
41 HLAObjectInstance::UpdateCallback::~UpdateCallback()
42 {
43 }
44
45 HLAObjectInstance::ReflectCallback::~ReflectCallback()
46 {
47 }
48
49 HLAObjectInstance::HLAObjectInstance(HLAObjectClass* objectClass) :
50     _federate(objectClass->_federate),
51     _objectClass(objectClass)
52 {
53 }
54
55 HLAObjectInstance::~HLAObjectInstance()
56 {
57     _clearRTIObjectInstance();
58 }
59
60 const std::string&
61 HLAObjectInstance::getName() const
62 {
63     return _name;
64 }
65
66 const SGWeakPtr<HLAFederate>&
67 HLAObjectInstance::getFederate() const
68 {
69     return _federate;
70 }
71
72 const SGSharedPtr<HLAObjectClass>&
73 HLAObjectInstance::getObjectClass() const
74 {
75     return _objectClass;
76 }
77
78 unsigned
79 HLAObjectInstance::getNumAttributes() const
80 {
81     return _objectClass->getNumAttributes();
82 }
83
84 unsigned
85 HLAObjectInstance::getAttributeIndex(const std::string& name) const
86 {
87     return _objectClass->getAttributeIndex(name);
88 }
89
90 std::string
91 HLAObjectInstance::getAttributeName(unsigned index) const
92 {
93     return _objectClass->getAttributeName(index);
94 }
95
96 bool
97 HLAObjectInstance::getAttributeOwned(unsigned index) const
98 {
99     if (!_rtiObjectInstance.valid())
100         return false;
101     return _rtiObjectInstance->getAttributeOwned(index);
102 }
103
104 const HLADataType*
105 HLAObjectInstance::getAttributeDataType(unsigned index) const
106 {
107     return _objectClass->getAttributeDataType(index);
108 }
109
110 HLADataElement*
111 HLAObjectInstance::getAttributeDataElement(unsigned index)
112 {
113     if (_attributeVector.size() <= index)
114         return 0;
115     return _attributeVector[index]._dataElement.get();
116 }
117
118 const HLADataElement*
119 HLAObjectInstance::getAttributeDataElement(unsigned index) const
120 {
121     if (_attributeVector.size() <= index)
122         return 0;
123     return _attributeVector[index]._dataElement.get();
124 }
125
126 bool
127 HLAObjectInstance::getAttributeData(unsigned index, RTIData& data) const
128 {
129     if (!_rtiObjectInstance.valid()) {
130         SG_LOG(SG_IO, SG_ALERT, "Trying to get raw attribute data without rti object instance for \"" << getName() << "\"!");
131         return false;
132     }
133     return _rtiObjectInstance->getAttributeData(index, data);
134 }
135
136 void
137 HLAObjectInstance::setAttributeDataElement(unsigned index, const SGSharedPtr<HLADataElement>& dataElement)
138 {
139     unsigned numAttributes = getNumAttributes();
140     if (numAttributes <= index)
141         return;
142     _attributeVector.resize(numAttributes);
143     if (_attributeVector[index]._dataElement.valid())
144         _attributeVector[index]._dataElement->clearStamp();
145     _attributeVector[index]._dataElement = dataElement;
146     if (_attributeVector[index]._dataElement.valid())
147         _attributeVector[index]._dataElement->createStamp();
148 }
149
150 class HLAObjectInstance::DataElementFactoryVisitor : public HLADataElementFactoryVisitor {
151 public:
152     DataElementFactoryVisitor(const HLAPathElementMap& pathElementMap) :
153         _pathElementMap(pathElementMap)
154     { }
155     DataElementFactoryVisitor(const HLADataElement::Path& path, const HLAPathElementMap& pathElementMap) :
156         _pathElementMap(pathElementMap),
157         _path(path)
158     { }
159     virtual ~DataElementFactoryVisitor() {}
160
161     virtual void apply(const HLADataType& dataType)
162     {
163         _dataElement = createDataElement(_path, dataType);
164         if (_dataElement.valid())
165             return;
166
167         SG_LOG(SG_NETWORK, SG_ALERT, "HLA: Can not find a suitable data element for data type \""
168                << dataType.getName() << "\"");
169     }
170
171     virtual void apply(const HLAInt8DataType& dataType)
172     {
173         _dataElement = createDataElement(_path, dataType);
174         if (_dataElement.valid())
175             return;
176
177         HLADataElementFactoryVisitor::apply(dataType);
178     }
179     virtual void apply(const HLAUInt8DataType& dataType)
180     {
181         _dataElement = createDataElement(_path, dataType);
182         if (_dataElement.valid())
183             return;
184
185         HLADataElementFactoryVisitor::apply(dataType);
186     }
187     virtual void apply(const HLAInt16DataType& dataType)
188     {
189         _dataElement = createDataElement(_path, dataType);
190         if (_dataElement.valid())
191             return;
192
193         HLADataElementFactoryVisitor::apply(dataType);
194     }
195     virtual void apply(const HLAUInt16DataType& dataType)
196     {
197         _dataElement = createDataElement(_path, dataType);
198         if (_dataElement.valid())
199             return;
200
201         HLADataElementFactoryVisitor::apply(dataType);
202     }
203     virtual void apply(const HLAInt32DataType& dataType)
204     {
205         _dataElement = createDataElement(_path, dataType);
206         if (_dataElement.valid())
207             return;
208
209         HLADataElementFactoryVisitor::apply(dataType);
210     }
211     virtual void apply(const HLAUInt32DataType& dataType)
212     {
213         _dataElement = createDataElement(_path, dataType);
214         if (_dataElement.valid())
215             return;
216
217         HLADataElementFactoryVisitor::apply(dataType);
218     }
219     virtual void apply(const HLAInt64DataType& dataType)
220     {
221         _dataElement = createDataElement(_path, dataType);
222         if (_dataElement.valid())
223             return;
224
225         HLADataElementFactoryVisitor::apply(dataType);
226     }
227     virtual void apply(const HLAUInt64DataType& dataType)
228     {
229         _dataElement = createDataElement(_path, dataType);
230         if (_dataElement.valid())
231             return;
232
233         HLADataElementFactoryVisitor::apply(dataType);
234     }
235     virtual void apply(const HLAFloat32DataType& dataType)
236     {
237         _dataElement = createDataElement(_path, dataType);
238         if (_dataElement.valid())
239             return;
240
241         HLADataElementFactoryVisitor::apply(dataType);
242     }
243     virtual void apply(const HLAFloat64DataType& dataType)
244     {
245         _dataElement = createDataElement(_path, dataType);
246         if (_dataElement.valid())
247             return;
248
249         HLADataElementFactoryVisitor::apply(dataType);
250     }
251
252     class ArrayDataElementFactory : public HLAArrayDataElement::DataElementFactory {
253     public:
254         ArrayDataElementFactory(const HLADataElement::Path& path, const HLAPathElementMap& pathElementMap) :
255             _path(path)
256         {
257             for (HLAPathElementMap::const_iterator i = pathElementMap.lower_bound(path);
258                  i != pathElementMap.end(); ++i) {
259                 if (i->first.begin() != std::search(i->first.begin(), i->first.end(),
260                                                     path.begin(), path.end()))
261                     break;
262                 _pathElementMap.insert(*i);
263             }
264         }
265         virtual HLADataElement* createElement(const HLAArrayDataElement& element, unsigned index)
266         {
267             const HLADataType* dataType = element.getElementDataType();
268             if (!dataType)
269                 return 0;
270             HLADataElement::Path path = _path;
271             path.push_back(HLADataElement::PathElement(index));
272             DataElementFactoryVisitor visitor(path, _pathElementMap);
273             dataType->accept(visitor);
274             return visitor._dataElement.release();
275         }
276     private:
277         HLADataElement::Path _path;
278         HLAPathElementMap _pathElementMap;
279     };
280
281     virtual void apply(const HLAFixedArrayDataType& dataType)
282     {
283         _dataElement = createDataElement(_path, dataType);
284         if (_dataElement.valid())
285             return;
286
287         SGSharedPtr<HLAArrayDataElement> arrayDataElement;
288         arrayDataElement = new HLAArrayDataElement(&dataType);
289         arrayDataElement->setDataElementFactory(new ArrayDataElementFactory(_path, _pathElementMap));
290         arrayDataElement->setNumElements(dataType.getNumElements());
291
292         _dataElement = arrayDataElement;
293     }
294
295     virtual void apply(const HLAVariableArrayDataType& dataType)
296     {
297         _dataElement = createDataElement(_path, dataType);
298         if (_dataElement.valid())
299             return;
300
301         SGSharedPtr<HLAArrayDataElement> arrayDataElement;
302         arrayDataElement = new HLAArrayDataElement(&dataType);
303         arrayDataElement->setDataElementFactory(new ArrayDataElementFactory(_path, _pathElementMap));
304
305         _dataElement = arrayDataElement;
306     }
307
308     virtual void apply(const HLAEnumeratedDataType& dataType)
309     {
310         _dataElement = createDataElement(_path, dataType);
311         if (_dataElement.valid())
312             return;
313
314         HLADataElementFactoryVisitor::apply(dataType);
315     }
316
317     virtual void apply(const HLAFixedRecordDataType& dataType)
318     {
319         _dataElement = createDataElement(_path, dataType);
320         if (_dataElement.valid())
321             return;
322
323         SGSharedPtr<HLAFixedRecordDataElement> recordDataElement;
324         recordDataElement = new HLAFixedRecordDataElement(&dataType);
325
326         unsigned numFields = dataType.getNumFields();
327         for (unsigned i = 0; i < numFields; ++i) {
328
329             _path.push_back(HLADataElement::PathElement(dataType.getFieldName(i)));
330
331             dataType.getFieldDataType(i)->accept(*this);
332             recordDataElement->setField(i, _dataElement.release());
333
334             _path.pop_back();
335         }
336         _dataElement = recordDataElement;
337     }
338
339     class VariantRecordDataElementFactory : public HLAVariantRecordDataElement::DataElementFactory {
340     public:
341         VariantRecordDataElementFactory(const HLADataElement::Path& path, const HLAPathElementMap& pathElementMap) :
342             _path(path)
343         {
344             for (HLAPathElementMap::const_iterator i = pathElementMap.lower_bound(path);
345                  i != pathElementMap.end(); ++i) {
346                 if (i->first.begin() != std::search(i->first.begin(), i->first.end(),
347                                                     path.begin(), path.end()))
348                     break;
349                 _pathElementMap.insert(*i);
350             }
351         }
352         virtual HLADataElement* createElement(const HLAVariantRecordDataElement& element, unsigned index)
353         {
354             const HLAVariantRecordDataType* dataType = element.getDataType();
355             if (!dataType)
356                 return 0;
357             const HLADataType* alternativeDataType = element.getAlternativeDataType();
358             if (!alternativeDataType)
359                 return 0;
360             HLADataElement::Path path = _path;
361             path.push_back(HLADataElement::PathElement(dataType->getAlternativeName(index)));
362             DataElementFactoryVisitor visitor(path, _pathElementMap);
363             alternativeDataType->accept(visitor);
364             return visitor._dataElement.release();
365         }
366     private:
367         HLADataElement::Path _path;
368         HLAPathElementMap _pathElementMap;
369     };
370
371     virtual void apply(const HLAVariantRecordDataType& dataType)
372     {
373         _dataElement = createDataElement(_path, dataType);
374         if (_dataElement.valid())
375             return;
376
377         SGSharedPtr<HLAVariantRecordDataElement> variantRecordDataElement;
378         variantRecordDataElement = new HLAVariantRecordDataElement(&dataType);
379         variantRecordDataElement->setDataElementFactory(new VariantRecordDataElementFactory(_path, _pathElementMap));
380
381         _dataElement = variantRecordDataElement;
382     }
383
384 private:
385     SGSharedPtr<HLADataElement> createDataElement(const HLADataElement::Path& path, const HLADataType& dataType)
386     {
387         HLAPathElementMap::const_iterator i = _pathElementMap.find(path);
388         if (i == _pathElementMap.end()) {
389             SG_LOG(SG_IO, SG_WARN, "No dataElement provided for \""
390                    << HLADataElement::toString(path) << "\".");
391
392             return 0;
393         }
394         SGSharedPtr<HLADataElement> dataElement = i->second.getDataElement(path);
395         if (!dataElement->setDataType(&dataType)) {
396             SG_LOG(SG_IO, SG_ALERT, "Cannot set data type for data element at \""
397                    << HLADataElement::toString(path) <<  "\"!");
398             return 0;
399         }
400         SG_LOG(SG_IO, SG_DEBUG, "Using provided dataElement for \""
401                << HLADataElement::toString(path) << "\".");
402         return dataElement;
403     }
404
405     const HLAPathElementMap& _pathElementMap;
406     HLADataElement::Path _path;
407 };
408
409 void
410 HLAObjectInstance::setAttribute(unsigned index, const HLAPathElementMap& pathElementMap)
411 {
412     const HLADataType* dataType = getAttributeDataType(index);
413     if (!dataType) {
414         SG_LOG(SG_IO, SG_ALERT, "Cannot get attribute data type for setting attribute \""
415                << getAttributeName(index) << "\" at index " << index << "!");
416         return;
417     }
418
419     SG_LOG(SG_IO, SG_DEBUG, "Setting DataElement for attribute \""
420            << getAttributeName(index) << "\".");
421
422     DataElementFactoryVisitor visitor(pathElementMap);
423     dataType->accept(visitor);
424     setAttributeDataElement(index, visitor.getDataElement());
425 }
426
427 void
428 HLAObjectInstance::setAttributes(const HLAAttributePathElementMap& attributePathElementMap)
429 {
430     for (HLAAttributePathElementMap::const_iterator i = attributePathElementMap.begin();
431          i != attributePathElementMap.end(); ++i) {
432         setAttribute(i->first, i->second);
433     }
434 }
435
436 void
437 HLAObjectInstance::registerInstance()
438 {
439     if (_rtiObjectInstance.valid()) {
440         SG_LOG(SG_IO, SG_ALERT, "Trying to register object " << getName() << " already known to the RTI!");
441         return;
442     }
443     if (!_objectClass.valid()) {
444         SG_LOG(SG_IO, SG_ALERT, "Could not register object with unknown object class!");
445         return;
446     }
447     // This error must have been flagged before
448     if (!_objectClass->_rtiObjectClass.valid())
449         return;
450     _setRTIObjectInstance(_objectClass->_rtiObjectClass->registerObjectInstance(this));
451     if (!_rtiObjectInstance.valid()) {
452         SG_LOG(SG_IO, SG_ALERT, "Could not register object at the RTI!");
453         return;
454     }
455     _objectClass->_registerInstance(this);
456 }
457
458 void
459 HLAObjectInstance::deleteInstance(const RTIData& tag)
460 {
461     if (!_rtiObjectInstance.valid()) {
462         SG_LOG(SG_IO, SG_ALERT, "Trying to delete inactive object!");
463         return;
464     }
465     if (!_objectClass.valid())
466         return;
467     _objectClass->_deleteInstance(*this);
468     _rtiObjectInstance->deleteObjectInstance(tag);
469 }
470
471 void
472 HLAObjectInstance::updateAttributeValues(const RTIData& tag)
473 {
474     if (_attributeCallback.valid())
475         _attributeCallback->updateAttributeValues(*this, tag);
476     if (_updateCallback.valid()) {
477         _updateCallback->updateAttributeValues(*this, tag);
478     } else {
479         encodeAttributeValues();
480         sendAttributeValues(tag);
481     }
482 }
483
484 void
485 HLAObjectInstance::updateAttributeValues(const SGTimeStamp& timeStamp, const RTIData& tag)
486 {
487     if (_attributeCallback.valid())
488         _attributeCallback->updateAttributeValues(*this, tag);
489     if (_updateCallback.valid()) {
490         _updateCallback->updateAttributeValues(*this, timeStamp, tag);
491     } else {
492         encodeAttributeValues();
493         sendAttributeValues(timeStamp, tag);
494     }
495 }
496
497 void
498 HLAObjectInstance::encodeAttributeValues()
499 {
500     unsigned numAttributes = _attributeVector.size();
501     for (unsigned i = 0; i < numAttributes;++i) {
502         if (_attributeVector[i]._unconditionalUpdate) {
503             encodeAttributeValue(i);
504         } else if (_attributeVector[i]._enabledUpdate) {
505             const HLADataElement* dataElement = getAttributeDataElement(i);
506             if (dataElement && dataElement->getDirty())
507                 encodeAttributeValue(i);
508         }
509     }
510 }
511
512 void
513 HLAObjectInstance::encodeAttributeValue(unsigned index)
514 {
515     if (!_rtiObjectInstance.valid()) {
516         SG_LOG(SG_IO, SG_INFO, "Not updating inactive object!");
517         return;
518     }
519     HLADataElement* dataElement = getAttributeDataElement(index);
520     if (!dataElement)
521         return;
522     _rtiObjectInstance->encodeAttributeData(index, *dataElement);
523     dataElement->setDirty(false);
524 }
525
526 void
527 HLAObjectInstance::sendAttributeValues(const RTIData& tag)
528 {
529     if (!_rtiObjectInstance.valid()) {
530         SG_LOG(SG_IO, SG_INFO, "Not updating inactive object!");
531         return;
532     }
533     _rtiObjectInstance->updateAttributeValues(tag);
534 }
535
536 void
537 HLAObjectInstance::sendAttributeValues(const SGTimeStamp& timeStamp, const RTIData& tag)
538 {
539     if (!_rtiObjectInstance.valid()) {
540         SG_LOG(SG_IO, SG_INFO, "Not updating inactive object!");
541         return;
542     }
543     _rtiObjectInstance->updateAttributeValues(timeStamp, tag);
544 }
545
546 void
547 HLAObjectInstance::reflectAttributeValues(const HLAIndexList& indexList, const RTIData& tag)
548 {
549     for (HLAIndexList::const_iterator i = indexList.begin(); i != indexList.end(); ++i)
550         reflectAttributeValue(*i, tag);
551 }
552
553 void
554 HLAObjectInstance::reflectAttributeValues(const HLAIndexList& indexList,
555                                           const SGTimeStamp& timeStamp, const RTIData& tag)
556 {
557     for (HLAIndexList::const_iterator i = indexList.begin(); i != indexList.end(); ++i)
558         reflectAttributeValue(*i, timeStamp, tag);
559 }
560
561 void
562 HLAObjectInstance::reflectAttributeValue(unsigned index, const RTIData& tag)
563 {
564     HLADataElement* dataElement = getAttributeDataElement(index);
565     if (!dataElement)
566         return;
567     dataElement->setTimeStampValid(false);
568     _rtiObjectInstance->decodeAttributeData(index, *dataElement);
569 }
570
571 void
572 HLAObjectInstance::reflectAttributeValue(unsigned index, const SGTimeStamp& timeStamp, const RTIData& tag)
573 {
574     HLADataElement* dataElement = getAttributeDataElement(index);
575     if (!dataElement)
576         return;
577     dataElement->setTimeStamp(timeStamp);
578     dataElement->setTimeStampValid(true);
579     _rtiObjectInstance->decodeAttributeData(index, *dataElement);
580 }
581
582 void
583 HLAObjectInstance::_setRTIObjectInstance(RTIObjectInstance* rtiObjectInstance)
584 {
585     _rtiObjectInstance = rtiObjectInstance;
586     _rtiObjectInstance->setObjectInstance(this);
587     _name = _rtiObjectInstance->getName();
588
589     unsigned numAttributes = getNumAttributes();
590     _attributeVector.resize(numAttributes);
591     for (unsigned i = 0; i < numAttributes; ++i) {
592         HLAUpdateType updateType = getObjectClass()->getAttributeUpdateType(i);
593         if (getAttributeOwned(i) && updateType != HLAUndefinedUpdate) {
594             _attributeVector[i]._enabledUpdate = true;
595             _attributeVector[i]._unconditionalUpdate = (updateType == HLAPeriodicUpdate);
596             // In case of an owned attribute, now encode its value
597             encodeAttributeValue(i);
598         } else {
599             _attributeVector[i]._enabledUpdate = false;
600             _attributeVector[i]._unconditionalUpdate = false;
601         }
602     }
603
604     // This makes sense with any new object. Even if we registered one, there might be unpublished attributes.
605     HLAIndexList indexList;
606     for (unsigned i = 0; i < numAttributes; ++i) {
607         HLAUpdateType updateType = getObjectClass()->getAttributeUpdateType(i);
608         if (getAttributeOwned(i))
609             continue;
610         if (updateType == HLAUndefinedUpdate)
611             continue;
612         if (updateType == HLAPeriodicUpdate)
613             continue;
614         indexList.push_back(i);
615     }
616     _rtiObjectInstance->requestObjectAttributeValueUpdate(indexList);
617 }
618
619 void
620 HLAObjectInstance::_clearRTIObjectInstance()
621 {
622     if (!_rtiObjectInstance.valid())
623         return;
624
625     for (unsigned i = 0; i < _attributeVector.size(); ++i) {
626         _attributeVector[i]._enabledUpdate = false;
627         _attributeVector[i]._unconditionalUpdate = false;
628     }
629
630     _rtiObjectInstance->setObjectInstance(0);
631     _rtiObjectInstance = 0;
632 }
633
634 void
635 HLAObjectInstance::_removeInstance(const RTIData& tag)
636 {
637     if (!_objectClass.valid())
638         return;
639     _objectClass->_removeInstance(*this, tag);
640 }
641
642 void
643 HLAObjectInstance::_reflectAttributeValues(const HLAIndexList& indexList, const RTIData& tag)
644 {
645     if (_reflectCallback.valid()) {
646         _reflectCallback->reflectAttributeValues(*this, indexList, tag);
647     } else if (_attributeCallback.valid()) {
648         reflectAttributeValues(indexList, tag);
649
650         RTIIndexDataPairList dataPairList;
651         for (HLAIndexList::const_iterator i = indexList.begin(); i != indexList.end(); ++i) {
652             dataPairList.push_back(RTIIndexDataPair());
653             dataPairList.back().first = *i;
654             getAttributeData(*i, dataPairList.back().second);
655         }
656         _attributeCallback->reflectAttributeValues(*this, dataPairList, tag);
657     } else {
658         reflectAttributeValues(indexList, tag);
659     }
660 }
661
662 void
663 HLAObjectInstance::_reflectAttributeValues(const HLAIndexList& indexList, const SGTimeStamp& timeStamp, const RTIData& tag)
664 {
665     if (_reflectCallback.valid()) {
666         _reflectCallback->reflectAttributeValues(*this, indexList, timeStamp, tag);
667     } else if (_attributeCallback.valid()) {
668         reflectAttributeValues(indexList, timeStamp, tag);
669
670         RTIIndexDataPairList dataPairList;
671         for (HLAIndexList::const_iterator i = indexList.begin(); i != indexList.end(); ++i) {
672             dataPairList.push_back(RTIIndexDataPair());
673             dataPairList.back().first = *i;
674             getAttributeData(*i, dataPairList.back().second);
675         }
676         _attributeCallback->reflectAttributeValues(*this, dataPairList, timeStamp, tag);
677     } else {
678         reflectAttributeValues(indexList, timeStamp, tag);
679     }
680 }
681
682 } // namespace simgear