Extended replay and flight recording system
Save/restore replay sessions. Replay system message support, so recorded flights can be turned into flight tutorials.
This commit is contained in:
parent
08d82294bd
commit
fdb64a02a8
5 changed files with 703 additions and 68 deletions
|
@ -56,6 +56,21 @@ FGFlightRecorder::reinit(void)
|
||||||
{
|
{
|
||||||
m_ConfigNode = 0;
|
m_ConfigNode = 0;
|
||||||
|
|
||||||
|
SGPropertyNode_ptr ConfigNode;
|
||||||
|
int Selected = m_RecorderNode->getIntValue(m_ConfigName, 0);
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_INFO, "FlightRecorder: Recorder configuration #" << Selected);
|
||||||
|
if (Selected >= 0)
|
||||||
|
ConfigNode = m_RecorderNode->getChild("config", Selected);
|
||||||
|
|
||||||
|
if (!ConfigNode.valid())
|
||||||
|
ConfigNode = getDefault();
|
||||||
|
|
||||||
|
reinit(ConfigNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
FGFlightRecorder::reinit(SGPropertyNode_ptr ConfigNode)
|
||||||
|
{
|
||||||
m_TotalRecordSize = 0;
|
m_TotalRecordSize = 0;
|
||||||
|
|
||||||
m_CaptureDouble.clear();
|
m_CaptureDouble.clear();
|
||||||
|
@ -65,13 +80,7 @@ FGFlightRecorder::reinit(void)
|
||||||
m_CaptureInt8.clear();
|
m_CaptureInt8.clear();
|
||||||
m_CaptureBool.clear();
|
m_CaptureBool.clear();
|
||||||
|
|
||||||
int Selected = m_RecorderNode->getIntValue(m_ConfigName, 0);
|
m_ConfigNode = ConfigNode;
|
||||||
SG_LOG(SG_SYSTEMS, SG_INFO, "FlightRecorder: Recorder configuration #" << Selected);
|
|
||||||
if (Selected >= 0)
|
|
||||||
m_ConfigNode = m_RecorderNode->getChild("config", Selected);
|
|
||||||
|
|
||||||
if (!m_ConfigNode.valid())
|
|
||||||
initDefault();
|
|
||||||
|
|
||||||
if (!m_ConfigNode.valid())
|
if (!m_ConfigNode.valid())
|
||||||
{
|
{
|
||||||
|
@ -145,9 +154,11 @@ FGFlightRecorder::haveProperty(SGPropertyNode* pProperty)
|
||||||
|
|
||||||
/** Read default flight-recorder configuration.
|
/** Read default flight-recorder configuration.
|
||||||
* Default should match properties as hard coded for versions up to FG2.4.0. */
|
* Default should match properties as hard coded for versions up to FG2.4.0. */
|
||||||
void
|
SGPropertyNode_ptr
|
||||||
FGFlightRecorder::initDefault(void)
|
FGFlightRecorder::getDefault(void)
|
||||||
{
|
{
|
||||||
|
SGPropertyNode_ptr ConfigNode;
|
||||||
|
|
||||||
// set name of active flight recorder type
|
// set name of active flight recorder type
|
||||||
SG_LOG(SG_SYSTEMS, SG_INFO, "FlightRecorder: No custom configuration. Loading generic default recorder.");
|
SG_LOG(SG_SYSTEMS, SG_INFO, "FlightRecorder: No custom configuration. Loading generic default recorder.");
|
||||||
|
|
||||||
|
@ -168,7 +179,7 @@ FGFlightRecorder::initDefault(void)
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
readProperties(path.str(), m_RecorderNode->getChild("config", 0 ,true), 0);
|
readProperties(path.str(), m_RecorderNode->getChild("config", 0 ,true), 0);
|
||||||
m_ConfigNode = m_RecorderNode->getChild("config", 0 ,false);
|
ConfigNode = m_RecorderNode->getChild("config", 0 ,false);
|
||||||
} catch (sg_io_exception &e)
|
} catch (sg_io_exception &e)
|
||||||
{
|
{
|
||||||
SG_LOG(SG_SYSTEMS, SG_ALERT, "FlightRecorder: Error reading file '" <<
|
SG_LOG(SG_SYSTEMS, SG_ALERT, "FlightRecorder: Error reading file '" <<
|
||||||
|
@ -176,6 +187,8 @@ FGFlightRecorder::initDefault(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ConfigNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Read signal list below given base node.
|
/** Read signal list below given base node.
|
||||||
|
@ -548,3 +561,37 @@ FGFlightRecorder::replay(double SimTime, const FGReplayData* _pNextBuffer, const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
FGFlightRecorder::getConfig(SGPropertyNode* root, const char* typeStr, const FlightRecorder::TSignalList& SignalList)
|
||||||
|
{
|
||||||
|
static const char* InterpolationTypes[] = {"discrete", "linear", "angular-rad", "angular-deg"};
|
||||||
|
size_t SignalCount = SignalList.size();
|
||||||
|
SGPropertyNode* Signals = root->getNode("signals", true);
|
||||||
|
for (size_t i=0; i<SignalCount; i++)
|
||||||
|
{
|
||||||
|
SGPropertyNode* SignalProp = Signals->addChild("signal");
|
||||||
|
SignalProp->setStringValue("type", typeStr);
|
||||||
|
SignalProp->setStringValue("interpolation", InterpolationTypes[SignalList[i].Interpolation]);
|
||||||
|
SignalProp->setStringValue("property", SignalList[i].Signal->getPath());
|
||||||
|
}
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_DEBUG, "FlightRecorder: Have " << SignalCount << " signals of type " << typeStr);
|
||||||
|
root->setIntValue(typeStr, SignalCount);
|
||||||
|
return SignalCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
FGFlightRecorder::getConfig(SGPropertyNode* root)
|
||||||
|
{
|
||||||
|
root->setStringValue("name", m_RecorderNode->getStringValue("active-config-name", ""));
|
||||||
|
int SignalCount = 0;
|
||||||
|
SignalCount += getConfig(root, "double", m_CaptureDouble);
|
||||||
|
SignalCount += getConfig(root, "float", m_CaptureFloat);
|
||||||
|
SignalCount += getConfig(root, "int", m_CaptureInteger);
|
||||||
|
SignalCount += getConfig(root, "int16", m_CaptureInt16);
|
||||||
|
SignalCount += getConfig(root, "int8", m_CaptureInt8);
|
||||||
|
SignalCount += getConfig(root, "bool", m_CaptureBool);
|
||||||
|
|
||||||
|
root->setIntValue("recorder/record-size", getRecordSize());
|
||||||
|
root->setIntValue("recorder/signal-count", SignalCount);
|
||||||
|
}
|
||||||
|
|
|
@ -54,6 +54,7 @@ public:
|
||||||
virtual ~FGFlightRecorder();
|
virtual ~FGFlightRecorder();
|
||||||
|
|
||||||
void reinit (void);
|
void reinit (void);
|
||||||
|
void reinit (SGPropertyNode_ptr ConfigNode);
|
||||||
FGReplayData* createEmptyRecord (void);
|
FGReplayData* createEmptyRecord (void);
|
||||||
FGReplayData* capture (double SimTime, FGReplayData* pRecycledBuffer);
|
FGReplayData* capture (double SimTime, FGReplayData* pRecycledBuffer);
|
||||||
void replay (double SimTime, const FGReplayData* pNextBuffer,
|
void replay (double SimTime, const FGReplayData* pNextBuffer,
|
||||||
|
@ -61,9 +62,10 @@ public:
|
||||||
void deleteRecord (FGReplayData* pRecord);
|
void deleteRecord (FGReplayData* pRecord);
|
||||||
|
|
||||||
int getRecordSize (void) { return m_TotalRecordSize;}
|
int getRecordSize (void) { return m_TotalRecordSize;}
|
||||||
|
void getConfig (SGPropertyNode* root);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void initDefault(void);
|
SGPropertyNode_ptr getDefault(void);
|
||||||
void initSignalList(const char* pSignalType, FlightRecorder::TSignalList& SignalList,
|
void initSignalList(const char* pSignalType, FlightRecorder::TSignalList& SignalList,
|
||||||
SGPropertyNode_ptr BaseNode);
|
SGPropertyNode_ptr BaseNode);
|
||||||
void processSignalList(const char* pSignalType, FlightRecorder::TSignalList& SignalList,
|
void processSignalList(const char* pSignalType, FlightRecorder::TSignalList& SignalList,
|
||||||
|
@ -72,6 +74,8 @@ private:
|
||||||
bool haveProperty(FlightRecorder::TSignalList& Capture,SGPropertyNode* pProperty);
|
bool haveProperty(FlightRecorder::TSignalList& Capture,SGPropertyNode* pProperty);
|
||||||
bool haveProperty(SGPropertyNode* pProperty);
|
bool haveProperty(SGPropertyNode* pProperty);
|
||||||
|
|
||||||
|
int getConfig(SGPropertyNode* root, const char* typeStr, const FlightRecorder::TSignalList& SignalList);
|
||||||
|
|
||||||
SGPropertyNode_ptr m_RecorderNode;
|
SGPropertyNode_ptr m_RecorderNode;
|
||||||
SGPropertyNode_ptr m_ConfigNode;
|
SGPropertyNode_ptr m_ConfigNode;
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// replay.cxx - a system to record and replay FlightGear flights
|
// replay.cxx - a system to record and replay FlightGear flights
|
||||||
//
|
//
|
||||||
// Written by Curtis Olson, started July 2003.
|
// Written by Curtis Olson, started July 2003.
|
||||||
// Updated by Thorsten Brehm, September 2011.
|
// Updated by Thorsten Brehm, September 2011 and November 2012.
|
||||||
//
|
//
|
||||||
// Copyright (C) 2003 Curtis L. Olson - http://www.flightgear.org/~curt
|
// Copyright (C) 2003 Curtis L. Olson - http://www.flightgear.org/~curt
|
||||||
//
|
//
|
||||||
|
@ -29,17 +29,55 @@
|
||||||
|
|
||||||
#include <simgear/constants.h>
|
#include <simgear/constants.h>
|
||||||
#include <simgear/structure/exception.hxx>
|
#include <simgear/structure/exception.hxx>
|
||||||
|
#include <simgear/props/props_io.hxx>
|
||||||
|
#include <simgear/misc/gzcontainerfile.hxx>
|
||||||
|
#include <simgear/misc/sg_dir.hxx>
|
||||||
|
#include <simgear/misc/stdint.hxx>
|
||||||
|
#include <simgear/misc/strutils.hxx>
|
||||||
|
|
||||||
#include <Main/fg_props.hxx>
|
#include <Main/fg_props.hxx>
|
||||||
|
|
||||||
#include "replay.hxx"
|
#include "replay.hxx"
|
||||||
#include "flightrecorder.hxx"
|
#include "flightrecorder.hxx"
|
||||||
|
|
||||||
|
using std::deque;
|
||||||
|
using std::vector;
|
||||||
|
using simgear::gzContainerReader;
|
||||||
|
using simgear::gzContainerWriter;
|
||||||
|
|
||||||
|
#if 1
|
||||||
|
#define MY_SG_DEBUG SG_DEBUG
|
||||||
|
#else
|
||||||
|
#define MY_SG_DEBUG SG_ALERT
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** Magic string to verify valid FG flight recorder tapes. */
|
||||||
|
static const char* const FlightRecorderFileMagic = "FlightGear Flight Recorder Tape";
|
||||||
|
|
||||||
|
namespace ReplayContainer
|
||||||
|
{
|
||||||
|
enum Type
|
||||||
|
{
|
||||||
|
Invalid = -1,
|
||||||
|
Header = 0, /**< Used for initial file header (fixed identification string). */
|
||||||
|
MetaData = 1, /**< XML data / properties with arbitrary data, such as description, aircraft type, ... */
|
||||||
|
Properties = 2, /**< XML data describing the recorded flight recorder properties.
|
||||||
|
Format is identical to flight recorder XML configuration. Also contains some
|
||||||
|
extra data to verify flight recorder consistency. */
|
||||||
|
RawData = 3 /**< Actual binary data blobs (the recorder's tape).
|
||||||
|
One "RawData" blob is used for each resolution. */
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
FGReplay::FGReplay() :
|
FGReplay::FGReplay() :
|
||||||
|
sim_time(0),
|
||||||
|
last_mt_time(0.0),
|
||||||
|
last_lt_time(0.0),
|
||||||
|
last_msg_time(0),
|
||||||
last_replay_state(0),
|
last_replay_state(0),
|
||||||
m_high_res_time(60.0),
|
m_high_res_time(60.0),
|
||||||
m_medium_res_time(600.0),
|
m_medium_res_time(600.0),
|
||||||
|
@ -88,6 +126,9 @@ FGReplay::clear()
|
||||||
m_pRecorder->deleteRecord(recycler.front());
|
m_pRecorder->deleteRecord(recycler.front());
|
||||||
recycler.pop_front();
|
recycler.pop_front();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clear messages belonging to old replay session
|
||||||
|
fgGetNode("/sim/replay/messages", 0, true)->removeChildren("msg", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -98,11 +139,15 @@ void
|
||||||
FGReplay::init()
|
FGReplay::init()
|
||||||
{
|
{
|
||||||
disable_replay = fgGetNode("/sim/replay/disable", true);
|
disable_replay = fgGetNode("/sim/replay/disable", true);
|
||||||
replay_master = fgGetNode("/sim/freeze/replay-state", true);
|
replay_master = fgGetNode("/sim/replay/replay-state", true);
|
||||||
replay_time = fgGetNode("/sim/replay/time", true);
|
replay_time = fgGetNode("/sim/replay/time", true);
|
||||||
replay_time_str = fgGetNode("/sim/replay/time-str", true);
|
replay_time_str = fgGetNode("/sim/replay/time-str", true);
|
||||||
replay_looped = fgGetNode("/sim/replay/looped", true);
|
replay_looped = fgGetNode("/sim/replay/looped", true);
|
||||||
speed_up = fgGetNode("/sim/speed-up", true);
|
speed_up = fgGetNode("/sim/speed-up", true);
|
||||||
|
|
||||||
|
// alias to keep backward compatibility
|
||||||
|
fgGetNode("/sim/freeze/replay-state", true)->alias(replay_master);
|
||||||
|
|
||||||
reinit();
|
reinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,6 +161,7 @@ FGReplay::reinit()
|
||||||
sim_time = 0.0;
|
sim_time = 0.0;
|
||||||
last_mt_time = 0.0;
|
last_mt_time = 0.0;
|
||||||
last_lt_time = 0.0;
|
last_lt_time = 0.0;
|
||||||
|
last_msg_time = 0.0;
|
||||||
|
|
||||||
// Flush queues
|
// Flush queues
|
||||||
clear();
|
clear();
|
||||||
|
@ -128,20 +174,9 @@ FGReplay::reinit()
|
||||||
m_medium_sample_rate = fgGetDouble("/sim/replay/buffer/medium-res-sample-dt", 0.5); // medium term sample rate (sec)
|
m_medium_sample_rate = fgGetDouble("/sim/replay/buffer/medium-res-sample-dt", 0.5); // medium term sample rate (sec)
|
||||||
m_long_sample_rate = fgGetDouble("/sim/replay/buffer/low-res-sample-dt", 5.0); // long term sample rate (sec)
|
m_long_sample_rate = fgGetDouble("/sim/replay/buffer/low-res-sample-dt", 5.0); // long term sample rate (sec)
|
||||||
|
|
||||||
// Create an estimated nr of required ReplayData objects
|
fillRecycler();
|
||||||
// 120 is an estimated maximum frame rate.
|
loadMessages();
|
||||||
int estNrObjects = (int) ((m_high_res_time*120) + (m_medium_res_time*m_medium_sample_rate) +
|
|
||||||
(m_low_res_time*m_long_sample_rate));
|
|
||||||
for (int i = 0; i < estNrObjects; i++)
|
|
||||||
{
|
|
||||||
FGReplayData* r = m_pRecorder->createEmptyRecord();
|
|
||||||
if (r)
|
|
||||||
recycler.push_back(r);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SG_LOG(SG_SYSTEMS, SG_ALERT, "ReplaySystem: Out of memory!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
replay_master->setIntValue(0);
|
replay_master->setIntValue(0);
|
||||||
disable_replay->setBoolValue(0);
|
disable_replay->setBoolValue(0);
|
||||||
replay_time->setDoubleValue(0);
|
replay_time->setDoubleValue(0);
|
||||||
|
@ -157,7 +192,6 @@ FGReplay::bind()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unbind from the property tree
|
* Unbind from the property tree
|
||||||
*/
|
*/
|
||||||
|
@ -168,6 +202,25 @@ FGReplay::unbind()
|
||||||
// nothing to unbind
|
// nothing to unbind
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
FGReplay::fillRecycler()
|
||||||
|
{
|
||||||
|
// Create an estimated nr of required ReplayData objects
|
||||||
|
// 120 is an estimated maximum frame rate.
|
||||||
|
int estNrObjects = (int) ((m_high_res_time*120) + (m_medium_res_time*m_medium_sample_rate) +
|
||||||
|
(m_low_res_time*m_long_sample_rate));
|
||||||
|
for (int i = 0; i < estNrObjects; i++)
|
||||||
|
{
|
||||||
|
FGReplayData* r = m_pRecorder->createEmptyRecord();
|
||||||
|
if (r)
|
||||||
|
recycler.push_back(r);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_ALERT, "ReplaySystem: Out of memory!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
printTimeStr(char* pStrBuffer,double _Time, bool ShowDecimal=true)
|
printTimeStr(char* pStrBuffer,double _Time, bool ShowDecimal=true)
|
||||||
{
|
{
|
||||||
|
@ -195,14 +248,67 @@ printTimeStr(char* pStrBuffer,double _Time, bool ShowDecimal=true)
|
||||||
sprintf(&pStrBuffer[len],".%u",d);
|
sprintf(&pStrBuffer[len],".%u",d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
FGReplay::guiMessage(const char* message)
|
||||||
|
{
|
||||||
|
fgSetString("/sim/messages/copilot", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
FGReplay::loadMessages()
|
||||||
|
{
|
||||||
|
// load messages
|
||||||
|
replay_messages.clear();
|
||||||
|
simgear::PropertyList msgs = fgGetNode("/sim/replay/messages", true)->getChildren("msg");
|
||||||
|
|
||||||
|
for (simgear::PropertyList::iterator it = msgs.begin();it != msgs.end();++it)
|
||||||
|
{
|
||||||
|
const char* msgText = (*it)->getStringValue("text", "");
|
||||||
|
const double msgTime = (*it)->getDoubleValue("time", -1.0);
|
||||||
|
const char* msgSpeaker = (*it)->getStringValue("speaker", "pilot");
|
||||||
|
if ((msgText[0] != 0)&&(msgTime >= 0))
|
||||||
|
{
|
||||||
|
FGReplayMessages data;
|
||||||
|
data.sim_time = msgTime;
|
||||||
|
data.message = msgText;
|
||||||
|
data.speaker = msgSpeaker;
|
||||||
|
replay_messages.push_back(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current_msg = replay_messages.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
FGReplay::replayMessage(double time)
|
||||||
|
{
|
||||||
|
if (time < last_msg_time)
|
||||||
|
{
|
||||||
|
current_msg = replay_messages.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if messages have to be triggered
|
||||||
|
while ((current_msg != replay_messages.end())&&
|
||||||
|
(time >= current_msg->sim_time))
|
||||||
|
{
|
||||||
|
// don't trigger messages when too long ago (fast-forward/skipped replay)
|
||||||
|
if (time - current_msg->sim_time < 3.0)
|
||||||
|
{
|
||||||
|
fgGetNode("/sim/messages", true)->getNode(current_msg->speaker, true)->setStringValue(current_msg->message);
|
||||||
|
}
|
||||||
|
++current_msg;
|
||||||
|
}
|
||||||
|
last_msg_time = time;
|
||||||
|
}
|
||||||
|
|
||||||
/** Start replay session
|
/** Start replay session
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
FGReplay::start()
|
FGReplay::start(bool NewTape)
|
||||||
{
|
{
|
||||||
// freeze the fdm, resume from sim pause
|
// freeze the fdm, resume from sim pause
|
||||||
double StartTime = get_start_time();
|
double StartTime = get_start_time();
|
||||||
double EndTime = get_end_time();
|
double EndTime = get_end_time();
|
||||||
|
was_finished_already = false;
|
||||||
fgSetDouble("/sim/replay/start-time", StartTime);
|
fgSetDouble("/sim/replay/start-time", StartTime);
|
||||||
fgSetDouble("/sim/replay/end-time", EndTime);
|
fgSetDouble("/sim/replay/end-time", EndTime);
|
||||||
char StrBuffer[30];
|
char StrBuffer[30];
|
||||||
|
@ -216,15 +322,26 @@ FGReplay::start()
|
||||||
buffer_elements*m_pRecorder->getRecordSize() / (1024*1024.0));
|
buffer_elements*m_pRecorder->getRecordSize() / (1024*1024.0));
|
||||||
if ((fgGetBool("/sim/freeze/master"))||
|
if ((fgGetBool("/sim/freeze/master"))||
|
||||||
(0 == replay_master->getIntValue()))
|
(0 == replay_master->getIntValue()))
|
||||||
fgSetString("/sim/messages/copilot", "Replay active. 'Esc' to stop.");
|
guiMessage("Replay active. 'Esc' to stop.");
|
||||||
fgSetBool ("/sim/freeze/master", 0);
|
fgSetBool ("/sim/freeze/master", 0);
|
||||||
fgSetBool ("/sim/freeze/clock", 0);
|
fgSetBool ("/sim/freeze/clock", 0);
|
||||||
if (0 == replay_master->getIntValue())
|
if (0 == replay_master->getIntValue())
|
||||||
{
|
{
|
||||||
replay_master->setIntValue(1);
|
replay_master->setIntValue(1);
|
||||||
replay_time->setDoubleValue(-1);
|
if (NewTape)
|
||||||
|
{
|
||||||
|
// start replay at initial time, when loading a new tape
|
||||||
|
replay_time->setDoubleValue(StartTime);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// start replay at "loop interval" when starting instant replay
|
||||||
|
replay_time->setDoubleValue(-1);
|
||||||
|
}
|
||||||
replay_time_str->setStringValue("");
|
replay_time_str->setStringValue("");
|
||||||
}
|
}
|
||||||
|
loadMessages();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,6 +361,7 @@ FGReplay::update( double dt )
|
||||||
if (fgGetBool("/sim/freeze/master",false)||
|
if (fgGetBool("/sim/freeze/master",false)||
|
||||||
fgGetBool("/sim/freeze/clock",false))
|
fgGetBool("/sim/freeze/clock",false))
|
||||||
{
|
{
|
||||||
|
// unpause - disable the replay system in next loop
|
||||||
fgSetBool("/sim/freeze/master",false);
|
fgSetBool("/sim/freeze/master",false);
|
||||||
fgSetBool("/sim/freeze/clock",false);
|
fgSetBool("/sim/freeze/clock",false);
|
||||||
last_replay_state = 1;
|
last_replay_state = 1;
|
||||||
|
@ -252,6 +370,7 @@ FGReplay::update( double dt )
|
||||||
if ((replay_master->getIntValue() != 3)||
|
if ((replay_master->getIntValue() != 3)||
|
||||||
(last_replay_state == 3))
|
(last_replay_state == 3))
|
||||||
{
|
{
|
||||||
|
// disable the replay system
|
||||||
current_replay_state = replay_master->getIntValue();
|
current_replay_state = replay_master->getIntValue();
|
||||||
replay_master->setIntValue(0);
|
replay_master->setIntValue(0);
|
||||||
replay_time->setDoubleValue(0);
|
replay_time->setDoubleValue(0);
|
||||||
|
@ -264,7 +383,7 @@ FGReplay::update( double dt )
|
||||||
fgSetBool("/sim/sound/enabled",true);
|
fgSetBool("/sim/sound/enabled",true);
|
||||||
fgSetBool("/sim/replay/mute",false);
|
fgSetBool("/sim/replay/mute",false);
|
||||||
}
|
}
|
||||||
fgSetString("/sim/messages/copilot", "Replay stopped. Your controls!");
|
guiMessage("Replay stopped. Your controls!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,7 +429,9 @@ FGReplay::update( double dt )
|
||||||
double endTime = get_end_time();
|
double endTime = get_end_time();
|
||||||
fgSetDouble( "/sim/replay/start-time", startTime );
|
fgSetDouble( "/sim/replay/start-time", startTime );
|
||||||
fgSetDouble( "/sim/replay/end-time", endTime );
|
fgSetDouble( "/sim/replay/end-time", endTime );
|
||||||
double duration = fgGetDouble( "/sim/replay/duration" );
|
double duration = 0;
|
||||||
|
if (replay_looped->getBoolValue())
|
||||||
|
fgGetDouble("/sim/replay/duration");
|
||||||
if( duration && (duration < (endTime - startTime)) ) {
|
if( duration && (duration < (endTime - startTime)) ) {
|
||||||
current_time = endTime - duration;
|
current_time = endTime - duration;
|
||||||
} else {
|
} else {
|
||||||
|
@ -319,9 +440,19 @@ FGReplay::update( double dt )
|
||||||
}
|
}
|
||||||
bool IsFinished = replay( replay_time->getDoubleValue() );
|
bool IsFinished = replay( replay_time->getDoubleValue() );
|
||||||
if (IsFinished)
|
if (IsFinished)
|
||||||
|
{
|
||||||
|
if (!was_finished_already)
|
||||||
|
{
|
||||||
|
guiMessage("End of tape. 'Esc' to return.");
|
||||||
|
was_finished_already = true;
|
||||||
|
}
|
||||||
current_time = (replay_looped->getBoolValue()) ? -1 : get_end_time()+0.01;
|
current_time = (replay_looped->getBoolValue()) ? -1 : get_end_time()+0.01;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
current_time += dt * speed_up->getDoubleValue();
|
current_time += dt * speed_up->getDoubleValue();
|
||||||
|
was_finished_already = false;
|
||||||
|
}
|
||||||
replay_time->setDoubleValue(current_time);
|
replay_time->setDoubleValue(current_time);
|
||||||
char StrBuffer[30];
|
char StrBuffer[30];
|
||||||
printTimeStr(StrBuffer,current_time);
|
printTimeStr(StrBuffer,current_time);
|
||||||
|
@ -341,7 +472,6 @@ FGReplay::update( double dt )
|
||||||
|
|
||||||
// flight recording
|
// flight recording
|
||||||
|
|
||||||
//cerr << "Recording replay" << endl;
|
|
||||||
sim_time += dt * speed_up->getDoubleValue();
|
sim_time += dt * speed_up->getDoubleValue();
|
||||||
|
|
||||||
// sanity check, don't collect data if FDM data isn't good
|
// sanity check, don't collect data if FDM data isn't good
|
||||||
|
@ -357,9 +487,7 @@ FGReplay::update( double dt )
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the short term list
|
// update the short term list
|
||||||
//stamp("point_06");
|
|
||||||
short_term.push_back( r );
|
short_term.push_back( r );
|
||||||
//stamp("point_07");
|
|
||||||
FGReplayData *st_front = short_term.front();
|
FGReplayData *st_front = short_term.front();
|
||||||
|
|
||||||
if (!st_front)
|
if (!st_front)
|
||||||
|
@ -367,39 +495,45 @@ FGReplay::update( double dt )
|
||||||
SG_LOG(SG_SYSTEMS, SG_ALERT, "ReplaySystem: Inconsistent data!");
|
SG_LOG(SG_SYSTEMS, SG_ALERT, "ReplaySystem: Inconsistent data!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( sim_time - st_front->sim_time > m_high_res_time ) {
|
if ( sim_time - st_front->sim_time > m_high_res_time )
|
||||||
while ( sim_time - st_front->sim_time > m_high_res_time ) {
|
{
|
||||||
|
while ( sim_time - st_front->sim_time > m_high_res_time )
|
||||||
|
{
|
||||||
st_front = short_term.front();
|
st_front = short_term.front();
|
||||||
recycler.push_back(st_front);
|
recycler.push_back(st_front);
|
||||||
short_term.pop_front();
|
short_term.pop_front();
|
||||||
}
|
}
|
||||||
//stamp("point_08");
|
|
||||||
// update the medium term list
|
// update the medium term list
|
||||||
if ( sim_time - last_mt_time > m_medium_sample_rate ) {
|
if ( sim_time - last_mt_time > m_medium_sample_rate )
|
||||||
|
{
|
||||||
last_mt_time = sim_time;
|
last_mt_time = sim_time;
|
||||||
st_front = short_term.front();
|
st_front = short_term.front();
|
||||||
medium_term.push_back( st_front );
|
medium_term.push_back( st_front );
|
||||||
short_term.pop_front();
|
short_term.pop_front();
|
||||||
|
|
||||||
FGReplayData *mt_front = medium_term.front();
|
FGReplayData *mt_front = medium_term.front();
|
||||||
if ( sim_time - mt_front->sim_time > m_medium_res_time ) {
|
if ( sim_time - mt_front->sim_time > m_medium_res_time )
|
||||||
//stamp("point_09");
|
{
|
||||||
while ( sim_time - mt_front->sim_time > m_medium_res_time ) {
|
while ( sim_time - mt_front->sim_time > m_medium_res_time )
|
||||||
|
{
|
||||||
mt_front = medium_term.front();
|
mt_front = medium_term.front();
|
||||||
recycler.push_back(mt_front);
|
recycler.push_back(mt_front);
|
||||||
medium_term.pop_front();
|
medium_term.pop_front();
|
||||||
}
|
}
|
||||||
// update the long term list
|
// update the long term list
|
||||||
if ( sim_time - last_lt_time > m_long_sample_rate ) {
|
if ( sim_time - last_lt_time > m_long_sample_rate )
|
||||||
|
{
|
||||||
last_lt_time = sim_time;
|
last_lt_time = sim_time;
|
||||||
mt_front = medium_term.front();
|
mt_front = medium_term.front();
|
||||||
long_term.push_back( mt_front );
|
long_term.push_back( mt_front );
|
||||||
medium_term.pop_front();
|
medium_term.pop_front();
|
||||||
|
|
||||||
FGReplayData *lt_front = long_term.front();
|
FGReplayData *lt_front = long_term.front();
|
||||||
if ( sim_time - lt_front->sim_time > m_low_res_time ) {
|
if ( sim_time - lt_front->sim_time > m_low_res_time )
|
||||||
//stamp("point_10");
|
{
|
||||||
while ( sim_time - lt_front->sim_time > m_low_res_time ) {
|
while ( sim_time - lt_front->sim_time > m_low_res_time )
|
||||||
|
{
|
||||||
lt_front = long_term.front();
|
lt_front = long_term.front();
|
||||||
recycler.push_back(lt_front);
|
recycler.push_back(lt_front);
|
||||||
long_term.pop_front();
|
long_term.pop_front();
|
||||||
|
@ -435,9 +569,7 @@ FGReplay::record(double time)
|
||||||
recycler.pop_front();
|
recycler.pop_front();
|
||||||
}
|
}
|
||||||
|
|
||||||
r = m_pRecorder->capture(time, r);
|
return m_pRecorder->capture(time, r);
|
||||||
|
|
||||||
return r;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -496,6 +628,8 @@ FGReplay::replay( double time ) {
|
||||||
// find the two frames to interpolate between
|
// find the two frames to interpolate between
|
||||||
double t1, t2;
|
double t1, t2;
|
||||||
|
|
||||||
|
replayMessage(time);
|
||||||
|
|
||||||
if ( short_term.size() > 0 ) {
|
if ( short_term.size() > 0 ) {
|
||||||
t1 = short_term.back()->sim_time;
|
t1 = short_term.back()->sim_time;
|
||||||
t2 = short_term.front()->sim_time;
|
t2 = short_term.front()->sim_time;
|
||||||
|
@ -504,52 +638,43 @@ FGReplay::replay( double time ) {
|
||||||
replay( time, short_term.back() );
|
replay( time, short_term.back() );
|
||||||
// replay is finished now
|
// replay is finished now
|
||||||
return true;
|
return true;
|
||||||
// cout << "first frame" << endl;
|
|
||||||
} else if ( time <= t1 && time >= t2 ) {
|
} else if ( time <= t1 && time >= t2 ) {
|
||||||
interpolate( time, short_term );
|
interpolate( time, short_term );
|
||||||
// cout << "from short term" << endl;
|
|
||||||
} else if ( medium_term.size() > 0 ) {
|
} else if ( medium_term.size() > 0 ) {
|
||||||
t1 = short_term.front()->sim_time;
|
t1 = short_term.front()->sim_time;
|
||||||
t2 = medium_term.back()->sim_time;
|
t2 = medium_term.back()->sim_time;
|
||||||
if ( time <= t1 && time >= t2 )
|
if ( time <= t1 && time >= t2 )
|
||||||
{
|
{
|
||||||
replay(time, medium_term.back(), short_term.front());
|
replay(time, medium_term.back(), short_term.front());
|
||||||
// cout << "from short/medium term" << endl;
|
|
||||||
} else {
|
} else {
|
||||||
t1 = medium_term.back()->sim_time;
|
t1 = medium_term.back()->sim_time;
|
||||||
t2 = medium_term.front()->sim_time;
|
t2 = medium_term.front()->sim_time;
|
||||||
if ( time <= t1 && time >= t2 ) {
|
if ( time <= t1 && time >= t2 ) {
|
||||||
interpolate( time, medium_term );
|
interpolate( time, medium_term );
|
||||||
// cout << "from medium term" << endl;
|
|
||||||
} else if ( long_term.size() > 0 ) {
|
} else if ( long_term.size() > 0 ) {
|
||||||
t1 = medium_term.front()->sim_time;
|
t1 = medium_term.front()->sim_time;
|
||||||
t2 = long_term.back()->sim_time;
|
t2 = long_term.back()->sim_time;
|
||||||
if ( time <= t1 && time >= t2 )
|
if ( time <= t1 && time >= t2 )
|
||||||
{
|
{
|
||||||
replay(time, long_term.back(), medium_term.front());
|
replay(time, long_term.back(), medium_term.front());
|
||||||
// cout << "from medium/long term" << endl;
|
|
||||||
} else {
|
} else {
|
||||||
t1 = long_term.back()->sim_time;
|
t1 = long_term.back()->sim_time;
|
||||||
t2 = long_term.front()->sim_time;
|
t2 = long_term.front()->sim_time;
|
||||||
if ( time <= t1 && time >= t2 ) {
|
if ( time <= t1 && time >= t2 ) {
|
||||||
interpolate( time, long_term );
|
interpolate( time, long_term );
|
||||||
// cout << "from long term" << endl;
|
|
||||||
} else {
|
} else {
|
||||||
// replay the oldest long term frame
|
// replay the oldest long term frame
|
||||||
replay(time, long_term.front());
|
replay(time, long_term.front());
|
||||||
// cout << "oldest long term frame" << endl;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// replay the oldest medium term frame
|
// replay the oldest medium term frame
|
||||||
replay(time, medium_term.front());
|
replay(time, medium_term.front());
|
||||||
// cout << "oldest medium term frame" << endl;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// replay the oldest short term frame
|
// replay the oldest short term frame
|
||||||
replay(time, short_term.front());
|
replay(time, short_term.front());
|
||||||
// cout << "oldest short term frame" << endl;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// nothing to replay
|
// nothing to replay
|
||||||
|
@ -596,3 +721,418 @@ FGReplay::get_end_time()
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Save raw replay data in a separate container */
|
||||||
|
static bool
|
||||||
|
saveRawReplayData(gzContainerWriter& output, const replay_list_type& ReplayData, size_t RecordSize)
|
||||||
|
{
|
||||||
|
// get number of records in this stream
|
||||||
|
size_t Count = ReplayData.size();
|
||||||
|
|
||||||
|
// write container header for raw data
|
||||||
|
if (!output.writeContainerHeader(ReplayContainer::RawData, Count * RecordSize))
|
||||||
|
{
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to save replay data. Cannot write data container. Disk full?");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// write the raw data (all records in the given list)
|
||||||
|
replay_list_type::const_iterator it = ReplayData.begin();
|
||||||
|
size_t CheckCount = 0;
|
||||||
|
while ((it != ReplayData.end())&&
|
||||||
|
!output.fail())
|
||||||
|
{
|
||||||
|
const FGReplayData* pRecord = *it++;
|
||||||
|
output.write((char*)pRecord, RecordSize);
|
||||||
|
CheckCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Did we really write as much as we intended?
|
||||||
|
if (CheckCount != Count)
|
||||||
|
{
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to save replay data. Expected to write " << Count << " records, but wrote " << CheckCount);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SG_LOG(SG_SYSTEMS, MY_SG_DEBUG, "Saved " << CheckCount << " records of size " << RecordSize);
|
||||||
|
return !output.fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Load raw replay data from a separate container */
|
||||||
|
static bool
|
||||||
|
loadRawReplayData(gzContainerReader& input, FGFlightRecorder* pRecorder, replay_list_type& ReplayData, size_t RecordSize)
|
||||||
|
{
|
||||||
|
size_t Size = 0;
|
||||||
|
simgear::ContainerType Type = ReplayContainer::Invalid;
|
||||||
|
|
||||||
|
// write container header for raw data
|
||||||
|
if (!input.readContainerHeader(&Type, &Size))
|
||||||
|
{
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to load replay data. Missing data container.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (Type != ReplayContainer::RawData)
|
||||||
|
{
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to load replay data. Expected data container, got " << Type);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read the raw data
|
||||||
|
size_t Count = Size / RecordSize;
|
||||||
|
SG_LOG(SG_SYSTEMS, MY_SG_DEBUG, "Loading replay data. Container size is " << Size << ", record size " << RecordSize <<
|
||||||
|
", expected record count " << Count << ".");
|
||||||
|
|
||||||
|
size_t CheckCount = 0;
|
||||||
|
for (CheckCount=0; (CheckCount<Count)&&(!input.eof()); ++CheckCount)
|
||||||
|
{
|
||||||
|
FGReplayData* pBuffer = pRecorder->createEmptyRecord();
|
||||||
|
input.read((char*) pBuffer, RecordSize);
|
||||||
|
ReplayData.push_back(pBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// did we get all we have hoped for?
|
||||||
|
if (CheckCount != Count)
|
||||||
|
{
|
||||||
|
if (input.eof())
|
||||||
|
{
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_ALERT, "Unexpected end of file.");
|
||||||
|
}
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to load replay data. Expected " << Count << " records, but got " << CheckCount);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SG_LOG(SG_SYSTEMS, MY_SG_DEBUG, "Loaded " << CheckCount << " records of size " << RecordSize);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Write flight recorder tape with given filename and meta properties to disk */
|
||||||
|
bool
|
||||||
|
FGReplay::saveTape(const char* Filename, SGPropertyNode* MetaDataProps)
|
||||||
|
{
|
||||||
|
bool ok = true;
|
||||||
|
|
||||||
|
/* open output stream *******************************************/
|
||||||
|
gzContainerWriter output(Filename, FlightRecorderFileMagic);
|
||||||
|
if (!output.good())
|
||||||
|
{
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_ALERT, "Cannot open file" << Filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* write meta data **********************************************/
|
||||||
|
ok &= output.writeContainer(ReplayContainer::MetaData, MetaDataProps);
|
||||||
|
|
||||||
|
/* write flight recorder configuration **************************/
|
||||||
|
SGPropertyNode_ptr Config;
|
||||||
|
if (ok)
|
||||||
|
{
|
||||||
|
Config = new SGPropertyNode();
|
||||||
|
m_pRecorder->getConfig(Config.get());
|
||||||
|
ok &= output.writeContainer(ReplayContainer::Properties, Config.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* write raw data ***********************************************/
|
||||||
|
if (Config)
|
||||||
|
{
|
||||||
|
size_t RecordSize = Config->getIntValue("recorder/record-size", 0);
|
||||||
|
SG_LOG(SG_SYSTEMS, MY_SG_DEBUG, "Total signal count: " << Config->getIntValue("recorder/signal-count", 0)
|
||||||
|
<< ", record size: " << RecordSize);
|
||||||
|
if (ok)
|
||||||
|
ok &= saveRawReplayData(output, short_term, RecordSize);
|
||||||
|
if (ok)
|
||||||
|
ok &= saveRawReplayData(output, medium_term, RecordSize);
|
||||||
|
if (ok)
|
||||||
|
ok &= saveRawReplayData(output, long_term, RecordSize);
|
||||||
|
Config = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* done *********************************************************/
|
||||||
|
output.close();
|
||||||
|
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Write flight recorder tape to disk. User/script command. */
|
||||||
|
bool
|
||||||
|
FGReplay::saveTape(const SGPropertyNode* ConfigData)
|
||||||
|
{
|
||||||
|
const char* tapeDirectory = fgGetString("/sim/replay/tape-directory", "");
|
||||||
|
const char* aircraftType = fgGetString("/sim/aircraft", "unknown");
|
||||||
|
|
||||||
|
SGPropertyNode_ptr myMetaData = new SGPropertyNode();
|
||||||
|
SGPropertyNode* meta = myMetaData->getNode("meta", 0, true);
|
||||||
|
|
||||||
|
// add some data to the file - so we know for which aircraft/version it was recorded
|
||||||
|
meta->setStringValue("aircraft-type", aircraftType);
|
||||||
|
meta->setStringValue("aircraft-description", fgGetString("/sim/description", ""));
|
||||||
|
meta->setStringValue("aircraft-fdm", fgGetString("/sim/flight-model", ""));
|
||||||
|
meta->setStringValue("closest-airport-id", fgGetString("/sim/airport/closest-airport-id", ""));
|
||||||
|
const char* aircraft_version = fgGetString("/sim/aircraft-version", "");
|
||||||
|
if (aircraft_version[0]==0)
|
||||||
|
aircraft_version = "(unknown aircraft version)";
|
||||||
|
meta->setStringValue("aircraft-version", aircraft_version);
|
||||||
|
|
||||||
|
// add information on the tape's recording duration
|
||||||
|
double Duration = get_end_time()-get_start_time();
|
||||||
|
meta->setDoubleValue("tape-duration", Duration);
|
||||||
|
char StrBuffer[30];
|
||||||
|
printTimeStr(StrBuffer, Duration, false);
|
||||||
|
meta->setStringValue("tape-duration-str", StrBuffer);
|
||||||
|
|
||||||
|
// add simulator version
|
||||||
|
copyProperties(fgGetNode("/sim/version", 0, true), meta->getNode("version", 0, true));
|
||||||
|
if (ConfigData->getNode("user-data"))
|
||||||
|
{
|
||||||
|
copyProperties(ConfigData->getNode("user-data"), meta->getNode("user-data", 0, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
// store replay messages
|
||||||
|
copyProperties(fgGetNode("/sim/replay/messages", 0, true), myMetaData->getNode("messages", 0, true));
|
||||||
|
|
||||||
|
// generate file name (directory + aircraft type + date + time + suffix)
|
||||||
|
SGPath p(tapeDirectory);
|
||||||
|
p.append(aircraftType);
|
||||||
|
p.concat("-");
|
||||||
|
time_t calendar_time = time(NULL);
|
||||||
|
struct tm *local_tm;
|
||||||
|
local_tm = localtime( &calendar_time );
|
||||||
|
char time_str[256];
|
||||||
|
strftime( time_str, 256, "%Y%02m%02d-%02H%02M%02S", local_tm);
|
||||||
|
p.concat(time_str);
|
||||||
|
p.concat(".fgtape");
|
||||||
|
|
||||||
|
bool ok = true;
|
||||||
|
// make sure we're not overwriting something
|
||||||
|
if (p.exists())
|
||||||
|
{
|
||||||
|
// same timestamp!?
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_ALERT, "Error, flight recorder tape file with same name already exists.");
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ok)
|
||||||
|
ok &= saveTape(p.c_str(), myMetaData.get());
|
||||||
|
|
||||||
|
if (ok)
|
||||||
|
guiMessage("Flight recorder tape saved successfully!");
|
||||||
|
else
|
||||||
|
guiMessage("Failed to save tape! See log output.");
|
||||||
|
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read a flight recorder tape with given filename from disk and return meta properties.
|
||||||
|
* Actual data and signal configuration is not read when in "Preview" mode.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
FGReplay::loadTape(const char* Filename, bool Preview, SGPropertyNode* UserData)
|
||||||
|
{
|
||||||
|
bool ok = true;
|
||||||
|
|
||||||
|
/* open input stream ********************************************/
|
||||||
|
gzContainerReader input(Filename, FlightRecorderFileMagic);
|
||||||
|
if (input.eof() || !input.good())
|
||||||
|
{
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_ALERT, "Cannot open file " << Filename);
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SGPropertyNode_ptr MetaDataProps = new SGPropertyNode();
|
||||||
|
|
||||||
|
/* read meta data ***********************************************/
|
||||||
|
if (ok)
|
||||||
|
{
|
||||||
|
char* MetaData = NULL;
|
||||||
|
size_t Size = 0;
|
||||||
|
simgear::ContainerType Type = ReplayContainer::Invalid;
|
||||||
|
if (!input.readContainer(&Type, &MetaData, &Size) || Size<1)
|
||||||
|
{
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_ALERT, "File not recognized. This is not a valid FlightGear flight recorder tape: " << Filename
|
||||||
|
<< ". Invalid meta data.");
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (Type != ReplayContainer::MetaData)
|
||||||
|
{
|
||||||
|
SG_LOG(SG_SYSTEMS, MY_SG_DEBUG, "Invalid header. Container type " << Type);
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_ALERT, "File not recognized. This is not a valid FlightGear flight recorder tape: " << Filename);
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
readProperties(MetaData, Size-1, MetaDataProps);
|
||||||
|
copyProperties(MetaDataProps->getNode("meta", 0, true), UserData);
|
||||||
|
} catch (const sg_exception &e)
|
||||||
|
{
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_ALERT, "Error reading flight recorder tape: " << Filename
|
||||||
|
<< ", XML parser message:" << e.getFormattedMessage());
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MetaData)
|
||||||
|
{
|
||||||
|
//printf("%s\n", MetaData);
|
||||||
|
free(MetaData);
|
||||||
|
MetaData = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* read flight recorder configuration **************************/
|
||||||
|
if ((ok)&&(!Preview))
|
||||||
|
{
|
||||||
|
SG_LOG(SG_SYSTEMS, MY_SG_DEBUG, "Loading flight recorder data...");
|
||||||
|
char* ConfigXML = NULL;
|
||||||
|
size_t Size = 0;
|
||||||
|
simgear::ContainerType Type = ReplayContainer::Invalid;
|
||||||
|
if (!input.readContainer(&Type, &ConfigXML, &Size) || Size<1)
|
||||||
|
{
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_ALERT, "File not recognized. This is not a valid FlightGear flight recorder tape: " << Filename
|
||||||
|
<< ". Invalid configuration container.");
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if ((!ConfigXML)||(Type != ReplayContainer::Properties))
|
||||||
|
{
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_ALERT, "File not recognized. This is not a valid FlightGear flight recorder tape: " << Filename
|
||||||
|
<< ". Unexpected container type, expected \"properties\".");
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SGPropertyNode_ptr Config = new SGPropertyNode();
|
||||||
|
if (ok)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
readProperties(ConfigXML, Size-1, Config);
|
||||||
|
} catch (const sg_exception &e)
|
||||||
|
{
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_ALERT, "Error reading flight recorder tape: " << Filename
|
||||||
|
<< ", XML parser message:" << e.getFormattedMessage());
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
if (ok)
|
||||||
|
{
|
||||||
|
// reconfigure the recorder - and wipe old data (no longer matches the current recorder)
|
||||||
|
m_pRecorder->reinit(Config);
|
||||||
|
clear();
|
||||||
|
fillRecycler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ConfigXML)
|
||||||
|
{
|
||||||
|
free(ConfigXML);
|
||||||
|
ConfigXML = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* read raw data ***********************************************/
|
||||||
|
if (ok)
|
||||||
|
{
|
||||||
|
size_t RecordSize = m_pRecorder->getRecordSize();
|
||||||
|
size_t OriginalSize = Config->getIntValue("recorder/record-size", 0);
|
||||||
|
// check consistency - ugly things happen when data vs signals mismatch
|
||||||
|
if ((OriginalSize != RecordSize)&&
|
||||||
|
(OriginalSize != 0))
|
||||||
|
{
|
||||||
|
ok = false;
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_ALERT, "Error: Data inconsistency. Flight recorder tape has record size " << RecordSize
|
||||||
|
<< ", expected size was " << OriginalSize << ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ok)
|
||||||
|
ok &= loadRawReplayData(input, m_pRecorder, short_term, RecordSize);
|
||||||
|
if (ok)
|
||||||
|
ok &= loadRawReplayData(input, m_pRecorder, medium_term, RecordSize);
|
||||||
|
if (ok)
|
||||||
|
ok &= loadRawReplayData(input, m_pRecorder, long_term, RecordSize);
|
||||||
|
|
||||||
|
// restore replay messages
|
||||||
|
if (ok)
|
||||||
|
{
|
||||||
|
copyProperties(MetaDataProps->getNode("messages", 0, true),
|
||||||
|
fgGetNode("/sim/replay/messages", 0, true));
|
||||||
|
}
|
||||||
|
sim_time = get_end_time();
|
||||||
|
// TODO we could (re)store these too
|
||||||
|
last_mt_time = last_lt_time = sim_time;
|
||||||
|
}
|
||||||
|
/* done *********************************************************/
|
||||||
|
}
|
||||||
|
|
||||||
|
input.close();
|
||||||
|
|
||||||
|
if (!Preview)
|
||||||
|
{
|
||||||
|
if (ok)
|
||||||
|
{
|
||||||
|
guiMessage("Flight recorder tape loaded successfully!");
|
||||||
|
start(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
guiMessage("Failed to load tape. See log output.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** List available tapes in current directory.
|
||||||
|
* Limits to tapes matching current aircraft when SameAircraftFilter is enabled.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
FGReplay::listTapes(bool SameAircraftFilter, const SGPath& tapeDirectory)
|
||||||
|
{
|
||||||
|
const std::string& aircraftType = simgear::strutils::uppercase(fgGetString("/sim/aircraft", "unknown"));
|
||||||
|
|
||||||
|
// process directory listing of ".fgtape" files
|
||||||
|
simgear::Dir dir(tapeDirectory);
|
||||||
|
simgear::PathList list = dir.children(simgear::Dir::TYPE_FILE, ".fgtape");
|
||||||
|
|
||||||
|
SGPropertyNode* TapeList = fgGetNode("/sim/replay/tape-list", true);
|
||||||
|
TapeList->removeChildren("tape", false);
|
||||||
|
int Index = 0;
|
||||||
|
size_t l = aircraftType.size();
|
||||||
|
for (simgear::PathList::iterator it = list.begin(); it!=list.end(); ++it)
|
||||||
|
{
|
||||||
|
SGPath file(it->file());
|
||||||
|
std::string name(file.base());
|
||||||
|
if ((!SameAircraftFilter)||
|
||||||
|
(0==simgear::strutils::uppercase(name).compare(0,l, aircraftType)))
|
||||||
|
{
|
||||||
|
TapeList->getNode("tape", Index++, true)->setStringValue(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Load a flight recorder tape from disk. User/script command. */
|
||||||
|
bool
|
||||||
|
FGReplay::loadTape(const SGPropertyNode* ConfigData)
|
||||||
|
{
|
||||||
|
SGPath tapeDirectory(fgGetString("/sim/replay/tape-directory", ""));
|
||||||
|
|
||||||
|
// see if shall really load the file - or just obtain the meta data for preview
|
||||||
|
bool Preview = ConfigData->getBoolValue("preview", 0);
|
||||||
|
|
||||||
|
// file/tape to be loaded
|
||||||
|
std::string tape = ConfigData->getStringValue("tape", "");
|
||||||
|
|
||||||
|
if (tape.empty())
|
||||||
|
{
|
||||||
|
if (!Preview)
|
||||||
|
return listTapes(ConfigData->getBoolValue("same-aircraft", 0), tapeDirectory);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SGPropertyNode* UserData = fgGetNode("/sim/gui/dialogs/flightrecorder/preview", true);
|
||||||
|
tapeDirectory.append(tape);
|
||||||
|
tapeDirectory.concat(".fgtape");
|
||||||
|
SG_LOG(SG_SYSTEMS, MY_SG_DEBUG, "Checking flight recorder file " << tapeDirectory << ", preview: " << Preview);
|
||||||
|
return loadTape(tapeDirectory.c_str(), Preview, UserData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -30,13 +30,12 @@
|
||||||
|
|
||||||
#include <simgear/compiler.h>
|
#include <simgear/compiler.h>
|
||||||
|
|
||||||
#include <deque>
|
|
||||||
|
|
||||||
#include <simgear/math/sg_types.hxx>
|
#include <simgear/math/sg_types.hxx>
|
||||||
#include <simgear/props/props.hxx>
|
#include <simgear/props/props.hxx>
|
||||||
#include <simgear/structure/subsystem_mgr.hxx>
|
#include <simgear/structure/subsystem_mgr.hxx>
|
||||||
|
|
||||||
using std::deque;
|
#include <deque>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
class FGFlightRecorder;
|
class FGFlightRecorder;
|
||||||
|
|
||||||
|
@ -46,9 +45,14 @@ typedef struct {
|
||||||
/* more data here, hidden to the outside world */
|
/* more data here, hidden to the outside world */
|
||||||
} FGReplayData;
|
} FGReplayData;
|
||||||
|
|
||||||
typedef deque < FGReplayData *> replay_list_type;
|
typedef struct {
|
||||||
|
double sim_time;
|
||||||
|
std::string message;
|
||||||
|
std::string speaker;
|
||||||
|
} FGReplayMessages;
|
||||||
|
|
||||||
|
typedef std::deque < FGReplayData *> replay_list_type;
|
||||||
|
typedef std::vector < FGReplayMessages > replay_messages_type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A recording/replay module for FlightGear flights
|
* A recording/replay module for FlightGear flights
|
||||||
|
@ -57,9 +61,7 @@ typedef deque < FGReplayData *> replay_list_type;
|
||||||
|
|
||||||
class FGReplay : public SGSubsystem
|
class FGReplay : public SGSubsystem
|
||||||
{
|
{
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
FGReplay ();
|
FGReplay ();
|
||||||
virtual ~FGReplay();
|
virtual ~FGReplay();
|
||||||
|
|
||||||
|
@ -68,27 +70,44 @@ public:
|
||||||
virtual void bind();
|
virtual void bind();
|
||||||
virtual void unbind();
|
virtual void unbind();
|
||||||
virtual void update( double dt );
|
virtual void update( double dt );
|
||||||
bool start();
|
bool start(bool NewTape=false);
|
||||||
|
|
||||||
|
bool saveTape(const SGPropertyNode* ConfigData);
|
||||||
|
bool loadTape(const SGPropertyNode* ConfigData);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void clear();
|
void clear();
|
||||||
FGReplayData* record(double time);
|
FGReplayData* record(double time);
|
||||||
void interpolate(double time, const replay_list_type &list);
|
void interpolate(double time, const replay_list_type &list);
|
||||||
void replay(double time, FGReplayData* pCurrentFrame, FGReplayData* pOldFrame=NULL);
|
void replay(double time, FGReplayData* pCurrentFrame, FGReplayData* pOldFrame=NULL);
|
||||||
|
void guiMessage(const char* message);
|
||||||
|
void loadMessages();
|
||||||
|
void fillRecycler();
|
||||||
|
|
||||||
bool replay( double time );
|
bool replay( double time );
|
||||||
|
void replayMessage( double time );
|
||||||
|
|
||||||
double get_start_time();
|
double get_start_time();
|
||||||
double get_end_time();
|
double get_end_time();
|
||||||
|
|
||||||
|
bool listTapes(bool SameAircraftFilter, const SGPath& tapeDirectory);
|
||||||
|
bool saveTape(const char* Filename, SGPropertyNode* MetaData);
|
||||||
|
bool loadTape(const char* Filename, bool Preview, SGPropertyNode* UserData);
|
||||||
|
|
||||||
double sim_time;
|
double sim_time;
|
||||||
double last_mt_time;
|
double last_mt_time;
|
||||||
double last_lt_time;
|
double last_lt_time;
|
||||||
|
double last_msg_time;
|
||||||
|
replay_messages_type::iterator current_msg;
|
||||||
int last_replay_state;
|
int last_replay_state;
|
||||||
|
bool was_finished_already;
|
||||||
|
|
||||||
replay_list_type short_term;
|
replay_list_type short_term;
|
||||||
replay_list_type medium_term;
|
replay_list_type medium_term;
|
||||||
replay_list_type long_term;
|
replay_list_type long_term;
|
||||||
replay_list_type recycler;
|
replay_list_type recycler;
|
||||||
|
replay_messages_type replay_messages;
|
||||||
|
|
||||||
SGPropertyNode_ptr disable_replay;
|
SGPropertyNode_ptr disable_replay;
|
||||||
SGPropertyNode_ptr replay_master;
|
SGPropertyNode_ptr replay_master;
|
||||||
SGPropertyNode_ptr replay_time;
|
SGPropertyNode_ptr replay_time;
|
||||||
|
@ -106,5 +125,4 @@ private:
|
||||||
FGFlightRecorder* m_pRecorder;
|
FGFlightRecorder* m_pRecorder;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#endif // _FG_REPLAY_HXX
|
#endif // _FG_REPLAY_HXX
|
||||||
|
|
|
@ -298,6 +298,30 @@ do_save (const SGPropertyNode * arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Built-in command: save flight recorder tape.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
do_save_tape (const SGPropertyNode * arg)
|
||||||
|
{
|
||||||
|
FGReplay* replay = (FGReplay*) globals->get_subsystem("replay");
|
||||||
|
replay->saveTape(arg);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Built-in command: load flight recorder tape.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
do_load_tape (const SGPropertyNode * arg)
|
||||||
|
{
|
||||||
|
FGReplay* replay = (FGReplay*) globals->get_subsystem("replay");
|
||||||
|
replay->loadTape(arg);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Built-in command: (re)load the panel.
|
* Built-in command: (re)load the panel.
|
||||||
|
@ -1445,6 +1469,8 @@ static struct {
|
||||||
{ "pause", do_pause },
|
{ "pause", do_pause },
|
||||||
{ "load", do_load },
|
{ "load", do_load },
|
||||||
{ "save", do_save },
|
{ "save", do_save },
|
||||||
|
{ "save-tape", do_save_tape },
|
||||||
|
{ "load-tape", do_load_tape },
|
||||||
{ "panel-load", do_panel_load },
|
{ "panel-load", do_panel_load },
|
||||||
{ "preferences-load", do_preferences_load },
|
{ "preferences-load", do_preferences_load },
|
||||||
{ "view-cycle", do_view_cycle },
|
{ "view-cycle", do_view_cycle },
|
||||||
|
|
Loading…
Add table
Reference in a new issue