1
0
Fork 0

Various fixes and improvements to replaying of Continuous recordings.

FGFlightRecorder::replay():
    Fixed behaviour when going back in time while replaying - we were not
    replaying initial property changes correctly because we deliberately
    don't load signals data for some frames when we are only interested in
    multiplayer or extra properties.

    Removed incorrect code that tried to avoid replaying the same frame twice
    in succession.

FGReplay::update(double dt):
    Improved how we figure out which frames we need to replay just for multiplayer
    or extra property changes.

FGReplay::makeTapePath():
    New fn containing the code for converting tape name into path by prepending
    tape dir and appending .fgtape as necessary. Called when loading tape at
    startup or at runtime.

Cached some more property nodes in SGPropertyNode_ptr's.

Don't recordCHAT_MSG_ID messages; unfortunately it looks like most (all?)
chat message text is received as part of POS_DATA_ID messages tied to
sim/multiplay/chat, so this doesn't actually avoid recording chat messages.
This commit is contained in:
Julian Smith 2021-02-22 23:16:26 +00:00
parent 538e32d555
commit 4553d813b1
6 changed files with 424 additions and 349 deletions

View file

@ -222,10 +222,14 @@ static std::shared_ptr<RecordExtraProperties> s_record_extra_properties;
FGFlightRecorder::FGFlightRecorder(const char* pConfigName) : FGFlightRecorder::FGFlightRecorder(const char* pConfigName) :
m_RecorderNode(fgGetNode("/sim/flight-recorder", true)), m_RecorderNode (fgGetNode("/sim/flight-recorder", true)),
m_ReplayMultiplayer(fgGetNode("/sim/replay/multiplayer", true)), m_ReplayMultiplayer (fgGetNode("/sim/replay/multiplayer", true)),
m_RecordContinuous(fgGetNode("/sim/replay/record-continuous", true)), m_ReplayExtraProperties (fgGetNode("/sim/replay/replay-extra-properties", true)),
m_RecordExtraProperties(fgGetNode("/sim/replay/record-extra-properties", true)), m_ReplayMainView (fgGetNode("/sim/replay/replay-main-view", true)),
m_ReplayMainWindowPosition (fgGetNode("/sim/replay/replay-main-window-position", true)),
m_ReplayMainWindowSize (fgGetNode("/sim/replay/replay-main-window-size", true)),
m_RecordContinuous (fgGetNode("/sim/replay/record-continuous", true)),
m_RecordExtraProperties (fgGetNode("/sim/replay/record-extra-properties", true)),
m_TotalRecordSize(0), m_TotalRecordSize(0),
m_ConfigName(pConfigName), m_ConfigName(pConfigName),
m_usingDefaultConfig(false), m_usingDefaultConfig(false),
@ -624,12 +628,29 @@ FGFlightRecorder::capture(double SimTime, FGReplayData* ReplayData)
ReplayData->multiplayer_messages.clear(); ReplayData->multiplayer_messages.clear();
bool replayMultiplayer = m_ReplayMultiplayer->getBoolValue(); bool replayMultiplayer = m_ReplayMultiplayer->getBoolValue();
for(;;) { for(;;) {
auto MultiplayerMessage = m_MultiplayMgr->popMessageHistory(); auto multiplayerMessage = m_MultiplayMgr->popMessageHistory();
if (!MultiplayerMessage) { if (!multiplayerMessage) {
break; break;
} }
if (replayMultiplayer) { if (replayMultiplayer) {
ReplayData->multiplayer_messages.push_back( MultiplayerMessage); // Attempt to ignore chat messages. Unfortunately it seems
// that CHAT_MSG_ID is unused, and instead chat messages are
// included in POS_DATA_ID messages as sim/multiplay/chat in
// src/MultiPlayer/multiplaymgr.cxx's sIdPropertyList.
//
auto message_header = reinterpret_cast<const T_MsgHdr*>(&multiplayerMessage->front());
xdr_data_t message_id = message_header->MsgId;
if (message_id == CHAT_MSG_ID) {
SG_LOG(SG_GENERAL, SG_ALERT, "Not recording chat message: "
<< std::string(
reinterpret_cast<const T_ChatMsg*>(message_header + 1)->Text,
message_header->MsgLen - sizeof(*message_header)
)
);
}
else {
ReplayData->multiplayer_messages.push_back( multiplayerMessage);
}
} }
} }
@ -637,7 +658,7 @@ FGFlightRecorder::capture(double SimTime, FGReplayData* ReplayData)
// //
if (m_RecordContinuous->getBoolValue()) { if (m_RecordContinuous->getBoolValue()) {
if (!m_RecordExtraPropertiesReference) { if (!m_RecordExtraPropertiesReference) {
SG_LOG(SG_SYSTEMS, SG_ALERT, "m_RecordPropertiesReference is null"); SG_LOG(SG_SYSTEMS, SG_DEBUG, "m_RecordPropertiesReference is null");
m_RecordExtraPropertiesReference = new SGPropertyNode; m_RecordExtraPropertiesReference = new SGPropertyNode;
} }
s_record_extra_properties->capture(m_RecordExtraPropertiesReference, ReplayData); s_record_extra_properties->capture(m_RecordExtraPropertiesReference, ReplayData);
@ -698,7 +719,7 @@ weighting(TInterpolation interpolation, double ratio, double v1,double v2)
void void
FGFlightRecorder::resetExtraProperties() FGFlightRecorder::resetExtraProperties()
{ {
SG_LOG(SG_SYSTEMS, SG_ALERT, "Clearing m_RecordExtraPropertiesReference"); SG_LOG(SG_SYSTEMS, SG_DEBUG, "Clearing m_RecordExtraPropertiesReference");
m_RecordExtraPropertiesReference = nullptr; m_RecordExtraPropertiesReference = nullptr;
} }
@ -726,135 +747,128 @@ FGFlightRecorder::replay(double SimTime, const FGReplayData* _pNextBuffer,
int* main_window_ysize int* main_window_ysize
) )
{ {
const char* pLastBuffer = (_pLastBuffer) ? &_pLastBuffer->raw_data.front() : nullptr; const char* pLastBuffer = (_pLastBuffer && !_pLastBuffer->raw_data.empty()) ? &_pLastBuffer->raw_data.front() : nullptr;
const char* pBuffer = (_pNextBuffer) ? &_pNextBuffer->raw_data.front() : nullptr; const char* pBuffer = (_pNextBuffer && !_pNextBuffer->raw_data.empty()) ? &_pNextBuffer->raw_data.front() : nullptr;
if (!pBuffer)
return; if (pBuffer) {
/* Replay signals. */
int Offset = 0; int Offset = 0;
double ratio = 1.0; double ratio = 1.0;
if (pLastBuffer) if (pLastBuffer)
{
double NextSimTime = _pNextBuffer->sim_time;
double LastSimTime = _pLastBuffer->sim_time;
double Numerator = SimTime - LastSimTime;
double dt = NextSimTime - LastSimTime;
// avoid divide by zero and other quirks
if ((Numerator > 0.0)&&(dt != 0.0))
{ {
ratio = Numerator / dt; double NextSimTime = _pNextBuffer->sim_time;
if (ratio > 1.0) double LastSimTime = _pLastBuffer->sim_time;
ratio = 1.0; double Numerator = SimTime - LastSimTime;
} double dt = NextSimTime - LastSimTime;
} // avoid divide by zero and other quirks
if ((Numerator > 0.0)&&(dt != 0.0))
// 64bit aligned data first!
{
// restore doubles
const double* pDoubles = (const double*) &pBuffer[Offset];
const double* pLastDoubles = (const double*) &pLastBuffer[Offset];
unsigned int SignalCount = m_CaptureDouble.size();
for (unsigned int i=0; i<SignalCount; i++)
{
double v = pDoubles[i];
if (pLastBuffer)
{ {
v = weighting(m_CaptureDouble[i].Interpolation, ratio, ratio = Numerator / dt;
pLastDoubles[i], v); if (ratio > 1.0)
ratio = 1.0;
} }
m_CaptureDouble[i].Signal->setDoubleValue(v);
} }
Offset += SignalCount * sizeof(double);
}
// 32bit aligned data comes second... // 64bit aligned data first!
{
// restore floats
const float* pFloats = (const float*) &pBuffer[Offset];
const float* pLastFloats = (const float*) &pLastBuffer[Offset];
unsigned int SignalCount = m_CaptureFloat.size();
for (unsigned int i=0; i<SignalCount; i++)
{ {
float v = pFloats[i]; // restore doubles
if (pLastBuffer) const double* pDoubles = (const double*) &pBuffer[Offset];
const double* pLastDoubles = (const double*) &pLastBuffer[Offset];
unsigned int SignalCount = m_CaptureDouble.size();
for (unsigned int i=0; i<SignalCount; i++)
{ {
v = weighting(m_CaptureFloat[i].Interpolation, ratio, double v = pDoubles[i];
pLastFloats[i], v); if (pLastBuffer)
{
v = weighting(m_CaptureDouble[i].Interpolation, ratio,
pLastDoubles[i], v);
}
m_CaptureDouble[i].Signal->setDoubleValue(v);
} }
m_CaptureFloat[i].Signal->setDoubleValue(v);//setFloatValue Offset += SignalCount * sizeof(double);
} }
Offset += SignalCount * sizeof(float);
}
{ // 32bit aligned data comes second...
// restore integers (32bit aligned)
const int* pInt = (const int*) &pBuffer[Offset];
unsigned int SignalCount = m_CaptureInteger.size();
for (unsigned int i=0; i<SignalCount; i++)
{ {
m_CaptureInteger[i].Signal->setIntValue(pInt[i]); // restore floats
const float* pFloats = (const float*) &pBuffer[Offset];
const float* pLastFloats = (const float*) &pLastBuffer[Offset];
unsigned int SignalCount = m_CaptureFloat.size();
for (unsigned int i=0; i<SignalCount; i++)
{
float v = pFloats[i];
if (pLastBuffer)
{
v = weighting(m_CaptureFloat[i].Interpolation, ratio,
pLastFloats[i], v);
}
m_CaptureFloat[i].Signal->setDoubleValue(v);//setFloatValue
}
Offset += SignalCount * sizeof(float);
} }
Offset += SignalCount * sizeof(int);
}
// 16bit aligned data is next...
{
// restore 16bit short integers
const short int* pShortInt = (const short int*) &pBuffer[Offset];
unsigned int SignalCount = m_CaptureInt16.size();
for (unsigned int i=0; i<SignalCount; i++)
{ {
m_CaptureInt16[i].Signal->setIntValue(pShortInt[i]); // restore integers (32bit aligned)
const int* pInt = (const int*) &pBuffer[Offset];
unsigned int SignalCount = m_CaptureInteger.size();
for (unsigned int i=0; i<SignalCount; i++)
{
m_CaptureInteger[i].Signal->setIntValue(pInt[i]);
}
Offset += SignalCount * sizeof(int);
} }
Offset += SignalCount * sizeof(short int);
}
// finally: byte aligned data is last... // 16bit aligned data is next...
{
// restore 8bit chars
const signed char* pChar = (const signed char*) &pBuffer[Offset];
unsigned int SignalCount = m_CaptureInt8.size();
for (unsigned int i=0; i<SignalCount; i++)
{ {
m_CaptureInt8[i].Signal->setIntValue(pChar[i]); // restore 16bit short integers
const short int* pShortInt = (const short int*) &pBuffer[Offset];
unsigned int SignalCount = m_CaptureInt16.size();
for (unsigned int i=0; i<SignalCount; i++)
{
m_CaptureInt16[i].Signal->setIntValue(pShortInt[i]);
}
Offset += SignalCount * sizeof(short int);
} }
Offset += SignalCount * sizeof(signed char);
}
{ // finally: byte aligned data is last...
// restore 1bit booleans (8bit aligned)
const unsigned char* pFlags = (const unsigned char*) &pBuffer[Offset];
unsigned int SignalCount = m_CaptureBool.size();
int Size = (SignalCount+7)/8;
Offset += Size;
for (unsigned int i=0; i<SignalCount; i++)
{ {
m_CaptureBool[i].Signal->setBoolValue(0 != (pFlags[i>>3] & (1 << (i&7)))); // restore 8bit chars
const signed char* pChar = (const signed char*) &pBuffer[Offset];
unsigned int SignalCount = m_CaptureInt8.size();
for (unsigned int i=0; i<SignalCount; i++)
{
m_CaptureInt8[i].Signal->setIntValue(pChar[i]);
}
Offset += SignalCount * sizeof(signed char);
}
{
// restore 1bit booleans (8bit aligned)
const unsigned char* pFlags = (const unsigned char*) &pBuffer[Offset];
unsigned int SignalCount = m_CaptureBool.size();
int Size = (SignalCount+7)/8;
Offset += Size;
for (unsigned int i=0; i<SignalCount; i++)
{
m_CaptureBool[i].Signal->setBoolValue(0 != (pFlags[i>>3] & (1 << (i&7))));
}
} }
} }
// Replay any multiplayer messages. But don't send the same multiplayer // Replay any multiplayer messages.
// messages repeatedly when we are called with a timestamp that ends up for (auto multiplayer_message: _pNextBuffer->multiplayer_messages) {
// picking the same _pNextBuffer as last time. SG_LOG(SG_SYSTEMS, SG_DEBUG, "Pushing multiplayer message to multiplay manager");
m_MultiplayMgr->pushMessageHistory(multiplayer_message);
}
// Replay extra property changes.
// //
static const FGReplayData* _pNextBuffer_prev = nullptr; bool replay_extra_properties = m_ReplayExtraProperties->getBoolValue();
if ( _pNextBuffer != _pNextBuffer_prev) { bool replay_main_view = m_ReplayMainView->getBoolValue();
_pNextBuffer_prev = _pNextBuffer; bool replay_main_window_position = m_ReplayMainWindowPosition->getBoolValue();
for (auto multiplayer_message: _pNextBuffer->multiplayer_messages) { bool replay_main_window_size = m_ReplayMainWindowSize->getBoolValue();
m_MultiplayMgr->pushMessageHistory(multiplayer_message);
}
}
// Replay property changes. if (replay_extra_properties) {
//
bool replay_extra_property_removal = globals->get_props()->getBoolValue("sim/replay/replay-extra-property-removal");
bool replay_extra_property_changes = globals->get_props()->getBoolValue("sim/replay/replay-extra-property-changes");
bool replay_main_view = globals->get_props()->getBoolValue("sim/replay/replay-main-view");
bool replay_main_window_position = globals->get_props()->getBoolValue("sim/replay/replay-main-window-position");
bool replay_main_window_size = globals->get_props()->getBoolValue("sim/replay/replay-main-window-size");
if (replay_extra_property_removal) {
for (auto extra_property_removed_path: _pNextBuffer->replay_extra_property_removals) { for (auto extra_property_removed_path: _pNextBuffer->replay_extra_property_removals) {
SG_LOG(SG_SYSTEMS, SG_DEBUG, "replaying extra property removal: " << extra_property_removed_path); SG_LOG(SG_SYSTEMS, SG_DEBUG, "replaying extra property removal: " << extra_property_removed_path);
globals->get_props()->removeChild(extra_property_removed_path); globals->get_props()->removeChild(extra_property_removed_path);
@ -875,7 +889,7 @@ FGFlightRecorder::replay(double SimTime, const FGReplayData* _pNextBuffer,
const std::string& path = prop_change.first; const std::string& path = prop_change.first;
const std::string& value = prop_change.second; const std::string& value = prop_change.second;
if (simgear::strutils::starts_with(path, "/sim/current-view/view-number")) { if (simgear::strutils::starts_with(path, "/sim/current-view/view-number")) {
SG_LOG(SG_SYSTEMS, SG_DEBUG, "*** SimTime=" << SimTime << " replaying view " << path << "=" << value); SG_LOG(SG_SYSTEMS, SG_DEBUG, "SimTime=" << SimTime << " replaying view " << path << "=" << value);
globals->get_props()->setStringValue(path, value); globals->get_props()->setStringValue(path, value);
} }
} }
@ -905,7 +919,7 @@ FGFlightRecorder::replay(double SimTime, const FGReplayData* _pNextBuffer,
globals->get_props()->setStringValue(path, value); globals->get_props()->setStringValue(path, value);
} }
} }
else if (replay_extra_property_changes) { else if (replay_extra_properties) {
SG_LOG(SG_SYSTEMS, SG_DEBUG, "SimTime=" << SimTime SG_LOG(SG_SYSTEMS, SG_DEBUG, "SimTime=" << SimTime
<< " replaying extra_property change: " << path << "=" << value); << " replaying extra_property change: " << path << "=" << value);
globals->get_props()->setStringValue(path, value); globals->get_props()->setStringValue(path, value);

View file

@ -85,7 +85,13 @@ private:
SGPropertyNode_ptr m_RecorderNode; SGPropertyNode_ptr m_RecorderNode;
SGPropertyNode_ptr m_ConfigNode; SGPropertyNode_ptr m_ConfigNode;
SGPropertyNode_ptr m_ReplayMultiplayer; SGPropertyNode_ptr m_ReplayMultiplayer;
SGPropertyNode_ptr m_ReplayExtraProperties;
SGPropertyNode_ptr m_ReplayMainView;
SGPropertyNode_ptr m_ReplayMainWindowPosition;
SGPropertyNode_ptr m_ReplayMainWindowSize;
SGPropertyNode_ptr m_RecordContinuous; SGPropertyNode_ptr m_RecordContinuous;
SGPropertyNode_ptr m_RecordExtraProperties; SGPropertyNode_ptr m_RecordExtraProperties;

View file

@ -739,7 +739,7 @@ static SGPropertyNode_ptr saveSetup(
|| fgGetBool("/sim/replay/record-main-window", false) || fgGetBool("/sim/replay/record-main-window", false)
|| fgGetBool("/sim/replay/record-main-view", false) || fgGetBool("/sim/replay/record-main-view", false)
) { ) {
SG_LOG(SG_SYSTEMS, SG_ALERT, "Adding data[]=extra-properties." SG_LOG(SG_SYSTEMS, SG_DEBUG, "Adding data[]=extra-properties."
<< " record-extra-properties=" << fgGetBool("/sim/replay/record-extra-properties", false) << " record-extra-properties=" << fgGetBool("/sim/replay/record-extra-properties", false)
<< " record-main-window=" << fgGetBool("/sim/replay/record-main-window", false) << " record-main-window=" << fgGetBool("/sim/replay/record-main-window", false)
<< " record-main-view=" << fgGetBool("/sim/replay/record-main-view", false) << " record-main-view=" << fgGetBool("/sim/replay/record-main-view", false)
@ -852,6 +852,15 @@ struct FGFrameInfo
bool has_extra_properties = false; bool has_extra_properties = false;
}; };
std::ostream& operator << (std::ostream& out, const FGFrameInfo& frame_info)
{
return out << "{"
<< " offset=" << frame_info.offset
<< " has_multiplayer=" << frame_info.has_multiplayer
<< " has_extra_properties=" << frame_info.has_extra_properties
<< "}";
}
void void
FGReplay::update( double dt ) FGReplay::update( double dt )
{ {
@ -1257,69 +1266,44 @@ FGReplay::replay( double time ) {
int xsize = xsize0; int xsize = xsize0;
int ysize = ysize0; int ysize = ysize0;
double t_begin = m_continuous_in_time_last; double multiplayer_recent = 3;
if (m_continuous_in_extra_properties) {
// Continuous recording has property changes. // We replay all frames from just after the previously-replayed frame,
// in order to replay extra properties and multiplayer aircraft
// correctly.
//
double t_begin = m_continuous_in_frame_time_last;
if (time < m_continuous_in_time_last) {
// We have gone backwards, e.g. user has clicked on the back
// buttons in the Replay dialogue.
// //
if (time < m_continuous_in_time_last) {
// We have gone backwards; need to replay all property changes if (m_continuous_in_multiplayer) {
// from t=0. // Continuous recording has multiplayer data, so replay recent
// ones.
//
t_begin = time - multiplayer_recent;
}
if (m_continuous_in_extra_properties) {
// Continuous recording has property changes. we need to replay
// all property changes from the beginning.
//
t_begin = -1; t_begin = -1;
} }
}
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Have gone backwards."
double multiplayer_recent = 3;
if (m_continuous_in_multiplayer) {
double t = std::max(0.0, time - multiplayer_recent);
t_begin = std::min(t_begin, t);
}
// Replay property changes for all t in t_prop_begin < t < time, and multiplayer
// changes for most recent multiplayer_recent seconds.
//
for (auto p = m_continuous_in_time_to_frameinfo.upper_bound(t_begin);
p != m_continuous_in_time_to_frameinfo.end();
++p)
{
if (p->first >= time) break;
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Replaying extra property changes."
<< " m_continuous_in_time_last=" << m_continuous_in_time_last << " m_continuous_in_time_last=" << m_continuous_in_time_last
<< " time=" << time << " time=" << time
<< " t_begin=" << t_begin << " t_begin=" << t_begin
<< " p->first=" << p->first << " m_continuous_in_extra_properties=" << m_continuous_in_extra_properties
<< " p->second.offset=" << p->second.offset
); );
// Replaying a frame is expensive because we read frame data
// from disc each time. So we only replay this frame if it has
// extra_properties, or if it has multiplayer packets and we are
// within <multiplayer_recent> seconds of current time.
//
bool replay_this_frame = p->second.has_extra_properties;
if (!replay_this_frame) {
if (p->first > time - multiplayer_recent) {
if (p->second.has_multiplayer) {
replay_this_frame = true;
}
}
}
if (replay_this_frame) {
replay(
p->first,
p->second.offset,
0 /*offset_old*/,
false /*replay_signals*/,
p->first > time - multiplayer_recent /*replay_multiplayer*/,
true /*replay_extra_properties*/,
&xpos,
&ypos,
&xsize,
&ysize
);
}
} }
// Replay from uncompressed recording file. // Prepare to replay signals from uncompressed recording file. We want
// to find a pair of frames that straddle the requested <time> so that
// we can interpolate.
// //
auto p = m_continuous_in_time_to_frameinfo.lower_bound(time); auto p = m_continuous_in_time_to_frameinfo.lower_bound(time);
bool ret = false; bool ret = false;
@ -1340,7 +1324,7 @@ FGReplay::replay( double time ) {
offset = p->second.offset; offset = p->second.offset;
} }
else { else {
// Interpolate between items that straddle <time>. // Interpolate between pair of items that straddle <time>.
auto prev = p; auto prev = p;
--prev; --prev;
offset_prev = prev->second.offset; offset_prev = prev->second.offset;
@ -1351,6 +1335,56 @@ FGReplay::replay( double time ) {
// Exact match. // Exact match.
offset = p->second.offset; offset = p->second.offset;
} }
// Before interpolating signals, we replay all property changes from
// all frame times t satisfying t_prop_begin < t < time. We also replay
// all recent multiplayer packets in this range, i.e. for which t >
// time - multiplayer_recent.
//
for (auto p_before = m_continuous_in_time_to_frameinfo.upper_bound(t_begin);
p_before != m_continuous_in_time_to_frameinfo.end();
++p_before) {
if (p_before->first >= p->first) {
break;
}
// Replaying a frame is expensive because we read frame data
// from disc each time. So we only replay this frame if it has
// extra_properties, or if it has multiplayer packets and we are
// within <multiplayer_recent> seconds of current time.
//
bool replay_this_frame = p_before->second.has_extra_properties;
if (p_before->second.has_multiplayer && p_before->first > time - multiplayer_recent) {
replay_this_frame = true;
}
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Looking at extra property changes."
<< " replay_this_frame=" << replay_this_frame
<< " m_continuous_in_time_last=" << m_continuous_in_time_last
<< " m_continuous_in_frame_time_last=" << m_continuous_in_frame_time_last
<< " time=" << time
<< " t_begin=" << t_begin
<< " p_before->first=" << p_before->first
<< " p_before->second=" << p_before->second
);
if (replay_this_frame) {
replay(
p_before->first,
p_before->second.offset,
0 /*offset_old*/,
false /*replay_signals*/,
p_before->first > time - multiplayer_recent /*replay_multiplayer*/,
true /*replay_extra_properties*/,
&xpos,
&ypos,
&xsize,
&ysize
);
}
}
/* Now replay signals, interpolating between frames atoffset_prev and
offset. */
replay( replay(
time, time,
offset, offset,
@ -1392,6 +1426,7 @@ FGReplay::replay( double time ) {
} }
m_continuous_in_time_last = time; m_continuous_in_time_last = time;
m_continuous_in_frame_time_last = p->first;
return ret; return ret;
} }
@ -1543,7 +1578,6 @@ static std::unique_ptr<FGReplayData> ReadFGReplayData(
ret->multiplayer_messages.clear(); ret->multiplayer_messages.clear();
uint32_t pos = 0; uint32_t pos = 0;
for(;;) { for(;;) {
SG_LOG(SG_SYSTEMS, SG_DEBUG, "length=" << length << " pos=" << pos);
assert(pos <= length); assert(pos <= length);
if (pos == length) { if (pos == length) {
break; break;
@ -1551,6 +1585,12 @@ static std::unique_ptr<FGReplayData> ReadFGReplayData(
std::shared_ptr<std::vector<char>> v(new std::vector<char>); std::shared_ptr<std::vector<char>> v(new std::vector<char>);
ret->multiplayer_messages.push_back(v); ret->multiplayer_messages.push_back(v);
pos += VectorRead<uint16_t>(in, *ret->multiplayer_messages.back(), length - pos); pos += VectorRead<uint16_t>(in, *ret->multiplayer_messages.back(), length - pos);
SG_LOG(SG_SYSTEMS, SG_BULK, "replaying multiplayer data"
<< " ret->sim_time=" << ret->sim_time
<< " length=" << length
<< " pos=" << pos
<< " callsign=" << ((T_MsgHdr*) &v->front())->Callsign
);
} }
} }
else if (load_extra_properties && !strcmp(data_type, "extra-properties")) { else if (load_extra_properties && !strcmp(data_type, "extra-properties")) {
@ -1586,12 +1626,6 @@ void FGReplay::replay(
int* ysize int* ysize
) )
{ {
SG_LOG(SG_SYSTEMS, SG_BULK,
"FGReplay::replay():"
<< " time=" << time
<< " offset=" << offset
<< " offset_old=" << offset_old
);
std::unique_ptr<FGReplayData> replay_data = ReadFGReplayData( std::unique_ptr<FGReplayData> replay_data = ReadFGReplayData(
m_continuous_in, m_continuous_in,
offset, offset,
@ -1600,6 +1634,7 @@ void FGReplay::replay(
replay_multiplayer, replay_multiplayer,
replay_extra_properties replay_extra_properties
); );
assert(replay_data.get());
std::unique_ptr<FGReplayData> replay_data_old; std::unique_ptr<FGReplayData> replay_data_old;
if (offset_old) { if (offset_old) {
replay_data_old = ReadFGReplayData( replay_data_old = ReadFGReplayData(
@ -1611,6 +1646,16 @@ void FGReplay::replay(
replay_extra_properties replay_extra_properties
); );
} }
if (replay_extra_properties) SG_LOG(SG_SYSTEMS, SG_BULK,
"FGReplay::replay():"
<< " time=" << time
<< " offset=" << offset
<< " offset_old=" << offset_old
<< " replay_data->raw_data.size()=" << replay_data->raw_data.size()
<< " replay_data->multiplayer_messages.size()=" << replay_data->multiplayer_messages.size()
<< " replay_data->extra_properties.size()=" << replay_data->extra_properties.size()
<< " replay_data->replay_extra_property_changes.size()=" << replay_data->replay_extra_property_changes.size()
);
m_pRecorder->replay(time, replay_data.get(), replay_data_old.get(), xpos, ypos, xsize, ysize); m_pRecorder->replay(time, replay_data.get(), replay_data_old.get(), xpos, ypos, xsize, ysize);
} }
@ -1918,10 +1963,12 @@ void FGReplay::indexContinuousRecording(const void* data, size_t numbytes)
else if (!strcmp(data->getStringValue(), "multiplayer")) { else if (!strcmp(data->getStringValue(), "multiplayer")) {
frameinfo.has_multiplayer = true; frameinfo.has_multiplayer = true;
++m_num_frames_multiplayer; ++m_num_frames_multiplayer;
m_continuous_in_multiplayer = true;
} }
else if (!strcmp(data->getStringValue(), "extra-properties")) { else if (!strcmp(data->getStringValue(), "extra-properties")) {
frameinfo.has_extra_properties = true; frameinfo.has_extra_properties = true;
++m_num_frames_extra_properties; ++m_num_frames_extra_properties;
m_continuous_in_extra_properties = true;
} }
} }
} }
@ -2021,6 +2068,7 @@ FGReplay::loadTape(const SGPath& Filename, bool Preview, SGPropertyNode& MetaMet
clear(); clear();
fillRecycler(); fillRecycler();
m_continuous_in_time_last = -1; m_continuous_in_time_last = -1;
m_continuous_in_frame_time_last = -1;
m_continuous_in_time_to_frameinfo.clear(); m_continuous_in_time_to_frameinfo.clear();
m_num_frames_extra_properties = 0; m_num_frames_extra_properties = 0;
m_num_frames_multiplayer = 0; m_num_frames_multiplayer = 0;
@ -2241,6 +2289,17 @@ FGReplay::listTapes(bool SameAircraftFilter, const SGPath& tapeDirectory)
return true; return true;
} }
std::string FGReplay::makeTapePath(const std::string& tape_name)
{
std::string path = tape_name;
if (simgear::strutils::ends_with(path, ".fgtape")) {
return path;
}
SGPath path2(fgGetString("/sim/replay/tape-directory", ""));
path2.append(path + ".fgtape");
return path2.str();
}
/** Load a flight recorder tape from disk. User/script command. */ /** Load a flight recorder tape from disk. User/script command. */
bool bool
FGReplay::loadTape(const SGPropertyNode* ConfigData) FGReplay::loadTape(const SGPropertyNode* ConfigData)
@ -2262,19 +2321,9 @@ FGReplay::loadTape(const SGPropertyNode* ConfigData)
else else
{ {
SGPropertyNode* MetaMeta = fgGetNode("/sim/gui/dialogs/flightrecorder/preview", true); SGPropertyNode* MetaMeta = fgGetNode("/sim/gui/dialogs/flightrecorder/preview", true);
SGPath tapePath; std::string path = makeTapePath(tape);
if (simgear::strutils::ends_with(tape, ".fgtape")) SG_LOG(SG_SYSTEMS, MY_SG_DEBUG, "Checking flight recorder file " << path << ", preview: " << Preview);
{ return loadTape(path, Preview, *MetaMeta);
tapePath = tape;
}
else
{
tapePath = tapeDirectory;
tapePath.append(tape);
tapePath.concat(".fgtape");
}
SG_LOG(SG_SYSTEMS, MY_SG_DEBUG, "Checking flight recorder file " << tapePath << ", preview: " << Preview);
return loadTape(tapePath, Preview, *MetaMeta);
} }
} }

