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.
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>
33 #include <Main/fg_props.hxx>
34 #include <FDM/fdm_shell.hxx>
37 #include "flightrecorder.hxx"
43 FGReplay::FGReplay() :
45 m_high_res_time(60.0),
46 m_medium_res_time(600.0),
47 m_low_res_time(3600.0),
48 m_medium_sample_rate(0.5), // medium term sample rate (sec)
49 m_long_sample_rate(5.0), // long term sample rate (sec)
50 m_pRecorder(new FGFlightRecorder("replay-config"))
67 * Clear all internal buffers.
72 while ( !short_term.empty() )
74 m_pRecorder->deleteRecord(short_term.front());
75 short_term.pop_front();
77 while ( !medium_term.empty() )
79 m_pRecorder->deleteRecord(medium_term.front());
80 medium_term.pop_front();
82 while ( !long_term.empty() )
84 m_pRecorder->deleteRecord(long_term.front());
85 long_term.pop_front();
87 while ( !recycler.empty() )
89 m_pRecorder->deleteRecord(recycler.front());
95 * Initialize the data structures
101 disable_replay = fgGetNode("/sim/replay/disable", true);
102 replay_master = fgGetNode("/sim/freeze/replay-state", true);
103 replay_time = fgGetNode("/sim/replay/time", true);
104 replay_time_str = fgGetNode("/sim/replay/time-str", true);
105 replay_looped = fgGetNode("/sim/replay/looped", true);
106 speed_up = fgGetNode("/sim/speed-up", true);
111 * Reset replay queues.
123 m_pRecorder->reinit();
125 m_high_res_time = fgGetDouble("/sim/replay/buffer/high-res-time", 60.0);
126 m_medium_res_time = fgGetDouble("/sim/replay/buffer/medium-res-time", 600.0); // 10 mins
127 m_low_res_time = fgGetDouble("/sim/replay/buffer/low-res-time", 3600.0); // 1 h
128 // short term sample rate is as every frame
129 m_medium_sample_rate = fgGetDouble("/sim/replay/buffer/medium-res-sample-dt", 0.5); // medium term sample rate (sec)
130 m_long_sample_rate = fgGetDouble("/sim/replay/buffer/low-res-sample-dt", 5.0); // long term sample rate (sec)
132 // Create an estimated nr of required ReplayData objects
133 // 120 is an estimated maximum frame rate.
134 int estNrObjects = (int) ((m_high_res_time*120) + (m_medium_res_time*m_medium_sample_rate) +
135 (m_low_res_time*m_long_sample_rate));
136 for (int i = 0; i < estNrObjects; i++)
138 FGReplayData* r = m_pRecorder->createEmptyRecord();
140 recycler.push_back(r);
143 SG_LOG(SG_SYSTEMS, SG_ALERT, "ReplaySystem: Out of memory!");
146 replay_master->setIntValue(0);
147 disable_replay->setBoolValue(0);
148 replay_time->setDoubleValue(0);
149 replay_time_str->setStringValue("");
153 * Bind to the property tree
163 * Unbind from the property tree
173 printTimeStr(char* pStrBuffer,double _Time, bool ShowDecimal=true)
177 unsigned int Time = (unsigned int) (_Time*10);
179 int m = (Time % 36000)/600;
180 int s = (Time % 600)/10;
183 sprintf(pStrBuffer,"%u:%02u",h,m);
185 sprintf(pStrBuffer,"%u",m);
187 sprintf(pStrBuffer,"%s:%02u.%u",pStrBuffer,s,d);
189 sprintf(pStrBuffer,"%s:%02u",pStrBuffer,s);
192 /** Start replay session
197 // freeze the fdm, resume from sim pause
198 double StartTime = get_start_time();
199 double EndTime = get_end_time();
200 fgSetDouble("/sim/replay/start-time", StartTime);
201 fgSetDouble("/sim/replay/end-time", EndTime);
203 printTimeStr(StrBuffer,StartTime,false);
204 fgSetString("/sim/replay/start-time-str", StrBuffer);
205 printTimeStr(StrBuffer,EndTime,false);
206 fgSetString("/sim/replay/end-time-str", StrBuffer);
208 unsigned long buffer_elements = short_term.size()+medium_term.size()+long_term.size();
209 fgSetDouble("/sim/replay/buffer-size-mbyte",
210 buffer_elements*m_pRecorder->getRecordSize() / (1024*1024.0));
211 if ((fgGetBool("/sim/freeze/master"))||
212 (0 == replay_master->getIntValue()))
213 fgSetString("/sim/messages/copilot", "Replay active. 'Esc' to stop.");
214 fgSetBool ("/sim/freeze/master", 0);
215 fgSetBool ("/sim/freeze/clock", 0);
216 if (0 == replay_master->getIntValue())
218 replay_master->setIntValue(1);
219 replay_time->setDoubleValue(-1);
220 replay_time_str->setStringValue("");
226 * Update the saved data
230 FGReplay::update( double dt )
232 int current_replay_state = last_replay_state;
236 if ( disable_replay->getBoolValue() )
238 current_replay_state = replay_master->getIntValue();
239 replay_master->setIntValue(0);
240 replay_time->setDoubleValue(0);
241 replay_time_str->setStringValue("");
242 disable_replay->setBoolValue(0);
243 speed_up->setDoubleValue(1.0);
244 fgSetString("/sim/messages/copilot", "Replay stopped");
247 int replay_state = replay_master->getIntValue();
249 if ((replay_state > 0)&&
250 (last_replay_state == 0))
252 // replay is starting, suspend FDM
253 /* FIXME we need to suspend/resume the FDM - not the entire FDM shell.
254 * FDM isn't available via the global subsystem manager yet, so need a
255 * method at the FDMshell for now */
256 ((FDMShell*) globals->get_subsystem("flight"))->getFDM()->suspend();
259 if ((replay_state == 0)&&
260 (last_replay_state > 0))
262 if (current_replay_state == 3)
264 // "my controls!" requested: pilot takes control at current replay position...
265 // May need to uncrash the aircraft here :)
266 fgSetBool("/sim/crashed", false);
270 // replay was active, restore most recent frame
273 // replay is finished, resume FDM
274 ((FDMShell*) globals->get_subsystem("flight"))->getFDM()->resume();
277 // remember recent state
278 last_replay_state = replay_state;
283 // replay inactive, keep recording
288 double current_time = replay_time->getDoubleValue();
289 if (current_time<=0.0)
291 // initialize start time
292 double startTime = get_start_time();
293 double endTime = get_end_time();
294 fgSetDouble( "/sim/replay/start-time", startTime );
295 fgSetDouble( "/sim/replay/end-time", endTime );
296 double duration = fgGetDouble( "/sim/replay/duration" );
297 if( duration && (duration < (endTime - startTime)) ) {
298 current_time = endTime - duration;
300 current_time = startTime;
303 bool IsFinished = replay( replay_time->getDoubleValue() );
305 current_time = (replay_looped->getBoolValue()) ? -1 : get_end_time()+0.01;
307 current_time += dt * speed_up->getDoubleValue();
308 replay_time->setDoubleValue(current_time);
310 printTimeStr(StrBuffer,current_time);
311 replay_time_str->setStringValue((const char*)StrBuffer);
312 return; // don't record the replay session
314 case 2: // normal replay operation
315 case 3: // replay operation, prepare to resume normal flight at current replay position
316 // replay paused, no-op
317 return; // don't record the replay session
319 throw sg_range_exception("unknown FGReplay state");
324 //cerr << "Recording replay" << endl;
325 sim_time += dt * speed_up->getDoubleValue();
327 // sanity check, don't collect data if FDM data isn't good
328 if (!fgGetBool("/sim/fdm-initialized", false)) {
332 FGReplayData* r = record(sim_time);
335 SG_LOG(SG_SYSTEMS, SG_ALERT, "ReplaySystem: Out of memory!");
339 // update the short term list
341 short_term.push_back( r );
343 FGReplayData *st_front = short_term.front();
347 SG_LOG(SG_SYSTEMS, SG_ALERT, "ReplaySystem: Inconsistent data!");
350 if ( sim_time - st_front->sim_time > m_high_res_time ) {
351 while ( sim_time - st_front->sim_time > m_high_res_time ) {
352 st_front = short_term.front();
353 recycler.push_back(st_front);
354 short_term.pop_front();
357 // update the medium term list
358 if ( sim_time - last_mt_time > m_medium_sample_rate ) {
359 last_mt_time = sim_time;
360 st_front = short_term.front();
361 medium_term.push_back( st_front );
362 short_term.pop_front();
364 FGReplayData *mt_front = medium_term.front();
365 if ( sim_time - mt_front->sim_time > m_medium_res_time ) {
367 while ( sim_time - mt_front->sim_time > m_medium_res_time ) {
368 mt_front = medium_term.front();
369 recycler.push_back(mt_front);
370 medium_term.pop_front();
372 // update the long term list
373 if ( sim_time - last_lt_time > m_long_sample_rate ) {
374 last_lt_time = sim_time;
375 mt_front = medium_term.front();
376 long_term.push_back( mt_front );
377 medium_term.pop_front();
379 FGReplayData *lt_front = long_term.front();
380 if ( sim_time - lt_front->sim_time > m_low_res_time ) {
382 while ( sim_time - lt_front->sim_time > m_low_res_time ) {
383 lt_front = long_term.front();
384 recycler.push_back(lt_front);
385 long_term.pop_front();
394 cout << "short term size = " << short_term.size()
395 << " time = " << sim_time - short_term.front().sim_time
397 cout << "medium term size = " << medium_term.size()
398 << " time = " << sim_time - medium_term.front().sim_time
400 cout << "long term size = " << long_term.size()
401 << " time = " << sim_time - long_term.front().sim_time
404 //stamp("point_finished");
408 FGReplay::record(double time)
410 FGReplayData* r = NULL;
414 r = recycler.front();
415 recycler.pop_front();
418 r = m_pRecorder->capture(time, r);
424 * interpolate a specific time from a specific list
427 FGReplay::interpolate( double time, const replay_list_type &list)
430 if ( list.size() == 0 )
434 } else if ( list.size() == 1 )
436 // handle list size == 1
437 replay(time, list[0]);
441 unsigned int last = list.size() - 1;
442 unsigned int first = 0;
443 unsigned int mid = ( last + first ) / 2;
448 // cout << " " << first << " <=> " << last << endl;
449 if ( last == first ) {
451 } else if ( list[mid]->sim_time < time && list[mid+1]->sim_time < time ) {
454 mid = ( last + first ) / 2;
455 } else if ( list[mid]->sim_time > time && list[mid+1]->sim_time > time ) {
458 mid = ( last + first ) / 2;
464 replay(time, list[mid+1], list[mid]);
468 * Replay a saved frame based on time, interpolate from the two
469 * nearest saved frames.
470 * Returns true when replay sequence has finished, false otherwise.
474 FGReplay::replay( double time ) {
475 // cout << "replay: " << time << " ";
476 // find the two frames to interpolate between
479 if ( short_term.size() > 0 ) {
480 t1 = short_term.back()->sim_time;
481 t2 = short_term.front()->sim_time;
483 // replay the most recent frame
484 replay( time, short_term.back() );
485 // replay is finished now
487 // cout << "first frame" << endl;
488 } else if ( time <= t1 && time >= t2 ) {
489 interpolate( time, short_term );
490 // cout << "from short term" << endl;
491 } else if ( medium_term.size() > 0 ) {
492 t1 = short_term.front()->sim_time;
493 t2 = medium_term.back()->sim_time;
494 if ( time <= t1 && time >= t2 )
496 replay(time, medium_term.back(), short_term.front());
497 // cout << "from short/medium term" << endl;
499 t1 = medium_term.back()->sim_time;
500 t2 = medium_term.front()->sim_time;
501 if ( time <= t1 && time >= t2 ) {
502 interpolate( time, medium_term );
503 // cout << "from medium term" << endl;
504 } else if ( long_term.size() > 0 ) {
505 t1 = medium_term.front()->sim_time;
506 t2 = long_term.back()->sim_time;
507 if ( time <= t1 && time >= t2 )
509 replay(time, long_term.back(), medium_term.front());
510 // cout << "from medium/long term" << endl;
512 t1 = long_term.back()->sim_time;
513 t2 = long_term.front()->sim_time;
514 if ( time <= t1 && time >= t2 ) {
515 interpolate( time, long_term );
516 // cout << "from long term" << endl;
518 // replay the oldest long term frame
519 replay(time, long_term.front());
520 // cout << "oldest long term frame" << endl;
524 // replay the oldest medium term frame
525 replay(time, medium_term.front());
526 // cout << "oldest medium term frame" << endl;
530 // replay the oldest short term frame
531 replay(time, short_term.front());
532 // cout << "oldest short term frame" << endl;
542 * given two FGReplayData elements and a time, interpolate between them
545 FGReplay::replay(double time, FGReplayData* pCurrentFrame, FGReplayData* pOldFrame)
547 m_pRecorder->replay(time,pCurrentFrame,pOldFrame);
551 FGReplay::get_start_time()
553 if ( long_term.size() > 0 )
555 return long_term.front()->sim_time;
556 } else if ( medium_term.size() > 0 )
558 return medium_term.front()->sim_time;
559 } else if ( short_term.size() )
561 return short_term.front()->sim_time;
569 FGReplay::get_end_time()
571 if ( short_term.size() )
573 return short_term.back()->sim_time;