From 419f09f804d83c003021d4ec7be97a6889ef960c Mon Sep 17 00:00:00 2001 From: curt Date: Thu, 17 Jul 2003 18:24:17 +0000 Subject: [PATCH] Curt: I have added a fledgling replay system that records flight data and control positions during the flight. I have added an internal command called "replay" which will trigger a replay of the entire saved flight data set. This could be bound to a keyboard or menu command, in fact this entire module is screaming for someone to build a gui to control playback speed, amount of playback, etc. This is the initial version so there are kinks that still need to be worked out, please be patient. --- configure.ac | 1 + src/FDM/ExternalNet/ExternalNet.cxx | 2 +- src/FDM/ExternalPipe/ExternalPipe.cxx | 2 +- src/Main/Makefile.am | 1 + src/Main/fg_commands.cxx | 25 ++ src/Main/fg_init.cxx | 6 + src/Main/main.cxx | 29 ++- src/Makefile.am | 1 + src/Network/native_ctrls.cxx | 42 ++-- src/Network/native_ctrls.hxx | 6 +- src/Replay/Makefile.am | 5 + src/Replay/replay.cxx | 339 ++++++++++++++++++++++++++ src/Replay/replay.hxx | 96 ++++++++ 13 files changed, 530 insertions(+), 25 deletions(-) create mode 100644 src/Replay/Makefile.am create mode 100644 src/Replay/replay.cxx create mode 100644 src/Replay/replay.hxx diff --git a/configure.ac b/configure.ac index 1cb941926..a98c8fbe8 100644 --- a/configure.ac +++ b/configure.ac @@ -595,6 +595,7 @@ AC_CONFIG_FILES([ \ src/Network/Makefile \ src/NetworkOLK/Makefile \ src/Objects/Makefile \ + src/Replay/Makefile \ src/Scenery/Makefile \ src/Scripting/Makefile \ src/Sound/Makefile \ diff --git a/src/FDM/ExternalNet/ExternalNet.cxx b/src/FDM/ExternalNet/ExternalNet.cxx index 46129e24e..79c142265 100644 --- a/src/FDM/ExternalNet/ExternalNet.cxx +++ b/src/FDM/ExternalNet/ExternalNet.cxx @@ -164,7 +164,7 @@ void FGExternalNet::update( double dt ) { // Send control positions to remote fdm length = sizeof(ctrls); - FGProps2NetCtrls( &ctrls ); + FGProps2NetCtrls( &ctrls, true, true ); if ( data_client.send( (char *)(& ctrls), length, 0 ) != length ) { SG_LOG( SG_IO, SG_DEBUG, "Error writing data." ); } else { diff --git a/src/FDM/ExternalPipe/ExternalPipe.cxx b/src/FDM/ExternalPipe/ExternalPipe.cxx index c43b99f54..312fe1d3d 100644 --- a/src/FDM/ExternalPipe/ExternalPipe.cxx +++ b/src/FDM/ExternalPipe/ExternalPipe.cxx @@ -192,7 +192,7 @@ void FGExternalPipe::update( double dt ) { // Send control positions to remote fdm length = sizeof(ctrls); - FGProps2NetCtrls( &ctrls, false ); + FGProps2NetCtrls( &ctrls, true, false ); char *ptr = buf; *ptr = '2'; ptr++; diff --git a/src/Main/Makefile.am b/src/Main/Makefile.am index ccaa40f86..7c737b018 100644 --- a/src/Main/Makefile.am +++ b/src/Main/Makefile.am @@ -85,6 +85,7 @@ fgfs_LDADD = \ $(MPLAYER_LIBS) \ $(NETWORK_LIBS) \ $(top_builddir)/src/Objects/libObjects.a \ + $(top_builddir)/src/Replay/libReplay.a \ $(top_builddir)/src/Systems/libSystems.a \ $(top_builddir)/src/Time/libTime.a \ $(WEATHER_LIBS) \ diff --git a/src/Main/fg_commands.cxx b/src/Main/fg_commands.cxx index a6d53cd5c..2e20d8830 100644 --- a/src/Main/fg_commands.cxx +++ b/src/Main/fg_commands.cxx @@ -20,6 +20,7 @@ #include #include #include +#include #include #if defined(HAVE_PLIB_PSL) # include @@ -821,6 +822,29 @@ do_log_level (const SGPropertyNode * arg) return true; } +/** + * Built-in command: replay the FDR buffer + */ +static bool +do_replay (const SGPropertyNode * arg) +{ + // freeze the master fdm + fgSetBool( "/sim/freeze/master", true ); + fgSetBool( "/sim/freeze/clock", true ); + fgSetBool( "/sim/replay/master", true ); + + FGReplay *r = (FGReplay *)(globals->get_subsystem( "replay" )); + + fgSetDouble( "/sim/replay/start-time", r->get_start_time() ); + fgSetDouble( "/sim/replay/end-time", r->get_end_time() ); + fgSetDouble( "/sim/replay/time", r->get_start_time() ); + + cout << "start = " << r->get_start_time() + << " end = " << r->get_end_time() << endl; + + return true; +} + @@ -871,6 +895,7 @@ static struct { { "dialog-apply", do_dialog_apply }, { "presets-commit", do_presets_commit }, { "log-level", do_log_level }, + { "replay", do_replay }, { 0, 0 } // zero-terminated }; diff --git a/src/Main/fg_init.cxx b/src/Main/fg_init.cxx index 59b710428..2342ed739 100644 --- a/src/Main/fg_init.cxx +++ b/src/Main/fg_init.cxx @@ -103,6 +103,7 @@ #include #include #include +#include #include #include #if defined(HAVE_PLIB_PSL) @@ -1634,6 +1635,11 @@ bool fgInitSubsystems() { globals->add_subsystem("input", new FGInput); + //////////////////////////////////////////////////////////////////// + // Initialize the replay subsystem + //////////////////////////////////////////////////////////////////// + globals->add_subsystem("replay", new FGReplay); + //////////////////////////////////////////////////////////////////// // Bind and initialize subsystems. //////////////////////////////////////////////////////////////////// diff --git a/src/Main/main.cxx b/src/Main/main.cxx index 7299f0eed..3b718ab97 100644 --- a/src/Main/main.cxx +++ b/src/Main/main.cxx @@ -120,6 +120,7 @@ SG_USING_STD(endl); #include #endif +#include #include #include #ifdef ENABLE_AUDIO_SUPPORT @@ -165,7 +166,8 @@ sgVec3 rway_ols; float scene_nearplane = 0.5f; float scene_farplane = 120000.0f; -static double delta_time_sec = 0; +static double delta_time_sec = 0.0; +static double replay_dt_sec = 0.0; #ifdef FG_WEATHERCM @@ -932,6 +934,13 @@ void fgRenderFrame() { void fgUpdateTimeDepCalcs() { static bool inited = false; + static const SGPropertyNode *replay + = fgGetNode( "/sim/replay/master", true ); + static SGPropertyNode *replay_time + = fgGetNode( "/sim/replay/time", true ); + static const SGPropertyNode *replay_end_time + = fgGetNode( "/sim/replay/end-time", true ); + //SG_LOG(SG_FLIGHT,SG_INFO, "Updating time dep calcs()"); fgLIGHT *l = &cur_light_params; @@ -964,8 +973,15 @@ void fgUpdateTimeDepCalcs() { inited = true; } - globals->get_autopilot()->update(delta_time_sec); - cur_fdm_state->update(delta_time_sec); + if ( ! replay->getBoolValue() ) { + globals->get_autopilot()->update(delta_time_sec); + cur_fdm_state->update(delta_time_sec); + } else { + FGReplay *r = (FGReplay *)(globals->get_subsystem( "replay" )); + r->replay( replay_time->getDoubleValue() ); + replay_time->setDoubleValue( replay_time->getDoubleValue() + + replay_dt_sec ); + } } globals->get_model_mgr()->update(delta_time_sec); @@ -1010,6 +1026,8 @@ static void fgMainLoop( void ) { = fgGetNode("/sim/freeze/clock", true); static const SGPropertyNode *cur_time_override = fgGetNode("/sim/time/cur-time-override", true); + static const SGPropertyNode *replay + = fgGetNode("/sim/replay/master", true); // Update the elapsed time. static bool first_time = true; @@ -1032,9 +1050,12 @@ static void fgMainLoop( void ) { } delta_time_sec = double(current_time_stamp - last_time_stamp) / 1000000.0; + if ( replay->getBoolValue() ) { + replay_dt_sec = delta_time_sec; + } if ( clock_freeze->getBoolValue() ) { delta_time_sec = 0; - } + } last_time_stamp = current_time_stamp; globals->inc_sim_time_sec( delta_time_sec ); SGAnimation::set_sim_time_sec( globals->get_sim_time_sec() ); diff --git a/src/Makefile.am b/src/Makefile.am index ce31e6ebb..d88af9347 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -40,6 +40,7 @@ SUBDIRS = \ $(MPLAYER_DIRS) \ $(NETWORK_DIRS) \ Objects \ + Replay \ Scenery \ $(SCRIPTING_DIRS) \ Sound \ diff --git a/src/Network/native_ctrls.cxx b/src/Network/native_ctrls.cxx index 8049297a5..f03bc4446 100644 --- a/src/Network/native_ctrls.cxx +++ b/src/Network/native_ctrls.cxx @@ -98,7 +98,9 @@ static void htond (double &x) // Populate the FGNetCtrls structure from the property tree. -void FGProps2NetCtrls( FGNetCtrls *net, bool net_byte_order ) { +void FGProps2NetCtrls( FGNetCtrls *net, bool honor_freezes, + bool net_byte_order ) +{ int i; SGPropertyNode * node; SGPropertyNode * tempnode; @@ -192,14 +194,16 @@ void FGProps2NetCtrls( FGNetCtrls *net, bool net_byte_order ) { net->magvar = fgGetDouble("/environment/magnetic-variation-deg"); net->speedup = fgGetInt("/sim/speed-up"); net->freeze = 0; - if ( fgGetBool("/sim/freeze/master") ) { - net->freeze |= 0x01; - } - if ( fgGetBool("/sim/freeze/position") ) { - net->freeze |= 0x02; - } - if ( fgGetBool("/sim/freeze/fuel") ) { - net->freeze |= 0x04; + if ( honor_freezes ) { + if ( fgGetBool("/sim/freeze/master") ) { + net->freeze |= 0x01; + } + if ( fgGetBool("/sim/freeze/position") ) { + net->freeze |= 0x02; + } + if ( fgGetBool("/sim/freeze/fuel") ) { + net->freeze |= 0x04; + } } if ( net_byte_order ) { @@ -244,7 +248,9 @@ void FGProps2NetCtrls( FGNetCtrls *net, bool net_byte_order ) { // Update the property tree from the FGNetCtrls structure. -void FGNetCtrls2Props( FGNetCtrls *net, bool net_byte_order ) { +void FGNetCtrls2Props( FGNetCtrls *net, bool honor_freezes, + bool net_byte_order ) +{ int i; SGPropertyNode * node; @@ -346,10 +352,12 @@ void FGNetCtrls2Props( FGNetCtrls *net, bool net_byte_order ) { fgSetInt( "/sim/speed-up", net->speedup ); - node = fgGetNode( "/sim/freeze", true ); - node->setBoolValue( "master", net->freeze & 0x01 ); - node->setBoolValue( "position", net->freeze & 0x02 ); - node->setBoolValue( "fuel", net->freeze & 0x04 ); + if ( honor_freezes ) { + node = fgGetNode( "/sim/freeze", true ); + node->setBoolValue( "master", net->freeze & 0x01 ); + node->setBoolValue( "position", net->freeze & 0x02 ); + node->setBoolValue( "fuel", net->freeze & 0x04 ); + } } @@ -361,7 +369,7 @@ bool FGNativeCtrls::process() { if ( get_direction() == SG_IO_OUT ) { // cout << "size of cur_fdm_state = " << length << endl; - FGProps2NetCtrls( &net_ctrls ); + FGProps2NetCtrls( &net_ctrls, true, true ); if ( ! io->write( (char *)(& net_ctrls), length ) ) { SG_LOG( SG_IO, SG_ALERT, "Error writing data." ); @@ -371,12 +379,12 @@ bool FGNativeCtrls::process() { if ( io->get_type() == sgFileType ) { if ( io->read( (char *)(& net_ctrls), length ) == length ) { SG_LOG( SG_IO, SG_DEBUG, "Success reading data." ); - FGNetCtrls2Props( &net_ctrls ); + FGNetCtrls2Props( &net_ctrls, true, true ); } } else { while ( io->read( (char *)(& net_ctrls), length ) == length ) { SG_LOG( SG_IO, SG_DEBUG, "Success reading data." ); - FGNetCtrls2Props( &net_ctrls ); + FGNetCtrls2Props( &net_ctrls, true, true ); } } } diff --git a/src/Network/native_ctrls.hxx b/src/Network/native_ctrls.hxx index d64d07200..82a995aa4 100644 --- a/src/Network/native_ctrls.hxx +++ b/src/Network/native_ctrls.hxx @@ -63,10 +63,12 @@ public: // Helper functions which may be useful outside this class // Populate the FGNetCtrls structure from the property tree. -void FGProps2NetCtrls( FGNetCtrls *net, bool net_byte_order = true ); +void FGProps2NetCtrls( FGNetCtrls *net, bool honor_freezes, + bool net_byte_order ); // Update the property tree from the FGNetCtrls structure. -void FGNetCtrls2Props( FGNetCtrls *net, bool net_byte_order = true ); +void FGNetCtrls2Props( FGNetCtrls *net, bool honor_freezes, + bool net_byte_order ); #endif // _FG_NATIVE_CTRLS_HXX diff --git a/src/Replay/Makefile.am b/src/Replay/Makefile.am new file mode 100644 index 000000000..ed79d1eb8 --- /dev/null +++ b/src/Replay/Makefile.am @@ -0,0 +1,5 @@ +noinst_LIBRARIES = libReplay.a + +libReplay_a_SOURCES = replay.hxx replay.cxx + +INCLUDES = -I$(top_srcdir) -I$(top_srcdir)/src diff --git a/src/Replay/replay.cxx b/src/Replay/replay.cxx new file mode 100644 index 000000000..8bc2a0858 --- /dev/null +++ b/src/Replay/replay.cxx @@ -0,0 +1,339 @@ +// replay.cxx - a system to record and replay FlightGear flights +// +// Written by Curtis Olson, started Juley 2003. +// +// Copyright (C) 2003 Curtis L. Olson - curt@flightgear.org +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id$ + + +#include +#include +#include +#include + +#include "replay.hxx" + + +/** + * Constructor + */ + +FGReplay::FGReplay() { +} + + +/** + * Destructor + */ + +FGReplay::~FGReplay() { + // no dynamically allocated memory to free +} + + +/** + * Initialize the data structures + */ + +void FGReplay::init() { + sim_time = 0.0; + last_mt_time = 0.0; + last_lt_time = 0.0; + + // Make sure all queues are flushed + while ( !short_term.empty() ) { + short_term.pop_front(); + } + while ( !medium_term.empty() ) { + medium_term.pop_front(); + } + while ( !medium_term.empty() ) { + medium_term.pop_front(); + } +} + + +/** + * Bind to the property tree + */ + +void FGReplay::bind() { + // nothing to bind +} + + +/** + * Unbind from the property tree + */ + +void FGReplay::unbind() { + // nothing to unbind +} + + +/** + * Update the saved data + */ + +void FGReplay::update( double dt ) { + + if ( dt <= 0 ) { + // don't save data if nothing is going on ... + + return; + } + + sim_time += dt; + + // build the replay record + FGNetFDM f; + FGProps2NetFDM( &f, false ); + + FGNetCtrls c; + FGProps2NetCtrls( &c, false, false ); + + FGReplayData r; + r.sim_time = sim_time; + r.ctrls = c; + r.fdm = f; + + // update the short term list + short_term.push_back( r ); + + FGReplayData st_front = short_term.front(); + if ( sim_time - st_front.sim_time > st_list_time ) { + while ( sim_time - st_front.sim_time > st_list_time ) { + st_front = short_term.front(); + short_term.pop_front(); + } + + // update the medium term list + if ( sim_time - last_mt_time > 1.0 ) { + last_mt_time = sim_time; + medium_term.push_back( st_front ); + + FGReplayData mt_front = medium_term.front(); + if ( sim_time - mt_front.sim_time > mt_list_time ) { + while ( sim_time - mt_front.sim_time > mt_list_time ) { + mt_front = medium_term.front(); + medium_term.pop_front(); + } + + // update the long term list + if ( sim_time - last_lt_time > 10.0 ) { + last_lt_time = sim_time; + long_term.push_back( mt_front ); + + FGReplayData lt_front = long_term.front(); + if ( sim_time - lt_front.sim_time > lt_list_time ) { + while ( sim_time - lt_front.sim_time > lt_list_time ) { + lt_front = long_term.front(); + long_term.pop_front(); + } + } + } + } + } + } + +#if 0 + cout << "short term size = " << short_term.size() + << " time = " << sim_time - short_term.front().sim_time + << endl; + cout << "medium term size = " << medium_term.size() + << " time = " << sim_time - medium_term.front().sim_time + << endl; + cout << "long term size = " << long_term.size() + << " time = " << sim_time - long_term.front().sim_time + << endl; +#endif +} + + +static double weight( double data1, double data2, double ratio ) { + return data1 + ( data2 - data1 ) * ratio; +} + +/** + * given two FGReplayData elements and a time, interpolate between them + */ +static void update_fdm( FGReplayData frame ) { + FGNetFDM2Props( &frame.fdm, false ); + FGNetCtrls2Props( &frame.ctrls, false, false ); +} + +/** + * given two FGReplayData elements and a time, interpolate between them + */ +static FGReplayData interpolate( double time, FGReplayData f1, FGReplayData f2 ) +{ + FGReplayData result = f1; + + FGNetFDM fdm1 = f1.fdm; + FGNetFDM fdm2 = f2.fdm; + + // do some work + double ratio = (time - f1.sim_time) / (f2.sim_time - f1.sim_time); + + cout << fdm1.longitude << " " << fdm2.longitude << endl; + result.fdm.longitude = weight( fdm1.longitude, fdm2.longitude, ratio ); + result.fdm.latitude = weight( fdm1.latitude, fdm2.latitude, ratio ); + result.fdm.altitude = weight( fdm1.altitude, fdm2.altitude, ratio ); + result.fdm.agl = weight( fdm1.agl, fdm2.agl, ratio ); + result.fdm.phi = weight( fdm1.phi, fdm2.phi, ratio ); + result.fdm.theta = weight( fdm1.theta, fdm2.theta, ratio ); + result.fdm.psi = weight( fdm1.psi, fdm2.psi, ratio ); + + return result; +} + +/** + * interpolate a specific time from a specific list + */ +static void interpolate( double time, replay_list_type list ) { + // sanity checking + if ( list.size() == 0 ) { + // handle empty list + return; + } else if ( list.size() == 1 ) { + // handle list size == 1 + update_fdm( list[0] ); + return; + } + + unsigned int last = list.size() - 1; + unsigned int first = 0; + unsigned int mid = ( last + first ) / 2; + + + bool done = false; + while ( !done ) { + // cout << " " << first << " <=> " << last << endl; + if ( last == first ) { + done = true; + } else if ( list[mid].sim_time < time && list[mid+1].sim_time < time ) { + // too low + first = mid; + mid = ( last + first ) / 2; + } else if ( list[mid].sim_time > time && list[mid+1].sim_time > time ) { + // too high + last = mid; + mid = ( last + first ) / 2; + } else { + done = true; + } + } + + FGReplayData result = interpolate( time, list[mid], list[mid+1] ); + + update_fdm( result ); +} + + +/** + * Replay a saved frame based on time, interpolate from the two + * nearest saved frames. + */ + +void FGReplay::replay( double time ) { + cout << "replay: " << time << " "; + // find the two frames to interpolate between + double t1, t2; + + if ( short_term.size() > 0 ) { + t1 = short_term.back().sim_time; + t2 = short_term.front().sim_time; + if ( time > t1 ) { + // replay the most recent frame + update_fdm( short_term.back() ); + cout << "first frame" << endl; + } else if ( time <= t1 && time >= t2 ) { + interpolate( time, short_term ); + cout << "from short term" << endl; + } else if ( medium_term.size() > 0 ) { + t1 = short_term.front().sim_time; + t2 = medium_term.back().sim_time; + if ( time <= t1 && time >= t2 ) { + FGReplayData result = interpolate( time, + medium_term.back(), + short_term.front() ); + update_fdm( result ); + cout << "from short/medium term" << endl; + } else { + t1 = medium_term.back().sim_time; + t2 = medium_term.front().sim_time; + if ( time <= t1 && time >= t2 ) { + interpolate( time, medium_term ); + cout << "from medium term" << endl; + } else if ( long_term.size() > 0 ) { + t1 = medium_term.front().sim_time; + t2 = long_term.back().sim_time; + if ( time <= t1 && time >= t2 ) { + FGReplayData result = interpolate( time, + long_term.back(), + medium_term.front()); + update_fdm( result ); + cout << "from medium/long term" << endl; + } else { + t1 = long_term.back().sim_time; + t2 = long_term.front().sim_time; + if ( time <= t1 && time >= t2 ) { + interpolate( time, long_term ); + cout << "from long term" << endl; + } else { + // replay the oldest long term frame + update_fdm( long_term.front() ); + cout << "oldest long term frame" << endl; + } + } + } else { + // replay the oldest medium term frame + update_fdm( medium_term.front() ); + cout << "oldest medium term frame" << endl; + } + } + } else { + // replay the oldest short term frame + update_fdm( short_term.front() ); + cout << "oldest short term frame" << endl; + } + } else { + // nothing to replay + } +} + + +double FGReplay::get_start_time() { + if ( long_term.size() > 0 ) { + return long_term.front().sim_time; + } else if ( medium_term.size() > 0 ) { + return medium_term.front().sim_time; + } else if ( short_term.size() ) { + return short_term.front().sim_time; + } else { + return 0.0; + } +} + +double FGReplay::get_end_time() { + if ( short_term.size() ) { + return short_term.back().sim_time; + } else { + return 0.0; + } +} diff --git a/src/Replay/replay.hxx b/src/Replay/replay.hxx new file mode 100644 index 000000000..17d68fb99 --- /dev/null +++ b/src/Replay/replay.hxx @@ -0,0 +1,96 @@ +// replay.hxx - a system to record and replay FlightGear flights +// +// Written by Curtis Olson, started Juley 2003. +// +// Copyright (C) 2003 Curtis L. Olson - curt@flightgear.org +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id$ + + +#ifndef _FG_REPLAY_HXX +#define _FG_REPLAY_HXX 1 + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +#include + +#include +#include + +#include +#include +#include
+ +SG_USING_STD(deque); + + +class FGReplayData { + +public: + + double sim_time; + FGNetFDM fdm; + FGNetCtrls ctrls; +}; + +typedef deque < FGReplayData > replay_list_type; + + + +/** + * A recording/replay module for FlightGear flights + * + */ + +class FGReplay : public FGSubsystem +{ + +public: + + FGReplay (); + virtual ~FGReplay(); + + virtual void init(); + virtual void bind(); + virtual void unbind(); + virtual void update( double dt ); + + void replay( double time ); + double get_start_time(); + double get_end_time(); + +private: + + static const double st_list_time = 10.0; // 60 secs of high res data + static const double mt_list_time = 30.0; // 10 mins of 1 fps data + static const double lt_list_time = 60.0; // 1 hr of 10 spf data + + double sim_time; + double last_mt_time; + double last_lt_time; + + replay_list_type short_term; + replay_list_type medium_term; + replay_list_type long_term; +}; + + +#endif // _FG_REPLAY_HXX -- 2.39.5