View file

@ -145,6 +145,10 @@ public:
// //
static int loadContinuousHeader(const std::string& path, std::istream* in, SGPropertyNode* properties); static int loadContinuousHeader(const std::string& path, std::istream* in, SGPropertyNode* properties);
// Prepends /sim/replay/tape-directory and/or appends .fgtape etc.
//
static std::string makeTapePath(const std::string& tape_name);
private: private:
void clear(); void clear();
FGReplayData* record(double time); FGReplayData* record(double time);
@ -255,6 +259,7 @@ private:
std::map<double, FGFrameInfo> m_continuous_in_time_to_frameinfo; std::map<double, FGFrameInfo> m_continuous_in_time_to_frameinfo;
SGPropertyNode_ptr m_continuous_in_config; SGPropertyNode_ptr m_continuous_in_config;
double m_continuous_in_time_last; double m_continuous_in_time_last;
double m_continuous_in_frame_time_last;
std::ifstream m_continuous_indexing_in; std::ifstream m_continuous_indexing_in;
std::streampos m_continuous_indexing_pos; std::streampos m_continuous_indexing_pos;

View file

@ -1658,7 +1658,8 @@ fgOptLoadTape(const char* arg)
// //
// Load the recording's header if it is a Continuous recording. // Load the recording's header if it is a Continuous recording.
// //
(void) FGReplay::loadContinuousHeader(arg, nullptr /*in*/, properties); path = FGReplay::makeTapePath(arg);
(void) FGReplay::loadContinuousHeader(path.c_str(), nullptr /*in*/, properties);
} }
else { else {
// <arg> is a URL. Start download. // <arg> is a URL. Start download.

View file

@ -2299,169 +2299,169 @@ FGMultiplayMgr::ProcessPosMsg(const FGMultiplayMgr::MsgBuf& Msg,
// Check the ID actually exists and get the type // Check the ID actually exists and get the type
const IdPropertyList* plist = findProperty(id); const IdPropertyList* plist = findProperty(id);
if (plist) if (plist)
{
FGPropertyData* pData = new FGPropertyData;
if (plist->decode_received)
{ {
// FGPropertyData* pData = new FGPropertyData;
// this needs the pointer prior to the extraction of the property id and possible shortint decode if (plist->decode_received)
// too allow the method to redecode as it wishes
xdr = (*plist->decode_received)(plist, xdr, pData);
}
else
{
pData->id = id;
pData->type = plist->type;
xdr++;
// How we decode the remainder of the property depends on the type
switch (pData->type) {
case simgear::props::BOOL:
/*
* For 2017.2 we support boolean arrays transmitted as a single int for 30 bools.
* this section handles the unpacking into the arrays.
*/
if (pData->id >= BOOLARRAY_START_ID && pData->id <= BOOLARRAY_END_ID)
{
unsigned int val = XDR_decode_uint32(*xdr);
bool first_bool = true;
xdr++;
for (int bitidx = 0; bitidx <= 30; bitidx++)
{
// ensure that this property is in the master list.
const IdPropertyList* plistBool = findProperty(id + bitidx);
if (plistBool)
{
if (first_bool)
first_bool = false;
else
pData = new FGPropertyData;
pData->id = id + bitidx;
pData->int_value = (val & (1 << bitidx)) != 0;
pData->type = simgear::props::BOOL;
motionInfo.properties.push_back(pData);
// ensure that this is null because this section of code manages the property data and list directly
// it has to be this way because one MP value results in multiple properties being set.
pData = nullptr;
}
}
break;
}
case simgear::props::INT:
case simgear::props::LONG:
if (short_int_encoded)
{
pData->int_value = int_value;
pData->type = simgear::props::INT;
}
else
{
pData->int_value = XDR_decode_uint32(*xdr);
xdr++;
}
//cout << pData->int_value << "\n";
break;
case simgear::props::FLOAT:
case simgear::props::DOUBLE:
if (short_int_encoded)
{
switch (plist->TransmitAs)
{
case TT_SHORT_FLOAT_1:
pData->float_value = (double)int_value / 10.0;
break;
case TT_SHORT_FLOAT_2:
pData->float_value = (double)int_value / 100.0;
break;
case TT_SHORT_FLOAT_3:
pData->float_value = (double)int_value / 1000.0;
break;
case TT_SHORT_FLOAT_4:
pData->float_value = (double)int_value / 10000.0;
break;
case TT_SHORT_FLOAT_NORM:
pData->float_value = (double)int_value / 32767.0;
break;
default:
break;
}
}
else
{
pData->float_value = XDR_decode_float(*xdr);
xdr++;
}
break;
case simgear::props::STRING:
case simgear::props::UNSPECIFIED:
{ {
// if the string is using short int encoding then it is in the new format. //
if (short_int_encoded) // this needs the pointer prior to the extraction of the property id and possible shortint decode
{ // too allow the method to redecode as it wishes
uint32_t length = int_value; xdr = (*plist->decode_received)(plist, xdr, pData);
pData->string_value = new char[length + 1];
char *cptr = (char*)xdr;
for (unsigned i = 0; i < length; i++)
{
pData->string_value[i] = *cptr++;
}
pData->string_value[length] = '\0';
xdr = (xdr_data_t*)cptr;
}
else {
// String is complicated. It consists of
// The length of the string
// The string itself
// Padding to the nearest 4-bytes.
uint32_t length = XDR_decode_uint32(*xdr);
xdr++;
//cout << length << " ";
// Old versions truncated the string but left the length unadjusted.
if (length > MAX_TEXT_SIZE)
length = MAX_TEXT_SIZE;
pData->string_value = new char[length + 1];
//cout << " String: ";
for (unsigned i = 0; i < length; i++)
{
pData->string_value[i] = (char)XDR_decode_int8(*xdr);
xdr++;
}
pData->string_value[length] = '\0';
// Now handle the padding
while ((length % 4) != 0)
{
xdr++;
length++;
//cout << "0";
}
//cout << "\n";
}
} }
break; else
{
pData->id = id;
pData->type = plist->type;
xdr++;
// How we decode the remainder of the property depends on the type
switch (pData->type) {
case simgear::props::BOOL:
/*
* For 2017.2 we support boolean arrays transmitted as a single int for 30 bools.
* this section handles the unpacking into the arrays.
*/
if (pData->id >= BOOLARRAY_START_ID && pData->id <= BOOLARRAY_END_ID)
{
unsigned int val = XDR_decode_uint32(*xdr);
bool first_bool = true;
xdr++;
for (int bitidx = 0; bitidx <= 30; bitidx++)
{
// ensure that this property is in the master list.
const IdPropertyList* plistBool = findProperty(id + bitidx);
default: if (plistBool)
pData->float_value = XDR_decode_float(*xdr); {
SG_LOG(SG_NETWORK, SG_DEBUG, "Unknown Prop type " << pData->id << " " << pData->type); if (first_bool)
xdr++; first_bool = false;
break; else
} pData = new FGPropertyData;
}
if (pData) {
motionInfo.properties.push_back(pData);
// Special case - we need the /sim/model/fallback-model-index to create pData->id = id + bitidx;
// the MP model pData->int_value = (val & (1 << bitidx)) != 0;
if (pData->id == FALLBACK_MODEL_ID) { pData->type = simgear::props::BOOL;
fallback_model_index = pData->int_value; motionInfo.properties.push_back(pData);
SG_LOG(SG_NETWORK, SG_DEBUG, "Found Fallback model index in message " << fallback_model_index);
// ensure that this is null because this section of code manages the property data and list directly
// it has to be this way because one MP value results in multiple properties being set.
pData = nullptr;
}
}
break;
}
case simgear::props::INT:
case simgear::props::LONG:
if (short_int_encoded)
{
pData->int_value = int_value;
pData->type = simgear::props::INT;
}
else
{
pData->int_value = XDR_decode_uint32(*xdr);
xdr++;
}
//cout << pData->int_value << "\n";
break;
case simgear::props::FLOAT:
case simgear::props::DOUBLE:
if (short_int_encoded)
{
switch (plist->TransmitAs)
{
case TT_SHORT_FLOAT_1:
pData->float_value = (double)int_value / 10.0;
break;
case TT_SHORT_FLOAT_2:
pData->float_value = (double)int_value / 100.0;
break;
case TT_SHORT_FLOAT_3:
pData->float_value = (double)int_value / 1000.0;
break;
case TT_SHORT_FLOAT_4:
pData->float_value = (double)int_value / 10000.0;
break;
case TT_SHORT_FLOAT_NORM:
pData->float_value = (double)int_value / 32767.0;
break;
default:
break;
}
}
else
{
pData->float_value = XDR_decode_float(*xdr);
xdr++;
}
break;
case simgear::props::STRING:
case simgear::props::UNSPECIFIED:
{
// if the string is using short int encoding then it is in the new format.
if (short_int_encoded)
{
uint32_t length = int_value;
pData->string_value = new char[length + 1];
char *cptr = (char*)xdr;
for (unsigned i = 0; i < length; i++)
{
pData->string_value[i] = *cptr++;
}
pData->string_value[length] = '\0';
xdr = (xdr_data_t*)cptr;
}
else {
// String is complicated. It consists of
// The length of the string
// The string itself
// Padding to the nearest 4-bytes.
uint32_t length = XDR_decode_uint32(*xdr);
xdr++;
//cout << length << " ";
// Old versions truncated the string but left the length unadjusted.
if (length > MAX_TEXT_SIZE)
length = MAX_TEXT_SIZE;
pData->string_value = new char[length + 1];
//cout << " String: ";
for (unsigned i = 0; i < length; i++)
{
pData->string_value[i] = (char)XDR_decode_int8(*xdr);
xdr++;
}
pData->string_value[length] = '\0';
// Now handle the padding
while ((length % 4) != 0)
{
xdr++;
length++;
//cout << "0";
}
//cout << "\n";
}
}
break;
default:
pData->float_value = XDR_decode_float(*xdr);
SG_LOG(SG_NETWORK, SG_DEBUG, "Unknown Prop type " << pData->id << " " << pData->type);
xdr++;
break;
}
}
if (pData) {
motionInfo.properties.push_back(pData);
// Special case - we need the /sim/model/fallback-model-index to create
// the MP model
if (pData->id == FALLBACK_MODEL_ID) {
fallback_model_index = pData->int_value;
SG_LOG(SG_NETWORK, SG_DEBUG, "Found Fallback model index in message " << fallback_model_index);
}
} }
}
} }
else else
{ {