11#include <simgear/compiler.h>
18#include <simgear/debug/ErrorReportingCallback.hxx>
19#include <simgear/debug/logstream.hxx>
20#include <simgear/io/iostreams/sgstream.hxx>
21#include <simgear/math/sg_random.hxx>
22#include <simgear/props/props.hxx>
23#include <simgear/props/props_io.hxx>
24#include <simgear/sg_inlines.h>
25#include <simgear/structure/commands.hxx>
26#include <simgear/structure/event_mgr.hxx>
27#include <simgear/structure/exception.hxx>
28#include <simgear/timing/sg_time.hxx>
56# include <google/profiler.h>
69static inline SGPropertyNode *
70get_prop (
const SGPropertyNode * arg, SGPropertyNode * root)
74 SGPropertyNode *rv =
nullptr;
75 rv = root->getNode(arg->getStringValue(
"property[0]",
"/null"),
true);
78 rv = root->getNode(arg->getStringValue(
"property[0]",
"/null"),
true);
79 return fgGetNode(arg->getStringValue(
"property[0]",
"/null"),
true);
83 return fgGetNode(arg->getStringValue(
"property[0]",
"/null"),
true);
86static inline SGPropertyNode *
87get_prop2 (
const SGPropertyNode * arg, SGPropertyNode * root)
91 SGPropertyNode *rv =
nullptr;
92 rv = root->getNode(arg->getStringValue(
"property[1]",
"/null"),
true);
95 rv = root->getNode(arg->getStringValue(
"property[1]",
"/null"),
true);
96 return fgGetNode(arg->getStringValue(
"property[1]",
"/null"),
true);
100 return fgGetNode(arg->getStringValue(
"property[1]",
"/null"),
true);
109 double * unmodifiable,
double * modifiable)
111 if (!strcmp(
"integer", mask)) {
112 *modifiable = (full_value < 0 ? ceil(full_value) : floor (full_value));
113 *unmodifiable = full_value - *modifiable;
114 }
else if (!strcmp(
"decimal", mask)) {
115 *unmodifiable = (full_value < 0 ? ceil(full_value) : floor(full_value));
116 *modifiable = full_value - *unmodifiable;
118 if (strcmp(
"all", mask))
119 SG_LOG(SG_GENERAL, SG_ALERT,
"Bad value " << mask <<
" for mask;"
120 <<
" assuming 'all'");
122 *modifiable = full_value;
136static std::optional<T>
139 auto indirectNode = node->getChild(indirectName.empty() ? (
name +
"-prop") : indirectName);
141 const auto resolvedNode =
fgGetNode(indirectNode->getStringValue());
143 return resolvedNode->getValue<T>();
147 SG_LOG(SG_GENERAL, SG_DEV_WARN,
"getValueIndirect: property:" << indirectNode->getNameString() <<
" has value '" << indirectNode->getStringValue() <<
"' which was not found in the global property tree. Falling back to "
148 "value defined by argument '"
154 auto directNode = node->getChild(
name);
156 return directNode->getValue<T>();
171 const bool canWrap = (minv && maxv);
172 const bool wantsWrap = arg->getBoolValue(
"wrap");
173 const bool wrap = canWrap && wantsWrap;
174 if (wantsWrap && !canWrap) {
175 SG_LOG(SG_GENERAL, SG_DEV_WARN,
"limit_value: wrap requested, but no min/max values defined");
179 const double resolution = arg->getDoubleValue(
"resolution");
180 if (resolution > 0.0) {
182 int n = (int)floor((value - minv.value()) / resolution + 0.5);
183 int steps = (int)floor((maxv.value() - minv.value()) / resolution + 0.5);
184 SG_NORMALIZE_RANGE(n, 0, steps);
185 return minv.value() + resolution * n;
188 return SGMiscd::normalizePeriodic(minv.value(), maxv.value(), value);
191 if (minv && (value < minv.value())) {
195 if (maxv && (value > maxv.value())) {
206 switch (value1->getType()) {
207 case simgear::props::BOOL:
208 return (value1->getBoolValue() == value2->getBoolValue());
209 case simgear::props::INT:
210 return (value1->getIntValue() == value2->getIntValue());
211 case simgear::props::LONG:
212 return (value1->getLongValue() == value2->getLongValue());
213 case simgear::props::FLOAT:
214 return (value1->getFloatValue() == value2->getFloatValue());
215 case simgear::props::DOUBLE:
216 return (value1->getDoubleValue() == value2->getDoubleValue());
218 return value1->getStringValue() == value2->getStringValue();
233do_null (
const SGPropertyNode * arg, SGPropertyNode * root)
242do_nasal (
const SGPropertyNode * arg, SGPropertyNode * root)
246 SG_LOG(SG_GUI, SG_ALERT,
"do_nasal command: Nasal subsystem not found");
250 return nasalSys->handleCommand(arg, root);
257do_replay (
const SGPropertyNode * arg, SGPropertyNode * root)
267do_pause (
const SGPropertyNode * arg, SGPropertyNode * root)
269 bool forcePause = arg->getBoolValue(
"force-pause",
false );
270 bool forcePlay = arg->getBoolValue(
"force-play",
false );
272 bool paused =
fgGetBool(
"/sim/freeze/master",
true) ||
fgGetBool(
"/sim/freeze/clock",
true);
274 if(forcePause) paused =
false;
275 if(forcePlay) paused =
true;
277 if (paused && (
fgGetInt(
"/sim/freeze/replay-state",0)>0))
298do_load (
const SGPropertyNode * arg, SGPropertyNode * root)
300 SGPath file(arg->getStringValue(
"file",
"fgfs.sav").c_str());
302 if (file.extension() !=
"sav")
305 const SGPath validated_path = SGPath(file).validate(
false);
306 if (validated_path.isNull()) {
307 SG_LOG(SG_IO, SG_ALERT,
"load: reading '" << file <<
"' denied "
308 "(unauthorized access)");
312 sg_ifstream input(validated_path);
315 SG_LOG(SG_INPUT, SG_INFO,
"Restored flight from " << file);
318 SG_LOG(SG_INPUT, SG_WARN,
"Cannot load flight from " << file);
331do_save (
const SGPropertyNode * arg, SGPropertyNode * root)
333 SGPath file(arg->getStringValue(
"file",
"fgfs.sav").c_str());
335 if (file.extension() !=
"sav")
338 const SGPath validated_path = SGPath(file).validate(
true);
339 if (validated_path.isNull()) {
340 SG_LOG(SG_IO, SG_ALERT,
"save: writing '" << file <<
"' denied "
341 "(unauthorized access)");
345 bool write_all = arg->getBoolValue(
"write-all",
false);
346 SG_LOG(SG_INPUT, SG_INFO,
"Saving flight");
347 sg_ofstream output(validated_path);
350 SG_LOG(SG_INPUT, SG_INFO,
"Saved flight to " << file);
353 SG_LOG(SG_INPUT, SG_ALERT,
"Cannot save flight to " << file);
390 globals->get_current_view()->setHeadingOffset_deg(0.0);
391 globals->get_viewmgr()->next_view();
400 globals->get_current_view()->setHeadingOffset_deg(0.0);
401 globals->get_viewmgr()->prev_view();
411 globals->get_current_view()->setHeadingOffset_deg(0.0);
412 globals->get_viewmgr()->next_view();
423 SG_LOG(SG_GENERAL, SG_DEBUG,
"do_view_push() called");
424 globals->get_viewmgr()->view_push();
435 SG_LOG(SG_GENERAL, SG_DEBUG,
"do_view_clone() called");
436 globals->get_viewmgr()->clone_current_view(arg);
447 SG_LOG(SG_GENERAL, SG_DEBUG,
"do_view_last_pair() called");
448 globals->get_viewmgr()->clone_last_pair(arg);
459 SG_LOG(SG_GENERAL, SG_DEBUG,
"do_view_last_pair_double() called");
460 globals->get_viewmgr()->clone_last_pair_double(arg);
471 SG_LOG(SG_GENERAL, SG_ALERT,
"do_view_new() called");
472 globals->get_viewmgr()->view_new(arg);
495 if (!view_mgr)
return false;
497 arg->getStringValue(
"name"),
498 arg->getStringValue(
"codec"),
499 arg->getDoubleValue(
"quality", -1),
500 arg->getDoubleValue(
"speed", -1),
501 arg->getIntValue(
"bitrate", 0)
513 if (!view_mgr)
return false;
526 SGPropertyNode * prop =
get_prop(arg, root);
527 return prop->setBoolValue(!prop->getBoolValue());
541 SGPropertyNode * prop =
get_prop(arg,root);
542 const SGPropertyNode * value = arg->getNode(
"value");
545 return prop->setUnspecifiedValue(value->getStringValue());
548 const SGPropertyNode * prop2 =
get_prop2(arg,root);
550 return prop->setUnspecifiedValue(prop2->getStringValue());
580 SGPropertyNode * prop =
get_prop(arg,root);
583 if (arg->hasValue(
"step"))
584 amount = arg->getDoubleValue(
"step");
586 amount = (arg->getDoubleValue(
"factor", 1.0)
587 * arg->getDoubleValue(
"offset"));
589 double unmodifiable, modifiable;
590 split_value(prop->getDoubleValue(), arg->getStringValue(
"mask",
"all").c_str(),
591 &unmodifiable, &modifiable);
592 modifiable += amount;
595 prop->setDoubleValue(unmodifiable + modifiable);
618 SGPropertyNode * prop =
get_prop(arg,root);
621 SG_LOG(SG_GENERAL, SG_DEV_WARN,
"property-multiply: missing factor/factor-prop argument");
625 double unmodifiable, modifiable;
626 split_value(prop->getDoubleValue(), arg->getStringValue(
"mask",
"all").c_str(),
627 &unmodifiable, &modifiable);
628 modifiable *= factorValue.value();
631 prop->setDoubleValue(unmodifiable + modifiable);
646 SGPropertyNode * prop1 =
get_prop(arg,root);
647 SGPropertyNode * prop2 =
get_prop2(arg,root);
650 const string & tmp = prop1->getStringValue();
651 return (prop1->setUnspecifiedValue(prop2->getStringValue()) &&
652 prop2->setUnspecifiedValue(tmp.c_str()));
667 SGPropertyNode * prop =
get_prop(arg,root);
668 double setting = arg->getDoubleValue(
"setting");
669 double offset = arg->getDoubleValue(
"offset", 0.0);
670 double factor = arg->getDoubleValue(
"factor", 1.0);
671 bool squared = arg->getBoolValue(
"squared",
false);
672 int power = arg->getIntValue(
"power", (squared ? 2 : 1));
674 int sign = (setting < 0 ? -1 : 1);
680 setting = setting * setting * sign;
683 setting = setting * setting * setting;
686 setting = setting * setting * setting * setting * sign;
689 setting = pow(setting, power);
690 if ((power % 2) == 0)
695 return prop->setDoubleValue((setting + offset) * factor);
711 SGPropertyNode * prop =
get_prop(arg,root);
712 std::vector<SGPropertyNode_ptr> values = arg->getChildren(
"value");
714 bool wrap = arg->getBoolValue(
"wrap",
true);
716 int offset = arg->getIntValue(
"offset", 1);
719 int nSelections = values.size();
721 if (nSelections < 1) {
722 SG_LOG(SG_GENERAL, SG_ALERT,
"No values for property-cycle");
727 for (
int i = 0;
i < nSelections;
i++) {
739 selection = (selection + nSelections) % nSelections;
741 SG_CLAMP_RANGE(selection, 0, nSelections - 1);
745 prop->setUnspecifiedValue(values[selection]->getStringValue());
760 SGPropertyNode * prop =
get_prop(arg,root);
761 double min = arg->getDoubleValue(
"min", DBL_MIN);
762 double max = arg->getDoubleValue(
"max", DBL_MAX);
763 prop->setDoubleValue(sg_random() * (max -
min) +
min);
788 SGPropertyNode * prop =
get_prop(arg,root);
792 simgear::PropertyList time_nodes = arg->getChildren(
"time");
793 simgear::PropertyList rate_nodes = arg->getChildren(
"rate");
795 if( !time_nodes.empty() && !rate_nodes.empty() )
799 simgear::PropertyList::size_type num_times = time_nodes.empty()
803 simgear::PropertyList value_nodes = arg->getChildren(
"value");
804 if( value_nodes.empty() )
806 simgear::PropertyList prop_nodes = arg->getChildren(
"property");
809 if( prop_nodes.size() != num_times + 1 )
812 value_nodes.reserve(num_times);
813 for(
size_t i = 1;
i < prop_nodes.size(); ++
i )
814 value_nodes.push_back(
fgGetNode(prop_nodes[
i]->getStringValue()) );
818 if( value_nodes.size() != num_times )
821 std::vector<double> deltas;
822 deltas.reserve(num_times);
824 if( !time_nodes.empty() )
826 for(
size_t i = 0;
i < num_times; ++
i )
827 deltas.push_back( time_nodes[
i]->getDoubleValue() );
831 for(
size_t i = 0;
i < num_times; ++
i )
834 double delta = value_nodes[
i]->getDoubleValue()
836 ? value_nodes[
i - 1]->getDoubleValue()
837 : prop->getDoubleValue()
839 deltas.push_back( fabs(delta / rate_nodes[
i]->getDoubleValue()) );
843 return prop->interpolate
845 arg->getStringValue(
"type",
"numeric"),
848 arg->getStringValue(
"easing",
"linear")
870 sglog().setLogLevels( SG_ALL, (sgDebugPriority)arg->getIntValue() );
890 SGPath file(arg->getStringValue(
"filename"));
894 if (file.extension() !=
"xml")
899 const bool quiet = arg->getBoolValue(
"quiet",
false);
901 std::string icao = arg->getStringValue(
"icao");
903 if (file.isRelative()) {
904 SGPath absPath =
globals->resolve_maybe_aircraft_path(file.utf8Str());
905 if (!absPath.isNull())
910 SG_LOG(SG_IO, SG_ALERT,
"loadxml: Cannot find XML property file '" << file <<
"'.");
911 simgear::reportFailure(simgear::LoadFailure::NotFound, simgear::ErrorCode::XMLLoadCommand,
912 "loadxml: no such file:" + file.utf8Str(), file);
920 SG_LOG(SG_IO, SG_INFO,
"loadxml: failed to find airport data for " << file <<
" at ICAO:" << icao);
921 simgear::reportFailure(simgear::LoadFailure::NotFound, simgear::ErrorCode::XMLLoadCommand,
922 "loadxml: no airport data file for:" + icao, file);
928 if (!file.exists()) {
930 SG_LOG(SG_IO, SG_WARN,
"loadxml: no such file:" << file);
935 const SGPath validated_path = SGPath(file).validate(
false);
936 if (validated_path.isNull()) {
937 SG_LOG(SG_IO, quiet ? SG_DEV_WARN : SG_ALERT,
"loadxml: reading '" << file <<
"' denied "
938 "(unauthorized directory - authorization no longer follows symlinks; to authorize reading additional directories, pass them to --allow-nasal-read)");
942 SGPropertyNode *targetnode;
943 if (arg->hasValue(
"targetnode"))
944 targetnode =
fgGetNode(arg->getStringValue(
"targetnode"),
true);
946 targetnode =
const_cast<SGPropertyNode *
>(arg)->getNode(
"data",
true);
949 readProperties(validated_path, targetnode,
true);
950 }
catch (
const sg_exception &e) {
952 simgear::reportFailure(simgear::LoadFailure::BadData, simgear::ErrorCode::XMLLoadCommand,
953 "loadxml exception:" + e.getFormattedMessage(), e.getLocation());
955 SG_LOG(SG_IO, quiet ? SG_DEV_WARN : SG_WARN,
"loadxml exception: " << e.getFormattedMessage());
967 SG_LOG(SG_IO, SG_ALERT,
"xmlhttprequest: HTTP client not running");
971 std::string url(arg->getStringValue(
"url"));
975 SGPropertyNode *targetnode;
976 if (arg->hasValue(
"targetnode"))
977 targetnode =
fgGetNode(arg->getStringValue(
"targetnode"),
true);
979 targetnode =
const_cast<SGPropertyNode *
>(arg)->getNode(
"data",
true);
983 if (arg->hasChild(
"body"))
984 req->setBodyData(arg->getChild(
"body"));
987 if (arg->hasValue(
"complete"))
989 if (arg->hasValue(
"failure"))
991 if (arg->hasValue(
"status"))
994 http->makeRequest(req);
1015 SGPath file(arg->getStringValue(
"filename"));
1019 if (file.extension() !=
"xml")
1020 file.concat(
".xml");
1022 const SGPath validated_path = SGPath(file).validate(
true);
1023 if (validated_path.isNull()) {
1024 SG_LOG(SG_IO, SG_ALERT,
"savexml: writing to '" << file <<
"' denied "
1025 "(unauthorized directory - authorization no longer follows symlinks)");
1029 SGPropertyNode *sourcenode;
1030 if (arg->hasValue(
"sourcenode"))
1031 sourcenode =
fgGetNode(arg->getStringValue(
"sourcenode"),
true);
1032 else if (arg->getNode(
"data",
false))
1033 sourcenode =
const_cast<SGPropertyNode *
>(arg)->getNode(
"data");
1038 writeProperties (validated_path, sourcenode,
true);
1039 }
catch (
const sg_exception &e) {
1040 SG_LOG(SG_IO, SG_WARN,
"savexml: " << e.getFormattedMessage());
1050#if !FG_HAVE_GPERFTOOLS
1058 "No profiling support! Install gperftools and reconfigure/rebuild fgfs."
1066#if FG_HAVE_GPERFTOOLS
1067 const char *filename = arg->getStringValue(
"filename",
"fgfs.profile");
1068 ProfilerStart(filename);
1079#if FG_HAVE_GPERFTOOLS
1092 SG_LOG(SG_GUI, SG_ALERT,
"reloadModuleFromFile command: Nasal subsystem not found");
1096 return nasalSys->reloadModuleFromFile(arg->getStringValue(
"module"));
1106 SG_LOG(SG_GENERAL, SG_ALERT,
1107 "No VR support! Rebuild fgfs with VR enabled.");
1111#define do_vr_recenter no_vr_support
1118 return flightgear::VRManager::instance()->recenter();
1201 SGCommandMgr::instance()->setImplicitRoot(
globals->get_props());
1203 SG_LOG(SG_GENERAL, SG_BULK,
"Initializing basic built-in commands:");
1210 typedef bool (*dummy)();
1214 globals->get_props()->setValueReadOnly(
"/sim/debug/profiler-available",
1215 bool(FG_HAVE_GPERFTOOLS) );
static FGNasalSys * nasalSys
Log any property values to any number of CSV files.
bool video_start(const std::string &name="", const std::string &codec="", double quality=-1, double speed=-1, int bitrate=0)
void setStatusProp(SGPropertyNode_ptr p)
void setCompletionProp(SGPropertyNode_ptr p)
void setFailedProp(SGPropertyNode_ptr p)
static bool findAirportData(const std::string &aICAO, const std::string &aFileName, SGPath &aPath)
Search the scenery for a file name of the form: I/C/A/ICAO.filename.xml and return the corresponding ...
static bool do_property_swap(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: swap two property values.
static struct @067026177365276227015073311113211264353134373250 built_ins[]
Table of built-in commands.
static bool do_load_xml_from_url(const SGPropertyNode *arg, SGPropertyNode *root)
static void split_value(double full_value, const char *mask, double *unmodifiable, double *modifiable)
Get a double value and split it as required.
static bool do_property_randomize(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: randomize a numeric property value.
static bool do_view_clone(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: clone view.
static bool do_nasal(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: run a Nasal script.
static bool do_save(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: save flight.
static bool do_load_tape(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: load flight recorder tape.
static bool do_view_new(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: double view last pair.
static bool do_property_toggle(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: toggle a bool property value.
static double limit_value(double value, const SGPropertyNode *arg)
Clamp or wrap a value as specified.
static bool do_replay(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: replay the FDR buffer.
static bool do_view_push(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: view-push.
static bool do_video_start(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: video-start.
SGCommandMgr::command_t command
static bool do_save_xml_from_proptree(const SGPropertyNode *arg, SGPropertyNode *root)
An fgcommand to allow saving of xml files via nasal, the file's structure will be determined based on...
static bool do_view_last_pair_double(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: double view last pair.
static bool do_save_tape(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: save flight recorder tape.
static SGPropertyNode * get_prop2(const SGPropertyNode *arg, SGPropertyNode *root)
static bool do_reload_nasal_module(const SGPropertyNode *arg, SGPropertyNode *)
static void do_view_next(bool do_it)
static bool do_load_xml_to_proptree(const SGPropertyNode *arg, SGPropertyNode *root)
An fgcommand to allow loading of xml files via nasal, the xml file's structure will be made available...
static bool do_property_cycle(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: cycle a property through a set of values.
static bool do_data_logging_commit(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: reinit the data logging system based on the current contents of the /logger tree.
static bool compare_values(SGPropertyNode *value1, SGPropertyNode *value2)
static bool do_property_adjust(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: increment or decrement a property value.
static bool do_null(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: do nothing.
static bool no_vr_support(const SGPropertyNode *arg, SGPropertyNode *root)
static void do_view_prev(bool do_it)
static bool do_pause(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: pause/unpause the sim.
static bool do_property_assign(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: assign a value to a property.
static bool do_profiler_stop(const SGPropertyNode *arg, SGPropertyNode *root)
static std::optional< T > getValueIndirect(const SGPropertyNode *node, const std::string &name, const std::string &indirectName={})
Retrive a typed value from a node, either from <foo> directly or indirectly.
static bool do_video_stop(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: video-stop.
static bool do_load(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: load flight.
static SGPropertyNode * get_prop(const SGPropertyNode *arg, SGPropertyNode *root)
static bool do_view_last_pair(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: view last pair.
static bool do_property_interpolate(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: interpolate a property value over time.
static bool do_property_scale(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: Set a property to an axis or other moving input.
void fgInitCommands()
Initialize the default built-in commands.
static bool do_profiler_start(const SGPropertyNode *arg, SGPropertyNode *root)
static bool do_property_multiply(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: multiply a property value.
static bool do_view_cycle(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: cycle view.
static bool do_log_level(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: set log level (0 ... 7)
static void no_profiling_support()
bool fgSaveFlight(std::ostream &output, bool write_all)
Save the current state of the simulator to a stream.
bool fgLoadFlight(std::istream &input)
Restore the current state of the simulator from a stream.
int fgGetInt(const char *name, int defaultValue)
Get an int value for a property.
void fgTie(const char *name, V(*getter)(), void(*setter)(V)=0, bool useDefault=true)
Tie a property to a pair of simple functions.
void syncPausePopupState()
synchronize /sim/freeze properties with visiblity of the popup-dialog which informs the user
bool fgGetBool(char const *name, bool def)
Get a bool value for a property.
bool fgSetBool(char const *name, bool val)
Set a bool value for a property.
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
bool replay(FGReplayInternal &self, double time)
Replay a saved frame based on time, interpolate from the two nearest saved frames.
bool start(bool new_tape=false)
Start replay session.