Improved handling of continuous replay and added recovery system.
Continuous replay: If the user replays a continuous recording from file and then end replay with the 'My controls' or 'End replay' buttons, we now forget about the continuous recording. This enables the usual in-memory record/replay to be used subsequently. Also added a '-continuous' suffix to continuous fgtape filenames. New recovery system: If /sim/replay/recovery-period is set and greater than zero, we periodically save a single-item continuous recording to a recovery file called <aircraft-type>-recovery.fgtape (with no date or time in the name), doing so in such a way as to ensure that there is always a valid recovery file even if flightgear crashes. One can then resume the flight from the most recently-saved point by loading this from within flightgear or with the --load-tape=... option. Also did a fair amount of refactoring and tried to clarify the different property nodes that we embed within recordings.
This commit is contained in:
parent
4e12748b8c
commit
0506a72b30
3 changed files with 268 additions and 142 deletions
|
@ -338,7 +338,7 @@ FGFlightRecorder::capture(double SimTime, FGReplayData* ReplayData)
|
||||||
|
|
||||||
ReplayData->sim_time = SimTime;
|
ReplayData->sim_time = SimTime;
|
||||||
|
|
||||||
if (in_replay) {
|
if (in_replay && !s_recent_raw_data.empty()) {
|
||||||
// Record the fixed position of live user aircraft at the point at
|
// Record the fixed position of live user aircraft at the point at
|
||||||
// which we started replay.
|
// which we started replay.
|
||||||
//
|
//
|
||||||
|
|
|
@ -161,7 +161,6 @@ FGReplay::FGReplay() :
|
||||||
fdm->addChangeListener(this, true /*initial*/);
|
fdm->addChangeListener(this, true /*initial*/);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool saveSetup(const SGPropertyNode* ConfigData, SGPath& p, SGPropertyNode_ptr& myMetaData, double Duration);
|
|
||||||
|
|
||||||
static int PropertiesWrite(SGPropertyNode* root, std::ostream& out)
|
static int PropertiesWrite(SGPropertyNode* root, std::ostream& out)
|
||||||
{
|
{
|
||||||
|
@ -200,6 +199,45 @@ static void popupTip(const char* message, int delay)
|
||||||
globals->get_commands()->execute("show-message", args);
|
globals->get_commands()->execute("show-message", args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum FGTapeType
|
||||||
|
{
|
||||||
|
FGTapeType_NORMAL,
|
||||||
|
FGTapeType_CONTINUOUS,
|
||||||
|
FGTapeType_RECOVERY,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Returns a path using different formats depending on <type>:
|
||||||
|
//
|
||||||
|
// FGTapeType_NORMAL: <tape-directory>/<aircraft-type>-<date>-<time>.fgtape
|
||||||
|
// FGTapeType_CONTINUOUS: <tape-directory>/<aircraft-type>-<date>-<time>-continuous.fgtape
|
||||||
|
// FGTapeType_RECOVERY: <tape-directory>/<aircraft-type>-recovery.fgtape
|
||||||
|
//
|
||||||
|
static SGPath makeSavePath(FGTapeType type)
|
||||||
|
{
|
||||||
|
const char* tapeDirectory = fgGetString("/sim/replay/tape-directory", "");
|
||||||
|
const char* aircraftType = fgGetString("/sim/aircraft", "unknown");
|
||||||
|
|
||||||
|
SGPath path = SGPath(tapeDirectory);
|
||||||
|
path.append(aircraftType);
|
||||||
|
path.concat("-");
|
||||||
|
if (type == FGTapeType_RECOVERY) {
|
||||||
|
path.concat("recovery");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
time_t calendar_time = time(NULL);
|
||||||
|
struct tm *local_tm;
|
||||||
|
local_tm = localtime( &calendar_time );
|
||||||
|
char time_str[256];
|
||||||
|
strftime( time_str, 256, "%Y%m%d-%H%M%S", local_tm);
|
||||||
|
path.concat(time_str);
|
||||||
|
}
|
||||||
|
if (type == FGTapeType_CONTINUOUS) {
|
||||||
|
path.concat("-continuous");
|
||||||
|
}
|
||||||
|
path.concat(".fgtape");
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
void FGReplay::valueChanged(SGPropertyNode * node)
|
void FGReplay::valueChanged(SGPropertyNode * node)
|
||||||
{
|
{
|
||||||
bool prop_continuous = fgGetBool("/sim/replay/record-continuous");
|
bool prop_continuous = fgGetBool("/sim/replay/record-continuous");
|
||||||
|
@ -219,24 +257,16 @@ void FGReplay::valueChanged(SGPropertyNode * node)
|
||||||
|
|
||||||
if (continuous) {
|
if (continuous) {
|
||||||
// Start continuous recording.
|
// Start continuous recording.
|
||||||
SGPath p;
|
SGPropertyNode_ptr MetaData;
|
||||||
SGPropertyNode_ptr myMetaData;
|
SGPropertyNode_ptr Config;
|
||||||
bool ok = saveSetup(NULL, p, myMetaData, 0 /*Duration*/);
|
SGPath path = makeSavePath(FGTapeType_CONTINUOUS);
|
||||||
|
bool ok = continuousWriteHeader(m_continuous_out, MetaData, Config, path);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to start continuous recording");
|
SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to start continuous recording");
|
||||||
popupTip("Continuous record to file failed to start", 5 /*delay*/);
|
popupTip("Continuous record to file failed to start", 5 /*delay*/);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_continuous_out.open(
|
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Starting continuous recording to " << path);
|
||||||
p.c_str(),
|
|
||||||
std::ofstream::binary | std::ofstream::trunc
|
|
||||||
);
|
|
||||||
m_continuous_out.write(FlightRecorderFileMagic, strlen(FlightRecorderFileMagic)+1);
|
|
||||||
PropertiesWrite(myMetaData, m_continuous_out);
|
|
||||||
SGPropertyNode_ptr Config( new SGPropertyNode);
|
|
||||||
m_pRecorder->getConfig(Config.get());
|
|
||||||
PropertiesWrite(Config, m_continuous_out);
|
|
||||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Starting continuous recording to " << p);
|
|
||||||
popupTip("Continuous record to file started", 5 /*delay*/);
|
popupTip("Continuous record to file started", 5 /*delay*/);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -302,6 +332,7 @@ FGReplay::init()
|
||||||
replay_duration_act = fgGetNode("/sim/replay/duration-act", true);
|
replay_duration_act = fgGetNode("/sim/replay/duration-act", true);
|
||||||
speed_up = fgGetNode("/sim/speed-up", true);
|
speed_up = fgGetNode("/sim/speed-up", true);
|
||||||
replay_multiplayer = fgGetNode("/sim/replay/multiplayer", true);
|
replay_multiplayer = fgGetNode("/sim/replay/multiplayer", true);
|
||||||
|
recovery_period = fgGetNode("/sim/replay/recovery-period", true);
|
||||||
|
|
||||||
// alias to keep backward compatibility
|
// alias to keep backward compatibility
|
||||||
fgGetNode("/sim/freeze/replay-state", true)->alias(replay_master);
|
fgGetNode("/sim/freeze/replay-state", true)->alias(replay_master);
|
||||||
|
@ -605,6 +636,135 @@ saveRawReplayData(gzContainerWriter& output, const replay_list_type& ReplayData,
|
||||||
return !output.fail();
|
return !output.fail();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Sets things up for writing to a normal or continuous fgtape file.
|
||||||
|
//
|
||||||
|
// Extra:
|
||||||
|
// NULL or extra information when we are called from fgdata gui, e.g. with
|
||||||
|
// the flight description entered by the user in the save dialogue.
|
||||||
|
// path:
|
||||||
|
// Path of fgtape file. We return nullptr if this file already exists.
|
||||||
|
// Duration:
|
||||||
|
// Duration of recording. Zero if we are starting a continuous recording.
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// A new SGPropertyNode containing meta child with information about the
|
||||||
|
// aircraft etc plus Extra's user-data if specified.
|
||||||
|
//
|
||||||
|
static SGPropertyNode_ptr saveSetup(
|
||||||
|
const SGPropertyNode* Extra,
|
||||||
|
const SGPath& path,
|
||||||
|
double Duration
|
||||||
|
)
|
||||||
|
{
|
||||||
|
SGPropertyNode_ptr MetaData;
|
||||||
|
if (path.exists())
|
||||||
|
{
|
||||||
|
// same timestamp!?
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_ALERT, "Error, flight recorder tape file with same name already exists: " << path);
|
||||||
|
return MetaData;
|
||||||
|
}
|
||||||
|
|
||||||
|
MetaData = new SGPropertyNode();
|
||||||
|
SGPropertyNode* meta = MetaData->getNode("meta", 0, true);
|
||||||
|
|
||||||
|
// add some data to the file - so we know for which aircraft/version it was recorded
|
||||||
|
meta->setStringValue("aircraft-type", fgGetString("/sim/aircraft", "unknown"));
|
||||||
|
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", ""));
|
||||||
|
meta->setStringValue("aircraft-version", fgGetString("/sim/aircraft-version", "(undefined)"));
|
||||||
|
|
||||||
|
// add information on the tape's recording duration
|
||||||
|
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 (Extra && Extra->getNode("user-data"))
|
||||||
|
{
|
||||||
|
copyProperties(Extra->getNode("user-data"), meta->getNode("user-data", 0, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool multiplayer = fgGetBool("/sim/replay/multiplayer", false);
|
||||||
|
meta->setBoolValue("multiplayer", multiplayer);
|
||||||
|
|
||||||
|
// store replay messages
|
||||||
|
copyProperties(fgGetNode("/sim/replay/messages", 0, true), MetaData->getNode("messages", 0, true));
|
||||||
|
|
||||||
|
return MetaData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opens continuous recording file and writes header.
|
||||||
|
//
|
||||||
|
// If MetaData is unset, we initialise it by calling saveSetup(). Otherwise
|
||||||
|
// should be already set up.
|
||||||
|
//
|
||||||
|
// If Config is unset, we make it point to a new node populated by
|
||||||
|
// m_pRecorder->getConfig(). Otherwise it should be already set up to point to
|
||||||
|
// such information.
|
||||||
|
//
|
||||||
|
// If path_override is not "", we use it as the path (instead of the path
|
||||||
|
// determined by saveSetup().
|
||||||
|
//
|
||||||
|
bool
|
||||||
|
FGReplay::continuousWriteHeader(
|
||||||
|
std::ofstream& out,
|
||||||
|
SGPropertyNode_ptr& MetaData,
|
||||||
|
SGPropertyNode_ptr& Config,
|
||||||
|
const SGPath& path
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (!MetaData) {
|
||||||
|
MetaData = saveSetup(NULL /*Extra*/, path, 0 /*Duration*/);
|
||||||
|
if (!MetaData) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!Config) {
|
||||||
|
Config = new SGPropertyNode;
|
||||||
|
m_pRecorder->getConfig(Config.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
out.open(path.c_str(), std::ofstream::binary | std::ofstream::trunc);
|
||||||
|
out.write(FlightRecorderFileMagic, strlen(FlightRecorderFileMagic)+1);
|
||||||
|
PropertiesWrite(MetaData, out);
|
||||||
|
PropertiesWrite(Config, out);
|
||||||
|
if (!out) {
|
||||||
|
out.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Writes one frame of continuous record information.
|
||||||
|
//
|
||||||
|
bool
|
||||||
|
FGReplay::continuousWriteFrame(FGReplayData* r, std::ostream& out)
|
||||||
|
{
|
||||||
|
out.write(reinterpret_cast<char*>(&r->sim_time), sizeof(r->sim_time));
|
||||||
|
|
||||||
|
size_t aircraft_data_size = r->raw_data.size();
|
||||||
|
out.write(reinterpret_cast<char*>(&aircraft_data_size), sizeof(aircraft_data_size));
|
||||||
|
out.write(&r->raw_data.front(), r->raw_data.size());
|
||||||
|
|
||||||
|
size_t multiplayer_num = r->multiplayer_messages.size();
|
||||||
|
out.write(reinterpret_cast<char*>(&multiplayer_num), sizeof(multiplayer_num));
|
||||||
|
for ( size_t i=0; i<multiplayer_num; ++i) {
|
||||||
|
size_t length = r->multiplayer_messages[i]->size();
|
||||||
|
out.write(reinterpret_cast<char*>(&length), sizeof(length));
|
||||||
|
out.write(&r->multiplayer_messages[i]->front(), length);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ok = true;
|
||||||
|
if (!out) ok = false;
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
FGReplay::update( double dt )
|
FGReplay::update( double dt )
|
||||||
{
|
{
|
||||||
|
@ -627,6 +787,7 @@ FGReplay::update( double dt )
|
||||||
(last_replay_state == 3))
|
(last_replay_state == 3))
|
||||||
{
|
{
|
||||||
// disable the replay system
|
// disable the replay system
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_DEBUG, "End replay");
|
||||||
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);
|
||||||
|
@ -639,6 +800,19 @@ FGReplay::update( double dt )
|
||||||
fgSetBool("/sim/sound/enabled",true);
|
fgSetBool("/sim/sound/enabled",true);
|
||||||
fgSetBool("/sim/replay/mute",false);
|
fgSetBool("/sim/replay/mute",false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close any continuous replay file that we have open.
|
||||||
|
//
|
||||||
|
// This allows the user to use the in-memory record/replay system,
|
||||||
|
// instead of replay always showing the continuous recording.
|
||||||
|
//
|
||||||
|
if (m_continuous_in.is_open()) {
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Unloading continuous recording");
|
||||||
|
m_continuous_in.close();
|
||||||
|
m_continuous_time_to_offset.clear();
|
||||||
|
}
|
||||||
|
assert(m_continuous_time_to_offset.empty());
|
||||||
|
|
||||||
guiMessage("Replay stopped. Your controls!");
|
guiMessage("Replay stopped. Your controls!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -775,6 +949,7 @@ FGReplay::update( double dt )
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the short term list
|
// update the short term list
|
||||||
|
assert(r->raw_data.size() != 0);
|
||||||
short_term.push_back( r );
|
short_term.push_back( r );
|
||||||
FGReplayData *st_front = short_term.front();
|
FGReplayData *st_front = short_term.front();
|
||||||
|
|
||||||
|
@ -784,18 +959,48 @@ FGReplay::update( double dt )
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_continuous_out.is_open()) {
|
if (m_continuous_out.is_open()) {
|
||||||
m_continuous_out.write(reinterpret_cast<char*>(&r->sim_time), sizeof(r->sim_time));
|
continuousWriteFrame(r, m_continuous_out);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (replay_state == 0)
|
||||||
|
{
|
||||||
|
// Update recovery tape.
|
||||||
|
//
|
||||||
|
double recovery_period_s = recovery_period->getDoubleValue();
|
||||||
|
if (recovery_period_s > 0) {
|
||||||
|
|
||||||
size_t aircraft_data_size = r->raw_data.size();
|
static time_t s_last_recovery = 0;
|
||||||
m_continuous_out.write(reinterpret_cast<char*>(&aircraft_data_size), sizeof(aircraft_data_size));
|
time_t t = time(NULL);
|
||||||
m_continuous_out.write(&r->raw_data.front(), r->raw_data.size());
|
|
||||||
|
if (t - s_last_recovery >= recovery_period_s) {
|
||||||
size_t multiplayer_num = r->multiplayer_messages.size();
|
s_last_recovery = t;
|
||||||
m_continuous_out.write(reinterpret_cast<char*>(&multiplayer_num), sizeof(multiplayer_num));
|
|
||||||
for ( size_t i=0; i<multiplayer_num; ++i) {
|
// We use static variables here to avoid calculating the same
|
||||||
size_t length = r->multiplayer_messages[i]->size();
|
// data each time we are called.
|
||||||
m_continuous_out.write(reinterpret_cast<char*>(&length), sizeof(length));
|
//
|
||||||
m_continuous_out.write(&r->multiplayer_messages[i]->front(), length);
|
static SGPath path = makeSavePath(FGTapeType_RECOVERY);
|
||||||
|
static SGPath path_temp = SGPath( path.str() + "-");
|
||||||
|
static SGPropertyNode_ptr MetaData;
|
||||||
|
static SGPropertyNode_ptr Config;
|
||||||
|
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_BULK, "Creating recovery file: " << path);
|
||||||
|
// We write to <path_temp> then rename to <path>, which should
|
||||||
|
// guarantee that there is always a valid recovery tape even if
|
||||||
|
// flightgear crashes or is killed while we are writing.
|
||||||
|
//
|
||||||
|
(void) remove(path_temp.c_str());
|
||||||
|
std::ofstream out;
|
||||||
|
bool ok = true;
|
||||||
|
if (ok) ok = continuousWriteHeader(out, MetaData, Config, path_temp);
|
||||||
|
if (ok) ok = continuousWriteFrame(r, out);
|
||||||
|
out.close();
|
||||||
|
if (ok) {
|
||||||
|
rename(path_temp.c_str(), path.c_str());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
popupTip("Failed to update recovery file", 2 /*delay*/);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1179,7 +1384,7 @@ loadRawReplayData(gzContainerReader& input, FGFlightRecorder* pRecorder, replay_
|
||||||
|
|
||||||
/** Write flight recorder tape with given filename and meta properties to disk */
|
/** Write flight recorder tape with given filename and meta properties to disk */
|
||||||
bool
|
bool
|
||||||
FGReplay::saveTape(const SGPath& Filename, SGPropertyNode* MetaDataProps, bool continuous)
|
FGReplay::saveTape(const SGPath& Filename, SGPropertyNode_ptr MetaData)
|
||||||
{
|
{
|
||||||
bool ok = true;
|
bool ok = true;
|
||||||
|
|
||||||
|
@ -1192,10 +1397,9 @@ FGReplay::saveTape(const SGPath& Filename, SGPropertyNode* MetaDataProps, bool c
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "writing MetaDataProps:");
|
SG_LOG(SG_SYSTEMS, SG_DEBUG, "writing MetaData:");
|
||||||
writeProperties(std::cerr, MetaDataProps, true /*write_all*/);
|
|
||||||
/* write meta data **********************************************/
|
/* write meta data **********************************************/
|
||||||
ok &= output.writeContainer(ReplayContainer::MetaData, MetaDataProps);
|
ok &= output.writeContainer(ReplayContainer::MetaData, MetaData.get());
|
||||||
|
|
||||||
/* write flight recorder configuration **************************/
|
/* write flight recorder configuration **************************/
|
||||||
SGPropertyNode_ptr Config;
|
SGPropertyNode_ptr Config;
|
||||||
|
@ -1212,7 +1416,7 @@ FGReplay::saveTape(const SGPath& Filename, SGPropertyNode* MetaDataProps, bool c
|
||||||
size_t RecordSize = Config->getIntValue("recorder/record-size", 0);
|
size_t RecordSize = Config->getIntValue("recorder/record-size", 0);
|
||||||
SG_LOG(SG_SYSTEMS, MY_SG_DEBUG, "Config:recorder/signal-count=" << Config->getIntValue("recorder/signal-count", 0)
|
SG_LOG(SG_SYSTEMS, MY_SG_DEBUG, "Config:recorder/signal-count=" << Config->getIntValue("recorder/signal-count", 0)
|
||||||
<< " RecordSize: " << RecordSize);
|
<< " RecordSize: " << RecordSize);
|
||||||
bool multiplayer = MetaDataProps->getBoolValue("meta/multiplayer", 0);
|
bool multiplayer = MetaData->getBoolValue("meta/multiplayer", 0);
|
||||||
|
|
||||||
if (ok)
|
if (ok)
|
||||||
ok &= saveRawReplayData(output, short_term, RecordSize, multiplayer);
|
ok &= saveRawReplayData(output, short_term, RecordSize, multiplayer);
|
||||||
|
@ -1229,115 +1433,31 @@ FGReplay::saveTape(const SGPath& Filename, SGPropertyNode* MetaDataProps, bool c
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets things up for writing to a normal or continuous fgtape file.
|
|
||||||
//
|
|
||||||
// On exit:
|
|
||||||
//
|
|
||||||
// p: Contains path of the fgtape file.
|
|
||||||
//
|
|
||||||
// myMetaData: points to a new SGPropertyNode containing information on
|
|
||||||
// aircraft type etc.
|
|
||||||
//
|
|
||||||
static bool saveSetup(const SGPropertyNode* ConfigData, SGPath& p, SGPropertyNode_ptr& myMetaData, double Duration)
|
|
||||||
{
|
|
||||||
const char* tapeDirectory = fgGetString("/sim/replay/tape-directory", "");
|
|
||||||
const char* aircraftType = fgGetString("/sim/aircraft", "unknown");
|
|
||||||
|
|
||||||
myMetaData = new SGPropertyNode();
|
|
||||||
SGPropertyNode* meta = myMetaData->getNode("meta", 0, true);
|
|
||||||
|
|
||||||
SG_LOG(SG_SYSTEMS, SG_BULK,
|
|
||||||
" ConfigData=" << ConfigData
|
|
||||||
<< " tapeDirectory=" << tapeDirectory
|
|
||||||
<< " aircraftType=" << aircraftType
|
|
||||||
);
|
|
||||||
// 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 = "(undefined)";
|
|
||||||
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 && ConfigData->getNode("user-data"))
|
|
||||||
{
|
|
||||||
copyProperties(ConfigData->getNode("user-data"), meta->getNode("user-data", 0, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool multiplayer = fgGetBool("/sim/replay/multiplayer", false);
|
|
||||||
meta->setBoolValue("multiplayer", multiplayer);
|
|
||||||
|
|
||||||
// store replay messages
|
|
||||||
copyProperties(fgGetNode("/sim/replay/messages", 0, true), myMetaData->getNode("messages", 0, true));
|
|
||||||
|
|
||||||
// generate file name (directory + aircraft type + date + time + suffix)
|
|
||||||
p = SGPath(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%m%d-%H%M%S", 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;
|
|
||||||
}
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Write flight recorder tape to disk. User/script command. */
|
/** Write flight recorder tape to disk. User/script command. */
|
||||||
bool
|
bool
|
||||||
FGReplay::saveTape(const SGPropertyNode* ConfigData, bool continuous)
|
FGReplay::saveTape(const SGPropertyNode* Extra)
|
||||||
{
|
{
|
||||||
SGPath p;
|
SGPath path = makeSavePath(FGTapeType_NORMAL);
|
||||||
SGPropertyNode_ptr myMetaData;
|
SGPropertyNode_ptr MetaData = saveSetup(Extra, path, get_end_time()-get_start_time());
|
||||||
|
bool ok = false;
|
||||||
bool ok = saveSetup(ConfigData, p, myMetaData, get_end_time()-get_start_time());
|
if (MetaData) {
|
||||||
|
ok = saveTape(path, MetaData);
|
||||||
|
}
|
||||||
if (ok)
|
if (ok)
|
||||||
ok &= saveTape(p, myMetaData.get(), continuous);
|
guiMessage("Flight recorder tape saved successfully!");
|
||||||
|
else
|
||||||
if (continuous) {
|
guiMessage("Failed to save tape! See log output.");
|
||||||
if (ok)
|
|
||||||
guiMessage("Flight recorder continuous started successfully!");
|
|
||||||
else
|
|
||||||
guiMessage("Flight recorder continuous start failed. See log output.");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (ok)
|
|
||||||
guiMessage("Flight recorder tape saved successfully!");
|
|
||||||
else
|
|
||||||
guiMessage("Failed to save tape! See log output.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** Read a flight recorder tape with given filename from disk and return meta properties.
|
/** Read a flight recorder tape with given filename from disk.
|
||||||
|
* Copies MetaData's "meta" node into MetaMeta out-param.
|
||||||
* Actual data and signal configuration is not read when in "Preview" mode.
|
* Actual data and signal configuration is not read when in "Preview" mode.
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
FGReplay::loadTape(const SGPath& Filename, bool Preview, SGPropertyNode* UserData)
|
FGReplay::loadTape(const SGPath& Filename, bool Preview, SGPropertyNode& MetaMeta)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
/* Try to load as uncompressed first. */
|
/* Try to load as uncompressed first. */
|
||||||
|
@ -1348,12 +1468,10 @@ FGReplay::loadTape(const SGPath& Filename, bool Preview, SGPropertyNode* UserDat
|
||||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "fgtape prefix doesn't match FlightRecorderFileMagic: " << Filename);
|
SG_LOG(SG_SYSTEMS, SG_DEBUG, "fgtape prefix doesn't match FlightRecorderFileMagic: " << Filename);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "fgtap is uncompressed: " << Filename);
|
SG_LOG(SG_SYSTEMS, SG_DEBUG, "fgtape is uncompressed: " << Filename);
|
||||||
SGPropertyNode_ptr MetaDataProps = new SGPropertyNode();
|
SGPropertyNode_ptr MetaDataProps = new SGPropertyNode();
|
||||||
PropertiesRead(m_continuous_in, MetaDataProps.get());
|
PropertiesRead(m_continuous_in, MetaDataProps.get());
|
||||||
if (UserData) {
|
copyProperties(MetaDataProps->getNode("meta", 0, true), &MetaMeta);
|
||||||
copyProperties(MetaDataProps->getNode("meta", 0, true), UserData);
|
|
||||||
}
|
|
||||||
SGPropertyNode_ptr Config = new SGPropertyNode();
|
SGPropertyNode_ptr Config = new SGPropertyNode();
|
||||||
PropertiesRead(m_continuous_in, Config.get());
|
PropertiesRead(m_continuous_in, Config.get());
|
||||||
|
|
||||||
|
@ -1364,6 +1482,7 @@ FGReplay::loadTape(const SGPath& Filename, bool Preview, SGPropertyNode* UserDat
|
||||||
|
|
||||||
m_pRecorder->reinit(Config);
|
m_pRecorder->reinit(Config);
|
||||||
clear();
|
clear();
|
||||||
|
fillRecycler();
|
||||||
time_t t = time(NULL);
|
time_t t = time(NULL);
|
||||||
size_t pos = 0;
|
size_t pos = 0;
|
||||||
for(;;)
|
for(;;)
|
||||||
|
@ -1410,9 +1529,7 @@ FGReplay::loadTape(const SGPath& Filename, bool Preview, SGPropertyNode* UserDat
|
||||||
<< ". recording size: " << pos
|
<< ". recording size: " << pos
|
||||||
<< ". numrecording items: " << m_continuous_time_to_offset.size()
|
<< ". numrecording items: " << m_continuous_time_to_offset.size()
|
||||||
);
|
);
|
||||||
sim_time = get_end_time();
|
start(true /*NewTape*/);
|
||||||
m_pRecorder->reinit(Config);
|
|
||||||
start(true);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1454,7 +1571,7 @@ FGReplay::loadTape(const SGPath& Filename, bool Preview, SGPropertyNode* UserDat
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
readProperties(MetaData, Size-1, MetaDataProps);
|
readProperties(MetaData, Size-1, MetaDataProps);
|
||||||
copyProperties(MetaDataProps->getNode("meta", 0, true), UserData);
|
copyProperties(MetaDataProps->getNode("meta", 0, true), &MetaMeta);
|
||||||
} catch (const sg_exception &e)
|
} catch (const sg_exception &e)
|
||||||
{
|
{
|
||||||
SG_LOG(SG_SYSTEMS, SG_ALERT, "Error reading flight recorder tape: " << Filename
|
SG_LOG(SG_SYSTEMS, SG_ALERT, "Error reading flight recorder tape: " << Filename
|
||||||
|
@ -1536,7 +1653,7 @@ FGReplay::loadTape(const SGPath& Filename, bool Preview, SGPropertyNode* UserDat
|
||||||
<< ", expected size was " << OriginalSize << ".");
|
<< ", expected size was " << OriginalSize << ".");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool multiplayer = UserData->getBoolValue("multiplayer", 0);
|
bool multiplayer = MetaMeta.getBoolValue("multiplayer", 0);
|
||||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "multiplayer=" << multiplayer);
|
SG_LOG(SG_SYSTEMS, SG_DEBUG, "multiplayer=" << multiplayer);
|
||||||
|
|
||||||
if (ok)
|
if (ok)
|
||||||
|
@ -1625,11 +1742,11 @@ FGReplay::loadTape(const SGPropertyNode* ConfigData)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SGPropertyNode* UserData = fgGetNode("/sim/gui/dialogs/flightrecorder/preview", true);
|
SGPropertyNode* MetaMeta = fgGetNode("/sim/gui/dialogs/flightrecorder/preview", true);
|
||||||
tapeDirectory.append(tape);
|
tapeDirectory.append(tape);
|
||||||
tapeDirectory.concat(".fgtape");
|
tapeDirectory.concat(".fgtape");
|
||||||
SG_LOG(SG_SYSTEMS, MY_SG_DEBUG, "Checking flight recorder file " << tapeDirectory << ", preview: " << Preview);
|
SG_LOG(SG_SYSTEMS, MY_SG_DEBUG, "Checking flight recorder file " << tapeDirectory << ", preview: " << Preview);
|
||||||
return loadTape(tapeDirectory, Preview, UserData);
|
return loadTape(tapeDirectory, Preview, *MetaMeta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -107,7 +107,7 @@ public:
|
||||||
|
|
||||||
bool start(bool NewTape=false);
|
bool start(bool NewTape=false);
|
||||||
|
|
||||||
bool saveTape(const SGPropertyNode* ConfigData, bool continuous=false);
|
bool saveTape(const SGPropertyNode* ConfigData);
|
||||||
bool loadTape(const SGPropertyNode* ConfigData);
|
bool loadTape(const SGPropertyNode* ConfigData);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -127,8 +127,15 @@ private:
|
||||||
double get_end_time();
|
double get_end_time();
|
||||||
|
|
||||||
bool listTapes(bool SameAircraftFilter, const SGPath& tapeDirectory);
|
bool listTapes(bool SameAircraftFilter, const SGPath& tapeDirectory);
|
||||||
bool saveTape(const SGPath& Filename, SGPropertyNode* MetaData, bool continuous=false);
|
bool saveTape(const SGPath& Filename, SGPropertyNode_ptr MetaData);
|
||||||
bool loadTape(const SGPath& Filename, bool Preview, SGPropertyNode* UserData);
|
bool loadTape(const SGPath& Filename, bool Preview, SGPropertyNode& MetaMeta);
|
||||||
|
bool continuousWriteHeader(
|
||||||
|
std::ofstream& out,
|
||||||
|
SGPropertyNode_ptr& myMetaData,
|
||||||
|
SGPropertyNode_ptr& Config,
|
||||||
|
const SGPath& path
|
||||||
|
);
|
||||||
|
bool continuousWriteFrame(FGReplayData* r, std::ostream& out);
|
||||||
|
|
||||||
double sim_time;
|
double sim_time;
|
||||||
double last_mt_time;
|
double last_mt_time;
|
||||||
|
@ -152,6 +159,8 @@ private:
|
||||||
SGPropertyNode_ptr replay_duration_act;
|
SGPropertyNode_ptr replay_duration_act;
|
||||||
SGPropertyNode_ptr speed_up;
|
SGPropertyNode_ptr speed_up;
|
||||||
SGPropertyNode_ptr replay_multiplayer;
|
SGPropertyNode_ptr replay_multiplayer;
|
||||||
|
SGPropertyNode_ptr recovery_period;
|
||||||
|
|
||||||
double replay_time_prev; // Used to detect jumps while replaying.
|
double replay_time_prev; // Used to detect jumps while replaying.
|
||||||
|
|
||||||
double m_high_res_time; // default: 60 secs of high res data
|
double m_high_res_time; // default: 60 secs of high res data
|
||||||
|
|
Loading…
Add table
Reference in a new issue