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:
parent
538e32d555
commit
4553d813b1
6 changed files with 424 additions and 349 deletions
|
@ -222,10 +222,14 @@ static std::shared_ptr<RecordExtraProperties> s_record_extra_properties;
|
|||
|
||||
|
||||
FGFlightRecorder::FGFlightRecorder(const char* pConfigName) :
|
||||
m_RecorderNode(fgGetNode("/sim/flight-recorder", true)),
|
||||
m_ReplayMultiplayer(fgGetNode("/sim/replay/multiplayer", true)),
|
||||
m_RecordContinuous(fgGetNode("/sim/replay/record-continuous", true)),
|
||||
m_RecordExtraProperties(fgGetNode("/sim/replay/record-extra-properties", true)),
|
||||
m_RecorderNode (fgGetNode("/sim/flight-recorder", true)),
|
||||
m_ReplayMultiplayer (fgGetNode("/sim/replay/multiplayer", true)),
|
||||
m_ReplayExtraProperties (fgGetNode("/sim/replay/replay-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_ConfigName(pConfigName),
|
||||
m_usingDefaultConfig(false),
|
||||
|
@ -624,12 +628,29 @@ FGFlightRecorder::capture(double SimTime, FGReplayData* ReplayData)
|
|||
ReplayData->multiplayer_messages.clear();
|
||||
bool replayMultiplayer = m_ReplayMultiplayer->getBoolValue();
|
||||
for(;;) {
|
||||
auto MultiplayerMessage = m_MultiplayMgr->popMessageHistory();
|
||||
if (!MultiplayerMessage) {
|
||||
auto multiplayerMessage = m_MultiplayMgr->popMessageHistory();
|
||||
if (!multiplayerMessage) {
|
||||
break;
|
||||
}
|
||||
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_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;
|
||||
}
|
||||
s_record_extra_properties->capture(m_RecordExtraPropertiesReference, ReplayData);
|
||||
|
@ -698,7 +719,7 @@ weighting(TInterpolation interpolation, double ratio, double v1,double v2)
|
|||
void
|
||||
FGFlightRecorder::resetExtraProperties()
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_ALERT, "Clearing m_RecordExtraPropertiesReference");
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Clearing m_RecordExtraPropertiesReference");
|
||||
m_RecordExtraPropertiesReference = nullptr;
|
||||
}
|
||||
|
||||
|
@ -726,135 +747,128 @@ FGFlightRecorder::replay(double SimTime, const FGReplayData* _pNextBuffer,
|
|||
int* main_window_ysize
|
||||
)
|
||||
{
|
||||
const char* pLastBuffer = (_pLastBuffer) ? &_pLastBuffer->raw_data.front() : nullptr;
|
||||
const char* pBuffer = (_pNextBuffer) ? &_pNextBuffer->raw_data.front() : nullptr;
|
||||
if (!pBuffer)
|
||||
return;
|
||||
|
||||
int Offset = 0;
|
||||
double ratio = 1.0;
|
||||
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))
|
||||
const char* pLastBuffer = (_pLastBuffer && !_pLastBuffer->raw_data.empty()) ? &_pLastBuffer->raw_data.front() : nullptr;
|
||||
const char* pBuffer = (_pNextBuffer && !_pNextBuffer->raw_data.empty()) ? &_pNextBuffer->raw_data.front() : nullptr;
|
||||
|
||||
if (pBuffer) {
|
||||
/* Replay signals. */
|
||||
int Offset = 0;
|
||||
double ratio = 1.0;
|
||||
if (pLastBuffer)
|
||||
{
|
||||
ratio = Numerator / dt;
|
||||
if (ratio > 1.0)
|
||||
ratio = 1.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)
|
||||
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))
|
||||
{
|
||||
v = weighting(m_CaptureDouble[i].Interpolation, ratio,
|
||||
pLastDoubles[i], v);
|
||||
ratio = Numerator / dt;
|
||||
if (ratio > 1.0)
|
||||
ratio = 1.0;
|
||||
}
|
||||
m_CaptureDouble[i].Signal->setDoubleValue(v);
|
||||
}
|
||||
Offset += SignalCount * sizeof(double);
|
||||
}
|
||||
|
||||
// 32bit aligned data comes second...
|
||||
{
|
||||
// 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++)
|
||||
// 64bit aligned data first!
|
||||
{
|
||||
float v = pFloats[i];
|
||||
if (pLastBuffer)
|
||||
// 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++)
|
||||
{
|
||||
v = weighting(m_CaptureFloat[i].Interpolation, ratio,
|
||||
pLastFloats[i], v);
|
||||
double v = pDoubles[i];
|
||||
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);
|
||||
}
|
||||
|
||||
{
|
||||
// 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++)
|
||||
// 32bit aligned data comes second...
|
||||
{
|
||||
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...
|
||||
{
|
||||
// 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++)
|
||||
// 16bit aligned data is next...
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
{
|
||||
// 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++)
|
||||
// finally: byte aligned data is last...
|
||||
{
|
||||
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
|
||||
// messages repeatedly when we are called with a timestamp that ends up
|
||||
// picking the same _pNextBuffer as last time.
|
||||
// Replay any multiplayer messages.
|
||||
for (auto multiplayer_message: _pNextBuffer->multiplayer_messages) {
|
||||
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;
|
||||
if ( _pNextBuffer != _pNextBuffer_prev) {
|
||||
_pNextBuffer_prev = _pNextBuffer;
|
||||
for (auto multiplayer_message: _pNextBuffer->multiplayer_messages) {
|
||||
m_MultiplayMgr->pushMessageHistory(multiplayer_message);
|
||||
}
|
||||
}
|
||||
bool replay_extra_properties = m_ReplayExtraProperties->getBoolValue();
|
||||
bool replay_main_view = m_ReplayMainView->getBoolValue();
|
||||
bool replay_main_window_position = m_ReplayMainWindowPosition->getBoolValue();
|
||||
bool replay_main_window_size = m_ReplayMainWindowSize->getBoolValue();
|
||||
|
||||
// Replay property changes.
|
||||
//
|
||||
|
||||
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) {
|
||||
if (replay_extra_properties) {
|
||||
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);
|
||||
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& value = prop_change.second;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -905,7 +919,7 @@ FGFlightRecorder::replay(double SimTime, const FGReplayData* _pNextBuffer,
|
|||
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
|
||||
<< " replaying extra_property change: " << path << "=" << value);
|
||||
globals->get_props()->setStringValue(path, value);
|
||||
|
|
|
@ -85,7 +85,13 @@ private:
|
|||
|
||||
SGPropertyNode_ptr m_RecorderNode;
|
||||
SGPropertyNode_ptr m_ConfigNode;
|
||||
|
||||
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_RecordExtraProperties;
|
||||
|
||||
|
|
|
@ -739,7 +739,7 @@ static SGPropertyNode_ptr saveSetup(
|
|||
|| fgGetBool("/sim/replay/record-main-window", 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-main-window=" << fgGetBool("/sim/replay/record-main-window", false)
|
||||
<< " record-main-view=" << fgGetBool("/sim/replay/record-main-view", false)
|
||||
|
@ -852,6 +852,15 @@ struct FGFrameInfo
|
|||
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
|
||||
FGReplay::update( double dt )
|
||||
{
|
||||
|
@ -1257,69 +1266,44 @@ FGReplay::replay( double time ) {
|
|||
int xsize = xsize0;
|
||||
int ysize = ysize0;
|
||||
|
||||
double t_begin = m_continuous_in_time_last;
|
||||
if (m_continuous_in_extra_properties) {
|
||||
// Continuous recording has property changes.
|
||||
double multiplayer_recent = 3;
|
||||
|
||||
// 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
|
||||
// from t=0.
|
||||
|
||||
if (m_continuous_in_multiplayer) {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
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."
|
||||
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Have gone backwards."
|
||||
<< " m_continuous_in_time_last=" << m_continuous_in_time_last
|
||||
<< " time=" << time
|
||||
<< " t_begin=" << t_begin
|
||||
<< " p->first=" << p->first
|
||||
<< " p->second.offset=" << p->second.offset
|
||||
<< " m_continuous_in_extra_properties=" << m_continuous_in_extra_properties
|
||||
);
|
||||
|
||||
// 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);
|
||||
bool ret = false;
|
||||
|
@ -1340,7 +1324,7 @@ FGReplay::replay( double time ) {
|
|||
offset = p->second.offset;
|
||||
}
|
||||
else {
|
||||
// Interpolate between items that straddle <time>.
|
||||
// Interpolate between pair of items that straddle <time>.
|
||||
auto prev = p;
|
||||
--prev;
|
||||
offset_prev = prev->second.offset;
|
||||
|
@ -1351,6 +1335,56 @@ FGReplay::replay( double time ) {
|
|||
// Exact match.
|
||||
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(
|
||||
time,
|
||||
offset,
|
||||
|
@ -1392,6 +1426,7 @@ FGReplay::replay( double time ) {
|
|||
}
|
||||
|
||||
m_continuous_in_time_last = time;
|
||||
m_continuous_in_frame_time_last = p->first;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -1543,7 +1578,6 @@ static std::unique_ptr<FGReplayData> ReadFGReplayData(
|
|||
ret->multiplayer_messages.clear();
|
||||
uint32_t pos = 0;
|
||||
for(;;) {
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "length=" << length << " pos=" << pos);
|
||||
assert(pos <= length);
|
||||
if (pos == length) {
|
||||
break;
|
||||
|
@ -1551,6 +1585,12 @@ static std::unique_ptr<FGReplayData> ReadFGReplayData(
|
|||
std::shared_ptr<std::vector<char>> v(new std::vector<char>);
|
||||
ret->multiplayer_messages.push_back(v);
|
||||
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")) {
|
||||
|
@ -1586,12 +1626,6 @@ void FGReplay::replay(
|
|||
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(
|
||||
m_continuous_in,
|
||||
offset,
|
||||
|
@ -1600,6 +1634,7 @@ void FGReplay::replay(
|
|||
replay_multiplayer,
|
||||
replay_extra_properties
|
||||
);
|
||||
assert(replay_data.get());
|
||||
std::unique_ptr<FGReplayData> replay_data_old;
|
||||
if (offset_old) {
|
||||
replay_data_old = ReadFGReplayData(
|
||||
|
@ -1611,6 +1646,16 @@ void FGReplay::replay(
|
|||
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);
|
||||
}
|
||||
|
||||
|
@ -1918,10 +1963,12 @@ void FGReplay::indexContinuousRecording(const void* data, size_t numbytes)
|
|||
else if (!strcmp(data->getStringValue(), "multiplayer")) {
|
||||
frameinfo.has_multiplayer = true;
|
||||
++m_num_frames_multiplayer;
|
||||
m_continuous_in_multiplayer = true;
|
||||
}
|
||||
else if (!strcmp(data->getStringValue(), "extra-properties")) {
|
||||
frameinfo.has_extra_properties = true;
|
||||
++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();
|
||||
fillRecycler();
|
||||
m_continuous_in_time_last = -1;
|
||||
m_continuous_in_frame_time_last = -1;
|
||||
m_continuous_in_time_to_frameinfo.clear();
|
||||
m_num_frames_extra_properties = 0;
|
||||
m_num_frames_multiplayer = 0;
|
||||
|
@ -2241,6 +2289,17 @@ FGReplay::listTapes(bool SameAircraftFilter, const SGPath& tapeDirectory)
|
|||
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. */
|
||||
bool
|
||||
FGReplay::loadTape(const SGPropertyNode* ConfigData)
|
||||
|
@ -2262,19 +2321,9 @@ FGReplay::loadTape(const SGPropertyNode* ConfigData)
|
|||
else
|
||||
{
|
||||
SGPropertyNode* MetaMeta = fgGetNode("/sim/gui/dialogs/flightrecorder/preview", true);
|
||||
SGPath tapePath;
|
||||
if (simgear::strutils::ends_with(tape, ".fgtape"))
|
||||
{
|
||||
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);
|
||||
std::string path = makeTapePath(tape);
|
||||
SG_LOG(SG_SYSTEMS, MY_SG_DEBUG, "Checking flight recorder file " << path << ", preview: " << Preview);
|
||||
return loadTape(path, Preview, *MetaMeta);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -145,6 +145,10 @@ public:
|
|||
//
|
||||
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:
|
||||
void clear();
|
||||
FGReplayData* record(double time);
|
||||
|
@ -255,6 +259,7 @@ private:
|
|||
std::map<double, FGFrameInfo> m_continuous_in_time_to_frameinfo;
|
||||
SGPropertyNode_ptr m_continuous_in_config;
|
||||
double m_continuous_in_time_last;
|
||||
double m_continuous_in_frame_time_last;
|
||||
|
||||
std::ifstream m_continuous_indexing_in;
|
||||
std::streampos m_continuous_indexing_pos;
|
||||
|
|
|
@ -1658,7 +1658,8 @@ fgOptLoadTape(const char* arg)
|
|||
//
|
||||
// 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 {
|
||||
// <arg> is a URL. Start download.
|
||||
|
|
|
@ -2299,169 +2299,169 @@ FGMultiplayMgr::ProcessPosMsg(const FGMultiplayMgr::MsgBuf& Msg,
|
|||
// Check the ID actually exists and get the type
|
||||
const IdPropertyList* plist = findProperty(id);
|
||||
|
||||
if (plist)
|
||||
{
|
||||
FGPropertyData* pData = new FGPropertyData;
|
||||
if (plist->decode_received)
|
||||
if (plist)
|
||||
{
|
||||
//
|
||||
// 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
|
||||
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:
|
||||
FGPropertyData* pData = new FGPropertyData;
|
||||
if (plist->decode_received)
|
||||
{
|
||||
// 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";
|
||||
}
|
||||
//
|
||||
// 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
|
||||
xdr = (*plist->decode_received)(plist, xdr, pData);
|
||||
}
|
||||
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:
|
||||
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);
|
||||
if (plistBool)
|
||||
{
|
||||
if (first_bool)
|
||||
first_bool = false;
|
||||
else
|
||||
pData = new FGPropertyData;
|
||||
|
||||
// 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);
|
||||
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)
|
||||
{
|
||||
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
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue