1 // replay.cxx - a system to record and replay FlightGear flights
3 // Written by Curtis Olson, started July 2003.
4 // Updated by Thorsten Brehm, September 2011 and November 2012.
6 // Copyright (C) 2003 Curtis L. Olson - http://www.flightgear.org/~curt
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License as
10 // published by the Free Software Foundation; either version 2 of the
11 // License, or (at your option) any later version.
13 // This program is distributed in the hope that it will be useful, but
14 // WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 // General Public License for more details.
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
30 #include <simgear/constants.h>
31 #include <simgear/structure/exception.hxx>
32 #include <simgear/props/props_io.hxx>
33 #include <simgear/misc/gzcontainerfile.hxx>
34 #include <simgear/misc/sg_dir.hxx>
35 #include <simgear/misc/stdint.hxx>
36 #include <simgear/misc/strutils.hxx>
38 #include <Main/fg_props.hxx>
41 #include "flightrecorder.hxx"
45 using simgear::gzContainerReader;
46 using simgear::gzContainerWriter;
49 #define MY_SG_DEBUG SG_DEBUG
51 #define MY_SG_DEBUG SG_ALERT
54 /** Magic string to verify valid FG flight recorder tapes. */
55 static const char* const FlightRecorderFileMagic = "FlightGear Flight Recorder Tape";
57 namespace ReplayContainer
62 Header = 0, /**< Used for initial file header (fixed identification string). */
63 MetaData = 1, /**< XML data / properties with arbitrary data, such as description, aircraft type, ... */
64 Properties = 2, /**< XML data describing the recorded flight recorder properties.
65 Format is identical to flight recorder XML configuration. Also contains some
66 extra data to verify flight recorder consistency. */
67 RawData = 3 /**< Actual binary data blobs (the recorder's tape).
68 One "RawData" blob is used for each resolution. */
76 FGReplay::FGReplay() :
82 m_high_res_time(60.0),
83 m_medium_res_time(600.0),
84 m_low_res_time(3600.0),
85 m_medium_sample_rate(0.5), // medium term sample rate (sec)
86 m_long_sample_rate(5.0), // long term sample rate (sec)
87 m_pRecorder(new FGFlightRecorder("replay-config"))
104 * Clear all internal buffers.
109 while ( !short_term.empty() )
111 m_pRecorder->deleteRecord(short_term.front());
112 short_term.pop_front();
114 while ( !medium_term.empty() )
116 m_pRecorder->deleteRecord(medium_term.front());
117 medium_term.pop_front();
119 while ( !long_term.empty() )
121 m_pRecorder->deleteRecord(long_term.front());
122 long_term.pop_front();
124 while ( !recycler.empty() )
126 m_pRecorder->deleteRecord(recycler.front());
127 recycler.pop_front();
130 // clear messages belonging to old replay session
131 fgGetNode("/sim/replay/messages", 0, true)->removeChildren("msg", false);
135 * Initialize the data structures
141 disable_replay = fgGetNode("/sim/replay/disable", true);
142 replay_master = fgGetNode("/sim/replay/replay-state", true);
143 replay_time = fgGetNode("/sim/replay/time", true);
144 replay_time_str = fgGetNode("/sim/replay/time-str", true);
145 replay_looped = fgGetNode("/sim/replay/looped", true);
146 speed_up = fgGetNode("/sim/speed-up", true);
148 // alias to keep backward compatibility
149 fgGetNode("/sim/freeze/replay-state", true)->alias(replay_master);
155 * Reset replay queues.
168 m_pRecorder->reinit();
170 m_high_res_time = fgGetDouble("/sim/replay/buffer/high-res-time", 60.0);
171 m_medium_res_time = fgGetDouble("/sim/replay/buffer/medium-res-time", 600.0); // 10 mins
172 m_low_res_time = fgGetDouble("/sim/replay/buffer/low-res-time", 3600.0); // 1 h
173 // short term sample rate is as every frame
174 m_medium_sample_rate = fgGetDouble("/sim/replay/buffer/medium-res-sample-dt", 0.5); // medium term sample rate (sec)
175 m_long_sample_rate = fgGetDouble("/sim/replay/buffer/low-res-sample-dt", 5.0); // long term sample rate (sec)
180 replay_master->setIntValue(0);
181 disable_replay->setBoolValue(0);
182 replay_time->setDoubleValue(0);
183 replay_time_str->setStringValue("");
187 * Bind to the property tree
196 * Unbind from the property tree
206 FGReplay::fillRecycler()
208 // Create an estimated nr of required ReplayData objects
209 // 120 is an estimated maximum frame rate.
210 int estNrObjects = (int) ((m_high_res_time*120) + (m_medium_res_time*m_medium_sample_rate) +
211 (m_low_res_time*m_long_sample_rate));
212 for (int i = 0; i < estNrObjects; i++)
214 FGReplayData* r = m_pRecorder->createEmptyRecord();
216 recycler.push_back(r);
219 SG_LOG(SG_SYSTEMS, SG_ALERT, "ReplaySystem: Out of memory!");
225 printTimeStr(char* pStrBuffer,double _Time, bool ShowDecimal=true)
229 unsigned int Time = _Time*10;
230 unsigned int h = Time/36000;
231 unsigned int m = (Time % 36000)/600;
232 unsigned int s = (Time % 600)/10;
233 unsigned int d = Time % 10;
237 len = sprintf(pStrBuffer,"%u:%02u:%02u",h,m,s);
239 len = sprintf(pStrBuffer,"%u:%02u",m,s);
248 sprintf(&pStrBuffer[len],".%u",d);
252 FGReplay::guiMessage(const char* message)
254 fgSetString("/sim/messages/copilot", message);
258 FGReplay::loadMessages()
261 replay_messages.clear();
262 simgear::PropertyList msgs = fgGetNode("/sim/replay/messages", true)->getChildren("msg");
264 for (simgear::PropertyList::iterator it = msgs.begin();it != msgs.end();++it)
266 const char* msgText = (*it)->getStringValue("text", "");
267 const double msgTime = (*it)->getDoubleValue("time", -1.0);
268 const char* msgSpeaker = (*it)->getStringValue("speaker", "pilot");
269 if ((msgText[0] != 0)&&(msgTime >= 0))
271 FGReplayMessages data;
272 data.sim_time = msgTime;
273 data.message = msgText;
274 data.speaker = msgSpeaker;
275 replay_messages.push_back(data);
278 current_msg = replay_messages.begin();
282 FGReplay::replayMessage(double time)
284 if (time < last_msg_time)
286 current_msg = replay_messages.begin();
289 // check if messages have to be triggered
290 while ((current_msg != replay_messages.end())&&
291 (time >= current_msg->sim_time))
293 // don't trigger messages when too long ago (fast-forward/skipped replay)
294 if (time - current_msg->sim_time < 3.0)
296 fgGetNode("/sim/messages", true)->getNode(current_msg->speaker, true)->setStringValue(current_msg->message);
300 last_msg_time = time;
303 /** Start replay session
306 FGReplay::start(bool NewTape)
308 // freeze the fdm, resume from sim pause
309 double StartTime = get_start_time();
310 double EndTime = get_end_time();
311 was_finished_already = false;
312 fgSetDouble("/sim/replay/start-time", StartTime);
313 fgSetDouble("/sim/replay/end-time", EndTime);
315 printTimeStr(StrBuffer,StartTime,false);
316 fgSetString("/sim/replay/start-time-str", StrBuffer);
317 printTimeStr(StrBuffer,EndTime,false);
318 fgSetString("/sim/replay/end-time-str", StrBuffer);
320 unsigned long buffer_elements = short_term.size()+medium_term.size()+long_term.size();
321 fgSetDouble("/sim/replay/buffer-size-mbyte",
322 buffer_elements*m_pRecorder->getRecordSize() / (1024*1024.0));
323 if ((fgGetBool("/sim/freeze/master"))||
324 (0 == replay_master->getIntValue()))
325 guiMessage("Replay active. 'Esc' to stop.");
326 fgSetBool ("/sim/freeze/master", 0);
327 fgSetBool ("/sim/freeze/clock", 0);
328 if (0 == replay_master->getIntValue())
330 replay_master->setIntValue(1);
333 // start replay at initial time, when loading a new tape
334 replay_time->setDoubleValue(StartTime);
338 // start replay at "loop interval" when starting instant replay
339 replay_time->setDoubleValue(-1);
341 replay_time_str->setStringValue("");
349 * Update the saved data
353 FGReplay::update( double dt )
355 int current_replay_state = last_replay_state;
359 if ( disable_replay->getBoolValue() )
361 if (fgGetBool("/sim/freeze/master",false)||
362 fgGetBool("/sim/freeze/clock",false))
364 // unpause - disable the replay system in next loop
365 fgSetBool("/sim/freeze/master",false);
366 fgSetBool("/sim/freeze/clock",false);
367 last_replay_state = 1;
370 if ((replay_master->getIntValue() != 3)||
371 (last_replay_state == 3))
373 // disable the replay system
374 current_replay_state = replay_master->getIntValue();
375 replay_master->setIntValue(0);
376 replay_time->setDoubleValue(0);
377 replay_time_str->setStringValue("");
378 disable_replay->setBoolValue(0);
379 speed_up->setDoubleValue(1.0);
380 speed_up->setDoubleValue(1.0);
381 if (fgGetBool("/sim/replay/mute",false))
383 fgSetBool("/sim/sound/enabled",true);
384 fgSetBool("/sim/replay/mute",false);
386 guiMessage("Replay stopped. Your controls!");
390 int replay_state = replay_master->getIntValue();
391 if ((replay_state == 0)&&
392 (last_replay_state > 0))
394 if (current_replay_state == 3)
396 // take control at current replay position ("My controls!").
397 // May need to uncrash the aircraft here :)
398 fgSetBool("/sim/crashed", false);
402 // normal replay exit, restore most recent frame
406 // replay is finished
407 last_replay_state = replay_state;
411 // remember recent state
412 last_replay_state = replay_state;
417 // replay inactive, keep recording
419 case 1: // normal replay
420 case 3: // prepare to resume normal flight at current replay position
423 double current_time = replay_time->getDoubleValue();
424 bool ResetTime = (current_time<=0.0);
427 // initialize start time
428 double startTime = get_start_time();
429 double endTime = get_end_time();
430 fgSetDouble( "/sim/replay/start-time", startTime );
431 fgSetDouble( "/sim/replay/end-time", endTime );
433 if (replay_looped->getBoolValue())
434 fgGetDouble("/sim/replay/duration");
435 if( duration && (duration < (endTime - startTime)) ) {
436 current_time = endTime - duration;
438 current_time = startTime;
441 bool IsFinished = replay( replay_time->getDoubleValue() );
444 if (!was_finished_already)
446 guiMessage("End of tape. 'Esc' to return.");
447 was_finished_already = true;
449 current_time = (replay_looped->getBoolValue()) ? -1 : get_end_time()+0.01;
453 current_time += dt * speed_up->getDoubleValue();
454 was_finished_already = false;
456 replay_time->setDoubleValue(current_time);
458 printTimeStr(StrBuffer,current_time);
459 replay_time_str->setStringValue((const char*)StrBuffer);
461 // when time skipped (looped replay), trigger listeners to reset views etc
463 replay_master->setIntValue(replay_state);
465 return; // don't record the replay session
467 case 2: // normal replay operation
468 return; // don't record the replay session
470 throw sg_range_exception("unknown FGReplay state");
475 // sanity check, don't collect data if FDM data isn't good
476 if ((!fgGetBool("/sim/fdm-initialized", false))||(dt==0.0))
480 double new_sim_time = sim_time + dt * speed_up->getDoubleValue();
481 // don't record multiple records with the same timestamp (or go backwards in time)
482 if (new_sim_time <= sim_time)
484 SG_LOG(SG_SYSTEMS, SG_ALERT, "ReplaySystem: Time warp detected!");
487 sim_time = new_sim_time;
490 FGReplayData* r = record(sim_time);
493 SG_LOG(SG_SYSTEMS, SG_ALERT, "ReplaySystem: Out of memory!");
497 // update the short term list
498 short_term.push_back( r );
499 FGReplayData *st_front = short_term.front();
503 SG_LOG(SG_SYSTEMS, SG_ALERT, "ReplaySystem: Inconsistent data!");
506 if ( sim_time - st_front->sim_time > m_high_res_time )
508 while ( sim_time - st_front->sim_time > m_high_res_time )
510 st_front = short_term.front();
511 recycler.push_back(st_front);
512 short_term.pop_front();
515 // update the medium term list
516 if ( sim_time - last_mt_time > m_medium_sample_rate )
518 last_mt_time = sim_time;
519 st_front = short_term.front();
520 medium_term.push_back( st_front );
521 short_term.pop_front();
523 FGReplayData *mt_front = medium_term.front();
524 if ( sim_time - mt_front->sim_time > m_medium_res_time )
526 while ( sim_time - mt_front->sim_time > m_medium_res_time )
528 mt_front = medium_term.front();
529 recycler.push_back(mt_front);
530 medium_term.pop_front();
532 // update the long term list
533 if ( sim_time - last_lt_time > m_long_sample_rate )
535 last_lt_time = sim_time;
536 mt_front = medium_term.front();
537 long_term.push_back( mt_front );
538 medium_term.pop_front();
540 FGReplayData *lt_front = long_term.front();
541 if ( sim_time - lt_front->sim_time > m_low_res_time )
543 while ( sim_time - lt_front->sim_time > m_low_res_time )
545 lt_front = long_term.front();
546 recycler.push_back(lt_front);
547 long_term.pop_front();
556 cout << "short term size = " << short_term.size()
557 << " time = " << sim_time - short_term.front().sim_time
559 cout << "medium term size = " << medium_term.size()
560 << " time = " << sim_time - medium_term.front().sim_time
562 cout << "long term size = " << long_term.size()
563 << " time = " << sim_time - long_term.front().sim_time
566 //stamp("point_finished");
570 FGReplay::record(double time)
572 FGReplayData* r = NULL;
576 r = recycler.front();
577 recycler.pop_front();
580 return m_pRecorder->capture(time, r);
584 * interpolate a specific time from a specific list
587 FGReplay::interpolate( double time, const replay_list_type &list)
590 if ( list.size() == 0 )
594 } else if ( list.size() == 1 )
596 // handle list size == 1
597 replay(time, list[0]);
601 unsigned int last = list.size() - 1;
602 unsigned int first = 0;
603 unsigned int mid = ( last + first ) / 2;
608 // cout << " " << first << " <=> " << last << endl;
609 if ( last == first ) {
611 } else if ( list[mid]->sim_time < time && list[mid+1]->sim_time < time ) {
614 mid = ( last + first ) / 2;
615 } else if ( list[mid]->sim_time > time && list[mid+1]->sim_time > time ) {
618 mid = ( last + first ) / 2;
624 replay(time, list[mid+1], list[mid]);
628 * Replay a saved frame based on time, interpolate from the two
629 * nearest saved frames.
630 * Returns true when replay sequence has finished, false otherwise.
634 FGReplay::replay( double time ) {
635 // cout << "replay: " << time << " ";
636 // find the two frames to interpolate between
641 if ( short_term.size() > 0 ) {
642 t1 = short_term.back()->sim_time;
643 t2 = short_term.front()->sim_time;
645 // replay the most recent frame
646 replay( time, short_term.back() );
647 // replay is finished now
649 } else if ( time <= t1 && time >= t2 ) {
650 interpolate( time, short_term );
651 } else if ( medium_term.size() > 0 ) {
652 t1 = short_term.front()->sim_time;
653 t2 = medium_term.back()->sim_time;
654 if ( time <= t1 && time >= t2 )
656 replay(time, medium_term.back(), short_term.front());
658 t1 = medium_term.back()->sim_time;
659 t2 = medium_term.front()->sim_time;
660 if ( time <= t1 && time >= t2 ) {
661 interpolate( time, medium_term );
662 } else if ( long_term.size() > 0 ) {
663 t1 = medium_term.front()->sim_time;
664 t2 = long_term.back()->sim_time;
665 if ( time <= t1 && time >= t2 )
667 replay(time, long_term.back(), medium_term.front());
669 t1 = long_term.back()->sim_time;
670 t2 = long_term.front()->sim_time;
671 if ( time <= t1 && time >= t2 ) {
672 interpolate( time, long_term );
674 // replay the oldest long term frame
675 replay(time, long_term.front());
679 // replay the oldest medium term frame
680 replay(time, medium_term.front());
684 // replay the oldest short term frame
685 replay(time, short_term.front());
695 * given two FGReplayData elements and a time, interpolate between them
698 FGReplay::replay(double time, FGReplayData* pCurrentFrame, FGReplayData* pOldFrame)
700 m_pRecorder->replay(time,pCurrentFrame,pOldFrame);
704 FGReplay::get_start_time()
706 if ( long_term.size() > 0 )
708 return long_term.front()->sim_time;
709 } else if ( medium_term.size() > 0 )
711 return medium_term.front()->sim_time;
712 } else if ( short_term.size() )
714 return short_term.front()->sim_time;
722 FGReplay::get_end_time()
724 if ( short_term.size() )
726 return short_term.back()->sim_time;
733 /** Save raw replay data in a separate container */
735 saveRawReplayData(gzContainerWriter& output, const replay_list_type& ReplayData, size_t RecordSize)
737 // get number of records in this stream
738 size_t Count = ReplayData.size();
740 // write container header for raw data
741 if (!output.writeContainerHeader(ReplayContainer::RawData, Count * RecordSize))
743 SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to save replay data. Cannot write data container. Disk full?");
747 // write the raw data (all records in the given list)
748 replay_list_type::const_iterator it = ReplayData.begin();
749 size_t CheckCount = 0;
750 while ((it != ReplayData.end())&&
753 const FGReplayData* pRecord = *it++;
754 output.write((char*)pRecord, RecordSize);
758 // Did we really write as much as we intended?
759 if (CheckCount != Count)
761 SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to save replay data. Expected to write " << Count << " records, but wrote " << CheckCount);
765 SG_LOG(SG_SYSTEMS, MY_SG_DEBUG, "Saved " << CheckCount << " records of size " << RecordSize);
766 return !output.fail();
769 /** Load raw replay data from a separate container */
771 loadRawReplayData(gzContainerReader& input, FGFlightRecorder* pRecorder, replay_list_type& ReplayData, size_t RecordSize)
774 simgear::ContainerType Type = ReplayContainer::Invalid;
776 // write container header for raw data
777 if (!input.readContainerHeader(&Type, &Size))
779 SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to load replay data. Missing data container.");
783 if (Type != ReplayContainer::RawData)
785 SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to load replay data. Expected data container, got " << Type);
790 size_t Count = Size / RecordSize;
791 SG_LOG(SG_SYSTEMS, MY_SG_DEBUG, "Loading replay data. Container size is " << Size << ", record size " << RecordSize <<
792 ", expected record count " << Count << ".");
794 size_t CheckCount = 0;
795 for (CheckCount=0; (CheckCount<Count)&&(!input.eof()); ++CheckCount)
797 FGReplayData* pBuffer = pRecorder->createEmptyRecord();
798 input.read((char*) pBuffer, RecordSize);
799 ReplayData.push_back(pBuffer);
802 // did we get all we have hoped for?
803 if (CheckCount != Count)
807 SG_LOG(SG_SYSTEMS, SG_ALERT, "Unexpected end of file.");
809 SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to load replay data. Expected " << Count << " records, but got " << CheckCount);
813 SG_LOG(SG_SYSTEMS, MY_SG_DEBUG, "Loaded " << CheckCount << " records of size " << RecordSize);
817 /** Write flight recorder tape with given filename and meta properties to disk */
819 FGReplay::saveTape(const char* Filename, SGPropertyNode* MetaDataProps)
823 /* open output stream *******************************************/
824 gzContainerWriter output(Filename, FlightRecorderFileMagic);
827 SG_LOG(SG_SYSTEMS, SG_ALERT, "Cannot open file" << Filename);
831 /* write meta data **********************************************/
832 ok &= output.writeContainer(ReplayContainer::MetaData, MetaDataProps);
834 /* write flight recorder configuration **************************/
835 SGPropertyNode_ptr Config;
838 Config = new SGPropertyNode();
839 m_pRecorder->getConfig(Config.get());
840 ok &= output.writeContainer(ReplayContainer::Properties, Config.get());
843 /* write raw data ***********************************************/
846 size_t RecordSize = Config->getIntValue("recorder/record-size", 0);
847 SG_LOG(SG_SYSTEMS, MY_SG_DEBUG, "Total signal count: " << Config->getIntValue("recorder/signal-count", 0)
848 << ", record size: " << RecordSize);
850 ok &= saveRawReplayData(output, short_term, RecordSize);
852 ok &= saveRawReplayData(output, medium_term, RecordSize);
854 ok &= saveRawReplayData(output, long_term, RecordSize);
858 /* done *********************************************************/
864 /** Write flight recorder tape to disk. User/script command. */
866 FGReplay::saveTape(const SGPropertyNode* ConfigData)
868 const char* tapeDirectory = fgGetString("/sim/replay/tape-directory", "");
869 const char* aircraftType = fgGetString("/sim/aircraft", "unknown");
871 SGPropertyNode_ptr myMetaData = new SGPropertyNode();
872 SGPropertyNode* meta = myMetaData->getNode("meta", 0, true);
874 // add some data to the file - so we know for which aircraft/version it was recorded
875 meta->setStringValue("aircraft-type", aircraftType);
876 meta->setStringValue("aircraft-description", fgGetString("/sim/description", ""));
877 meta->setStringValue("aircraft-fdm", fgGetString("/sim/flight-model", ""));
878 meta->setStringValue("closest-airport-id", fgGetString("/sim/airport/closest-airport-id", ""));
879 const char* aircraft_version = fgGetString("/sim/aircraft-version", "");
880 if (aircraft_version[0]==0)
881 aircraft_version = "(undefined)";
882 meta->setStringValue("aircraft-version", aircraft_version);
884 // add information on the tape's recording duration
885 double Duration = get_end_time()-get_start_time();
886 meta->setDoubleValue("tape-duration", Duration);
888 printTimeStr(StrBuffer, Duration, false);
889 meta->setStringValue("tape-duration-str", StrBuffer);
891 // add simulator version
892 copyProperties(fgGetNode("/sim/version", 0, true), meta->getNode("version", 0, true));
893 if (ConfigData->getNode("user-data"))
895 copyProperties(ConfigData->getNode("user-data"), meta->getNode("user-data", 0, true));
898 // store replay messages
899 copyProperties(fgGetNode("/sim/replay/messages", 0, true), myMetaData->getNode("messages", 0, true));
901 // generate file name (directory + aircraft type + date + time + suffix)
902 SGPath p(tapeDirectory);
903 p.append(aircraftType);
905 time_t calendar_time = time(NULL);
907 local_tm = localtime( &calendar_time );
909 strftime( time_str, 256, "%Y%02m%02d-%02H%02M%02S", local_tm);
914 // make sure we're not overwriting something
918 SG_LOG(SG_SYSTEMS, SG_ALERT, "Error, flight recorder tape file with same name already exists.");
923 ok &= saveTape(p.c_str(), myMetaData.get());
926 guiMessage("Flight recorder tape saved successfully!");
928 guiMessage("Failed to save tape! See log output.");
933 /** Read a flight recorder tape with given filename from disk and return meta properties.
934 * Actual data and signal configuration is not read when in "Preview" mode.
937 FGReplay::loadTape(const char* Filename, bool Preview, SGPropertyNode* UserData)
941 /* open input stream ********************************************/
942 gzContainerReader input(Filename, FlightRecorderFileMagic);
943 if (input.eof() || !input.good())
945 SG_LOG(SG_SYSTEMS, SG_ALERT, "Cannot open file " << Filename);
949 SGPropertyNode_ptr MetaDataProps = new SGPropertyNode();
951 /* read meta data ***********************************************/
954 char* MetaData = NULL;
956 simgear::ContainerType Type = ReplayContainer::Invalid;
957 if (!input.readContainer(&Type, &MetaData, &Size) || Size<1)
959 SG_LOG(SG_SYSTEMS, SG_ALERT, "File not recognized. This is not a valid FlightGear flight recorder tape: " << Filename
960 << ". Invalid meta data.");
964 if (Type != ReplayContainer::MetaData)
966 SG_LOG(SG_SYSTEMS, MY_SG_DEBUG, "Invalid header. Container type " << Type);
967 SG_LOG(SG_SYSTEMS, SG_ALERT, "File not recognized. This is not a valid FlightGear flight recorder tape: " << Filename);
974 readProperties(MetaData, Size-1, MetaDataProps);
975 copyProperties(MetaDataProps->getNode("meta", 0, true), UserData);
976 } catch (const sg_exception &e)
978 SG_LOG(SG_SYSTEMS, SG_ALERT, "Error reading flight recorder tape: " << Filename
979 << ", XML parser message:" << e.getFormattedMessage());
986 //printf("%s\n", MetaData);
992 /* read flight recorder configuration **************************/
993 if ((ok)&&(!Preview))
995 SG_LOG(SG_SYSTEMS, MY_SG_DEBUG, "Loading flight recorder data...");
996 char* ConfigXML = NULL;
998 simgear::ContainerType Type = ReplayContainer::Invalid;
999 if (!input.readContainer(&Type, &ConfigXML, &Size) || Size<1)
1001 SG_LOG(SG_SYSTEMS, SG_ALERT, "File not recognized. This is not a valid FlightGear flight recorder tape: " << Filename
1002 << ". Invalid configuration container.");
1006 if ((!ConfigXML)||(Type != ReplayContainer::Properties))
1008 SG_LOG(SG_SYSTEMS, SG_ALERT, "File not recognized. This is not a valid FlightGear flight recorder tape: " << Filename
1009 << ". Unexpected container type, expected \"properties\".");
1013 SGPropertyNode_ptr Config = new SGPropertyNode();
1018 readProperties(ConfigXML, Size-1, Config);
1019 } catch (const sg_exception &e)
1021 SG_LOG(SG_SYSTEMS, SG_ALERT, "Error reading flight recorder tape: " << Filename
1022 << ", XML parser message:" << e.getFormattedMessage());
1027 // reconfigure the recorder - and wipe old data (no longer matches the current recorder)
1028 m_pRecorder->reinit(Config);
1040 /* read raw data ***********************************************/
1043 size_t RecordSize = m_pRecorder->getRecordSize();
1044 size_t OriginalSize = Config->getIntValue("recorder/record-size", 0);
1045 // check consistency - ugly things happen when data vs signals mismatch
1046 if ((OriginalSize != RecordSize)&&
1047 (OriginalSize != 0))
1050 SG_LOG(SG_SYSTEMS, SG_ALERT, "Error: Data inconsistency. Flight recorder tape has record size " << RecordSize
1051 << ", expected size was " << OriginalSize << ".");
1055 ok &= loadRawReplayData(input, m_pRecorder, short_term, RecordSize);
1057 ok &= loadRawReplayData(input, m_pRecorder, medium_term, RecordSize);
1059 ok &= loadRawReplayData(input, m_pRecorder, long_term, RecordSize);
1061 // restore replay messages
1064 copyProperties(MetaDataProps->getNode("messages", 0, true),
1065 fgGetNode("/sim/replay/messages", 0, true));
1067 sim_time = get_end_time();
1068 // TODO we could (re)store these too
1069 last_mt_time = last_lt_time = sim_time;
1071 /* done *********************************************************/
1080 guiMessage("Flight recorder tape loaded successfully!");
1084 guiMessage("Failed to load tape. See log output.");
1090 /** List available tapes in current directory.
1091 * Limits to tapes matching current aircraft when SameAircraftFilter is enabled.
1094 FGReplay::listTapes(bool SameAircraftFilter, const SGPath& tapeDirectory)
1096 const std::string& aircraftType = simgear::strutils::uppercase(fgGetString("/sim/aircraft", "unknown"));
1098 // process directory listing of ".fgtape" files
1099 simgear::Dir dir(tapeDirectory);
1100 simgear::PathList list = dir.children(simgear::Dir::TYPE_FILE, ".fgtape");
1102 SGPropertyNode* TapeList = fgGetNode("/sim/replay/tape-list", true);
1103 TapeList->removeChildren("tape", false);
1105 size_t l = aircraftType.size();
1106 for (simgear::PathList::iterator it = list.begin(); it!=list.end(); ++it)
1108 SGPath file(it->file());
1109 std::string name(file.base());
1110 if ((!SameAircraftFilter)||
1111 (0==simgear::strutils::uppercase(name).compare(0,l, aircraftType)))
1113 TapeList->getNode("tape", Index++, true)->setStringValue(name);
1120 /** Load a flight recorder tape from disk. User/script command. */
1122 FGReplay::loadTape(const SGPropertyNode* ConfigData)
1124 SGPath tapeDirectory(fgGetString("/sim/replay/tape-directory", ""));
1126 // see if shall really load the file - or just obtain the meta data for preview
1127 bool Preview = ConfigData->getBoolValue("preview", 0);
1129 // file/tape to be loaded
1130 std::string tape = ConfigData->getStringValue("tape", "");
1135 return listTapes(ConfigData->getBoolValue("same-aircraft", 0), tapeDirectory);
1140 SGPropertyNode* UserData = fgGetNode("/sim/gui/dialogs/flightrecorder/preview", true);
1141 tapeDirectory.append(tape);
1142 tapeDirectory.concat(".fgtape");
1143 SG_LOG(SG_SYSTEMS, MY_SG_DEBUG, "Checking flight recorder file " << tapeDirectory << ", preview: " << Preview);
1144 return loadTape(tapeDirectory.c_str(), Preview, UserData);