1
0
Fork 0

Multiplayer replay: new, support for replaying multiplayer aircraft as well as the user's aircraft.

At the moment this only works for live replays - we don't (yet) write the
multiplayer information to fgtape files.

Enable with:
    --prop:bool:/sim/replay/multiplayer=true

This works by copying all raw multiplayer packets into a buffer in
FGMultiplayMgr. Each time it is called, FGFlightRecorder::capture() moves all
the available packet from this buffer into its FGReplayData. Thus we store
roughly syncronised multiplayer packets along with the user aircraft's detailed
replay properties.

When replaying, FGFlightRecorder pushes packets into a buffer in
FGMultiplayMgr, which are used instead of live multiplayer packets. [Actually
when replaying FGMultiplayMgr still reads live packets in order to handle live
chat messages, and ignores chat messages from FGFlightRecorder.]
This commit is contained in:
Julian Smith 2020-05-16 23:15:34 +01:00
parent 0015744486
commit e7b1f3f52e
6 changed files with 211 additions and 85 deletions

View file

@ -36,15 +36,19 @@
#include <simgear/math/SGMath.hxx>
#include <Main/fg_props.hxx>
#include "flightrecorder.hxx"
#include <MultiPlayer/multiplaymgr.hxx>
#include <MultiPlayer/mpmessages.hxx>
using namespace FlightRecorder;
using std::string;
FGFlightRecorder::FGFlightRecorder(const char* pConfigName) :
m_RecorderNode(fgGetNode("/sim/flight-recorder", true)),
m_ReplayMultiplayer(fgGetNode("/sim/replay/multiplayer", true)),
m_TotalRecordSize(0),
m_ConfigName(pConfigName),
m_usingDefaultConfig(false)
m_usingDefaultConfig(false),
m_MultiplayMgr(globals->get_subsystem<FGMultiplayMgr>())
{
}
@ -308,23 +312,6 @@ FGFlightRecorder::processSignalList(const char* pSignalType, TSignalList& Signal
}
}
/** Get an empty container for a single capture. */
FGReplayData*
FGFlightRecorder::createEmptyRecord(void)
{
if (!m_TotalRecordSize)
return NULL;
FGReplayData* p = (FGReplayData*) new unsigned char[m_TotalRecordSize];
return p;
}
/** Free given container with capture data. */
void
FGFlightRecorder::deleteRecord(FGReplayData* pRecord)
{
delete[] pRecord;
}
/** Capture data.
* When pBuffer==NULL new memory is allocated.
* If pBuffer!=NULL memory of given buffer is reused.
@ -334,16 +321,15 @@ FGFlightRecorder::capture(double SimTime, FGReplayData* pRecycledBuffer)
{
if (!pRecycledBuffer)
{
pRecycledBuffer = createEmptyRecord();
pRecycledBuffer = new FGReplayData;
if (!pRecycledBuffer)
return NULL;
}
unsigned char* pBuffer = (unsigned char*) pRecycledBuffer;
int Offset = 0;
pRecycledBuffer->sim_time = SimTime;
Offset += sizeof(double);
pRecycledBuffer->raw_data.resize( m_TotalRecordSize);
char* pBuffer = &pRecycledBuffer->raw_data.front();
// 64bit aligned data first!
{
// capture doubles
@ -417,9 +403,26 @@ FGFlightRecorder::capture(double SimTime, FGReplayData* pRecycledBuffer)
}
}
assert(Offset == m_TotalRecordSize);
assert(Offset + sizeof(double) == m_TotalRecordSize);
// If m_ReplayMultiplayer is true, move all recent
// multiplayer messages from m_MultiplayMgr into
// pRecycledBuffer->multiplayer_messages. Otherwise clear m_MultiplayMgr's
// list of recent messages.
//
pRecycledBuffer->multiplayer_messages.clear();
bool replayMultiplayer = m_ReplayMultiplayer->getBoolValue();
for(;;) {
auto MultiplayerMessage = m_MultiplayMgr->popMessageHistory();
if (!MultiplayerMessage) {
break;
}
if (replayMultiplayer) {
pRecycledBuffer->multiplayer_messages.push_back( MultiplayerMessage);
}
}
return (FGReplayData*) pBuffer;
return pRecycledBuffer;
}
/** Do interpolation as defined by given interpolation type and weighting ratio. */
@ -465,8 +468,8 @@ weighting(TInterpolation interpolation, double ratio, double v1,double v2)
void
FGFlightRecorder::replay(double SimTime, const FGReplayData* _pNextBuffer, const FGReplayData* _pLastBuffer)
{
const char* pLastBuffer = (const char*) _pLastBuffer;
const char* pBuffer = (const char*) _pNextBuffer;
const char* pLastBuffer = (_pLastBuffer) ? &_pLastBuffer->raw_data.front() : nullptr;
const char* pBuffer = (_pNextBuffer) ? &_pNextBuffer->raw_data.front() : nullptr;
if (!pBuffer)
return;
@ -487,8 +490,6 @@ FGFlightRecorder::replay(double SimTime, const FGReplayData* _pNextBuffer, const
}
}
Offset += sizeof(double);
// 64bit aligned data first!
{
// restore doubles
@ -573,6 +574,11 @@ FGFlightRecorder::replay(double SimTime, const FGReplayData* _pNextBuffer, const
m_CaptureBool[i].Signal->setBoolValue(0 != (pFlags[i>>3] & (1 << (i&7))));
}
}
// Replay any multiplayer messages.
for ( auto multiplayer_message: _pNextBuffer->multiplayer_messages) {
m_MultiplayMgr->pushMessageHistory(multiplayer_message);
}
}
int

View file

@ -24,6 +24,7 @@
#define FLIGHTRECORDER_HXX_
#include <simgear/props/props.hxx>
#include <MultiPlayer/multiplaymgr.hxx>
#include "replay.hxx"
namespace FlightRecorder
@ -55,11 +56,9 @@ public:
void reinit (void);
void reinit (SGPropertyNode_ptr ConfigNode);
FGReplayData* createEmptyRecord (void);
FGReplayData* capture (double SimTime, FGReplayData* pRecycledBuffer);
void replay (double SimTime, const FGReplayData* pNextBuffer,
const FGReplayData* pLastBuffer = NULL);
void deleteRecord (FGReplayData* pRecord);
int getRecordSize (void) { return m_TotalRecordSize;}
void getConfig (SGPropertyNode* root);
@ -78,6 +77,7 @@ private:
SGPropertyNode_ptr m_RecorderNode;
SGPropertyNode_ptr m_ConfigNode;
SGPropertyNode_ptr m_ReplayMultiplayer;
FlightRecorder::TSignalList m_CaptureDouble;
FlightRecorder::TSignalList m_CaptureFloat;
@ -89,6 +89,7 @@ private:
int m_TotalRecordSize;
std::string m_ConfigName;
bool m_usingDefaultConfig;
FGMultiplayMgr* m_MultiplayMgr;
};
#endif /* FLIGHTRECORDER_HXX_ */

View file

@ -109,22 +109,22 @@ FGReplay::clear()
{
while ( !short_term.empty() )
{
m_pRecorder->deleteRecord(short_term.front());
delete short_term.front();
short_term.pop_front();
}
while ( !medium_term.empty() )
{
m_pRecorder->deleteRecord(medium_term.front());
delete medium_term.front();
medium_term.pop_front();
}
while ( !long_term.empty() )
{
m_pRecorder->deleteRecord(long_term.front());
delete long_term.front();
long_term.pop_front();
}
while ( !recycler.empty() )
{
m_pRecorder->deleteRecord(recycler.front());
delete recycler.front();
recycler.pop_front();
}
@ -213,7 +213,7 @@ FGReplay::fillRecycler()
(m_low_res_time*m_long_sample_rate));
for (int i = 0; i < estNrObjects; i++)
{
FGReplayData* r = m_pRecorder->createEmptyRecord();
FGReplayData* r = new FGReplayData;
if (r)
recycler.push_back(r);
else
@ -753,7 +753,9 @@ saveRawReplayData(gzContainerWriter& output, const replay_list_type& ReplayData,
!output.fail())
{
const FGReplayData* pRecord = *it++;
output.write((char*)pRecord, RecordSize);
assert(RecordSize == pRecord->raw_data.size());
output.write(reinterpret_cast<const char*>(&pRecord->sim_time), sizeof(pRecord->sim_time));
output.write(&pRecord->raw_data.front(), pRecord->raw_data.size());
CheckCount++;
}
@ -796,8 +798,10 @@ loadRawReplayData(gzContainerReader& input, FGFlightRecorder* pRecorder, replay_
size_t CheckCount = 0;
for (CheckCount=0; (CheckCount<Count)&&(!input.eof()); ++CheckCount)
{
FGReplayData* pBuffer = pRecorder->createEmptyRecord();
input.read((char*) pBuffer, RecordSize);
FGReplayData* pBuffer = new FGReplayData;
input.read(reinterpret_cast<char*>(&pBuffer->sim_time), sizeof(pBuffer->sim_time));
pBuffer->raw_data.resize(RecordSize);
input.read(&pBuffer->raw_data.front(), RecordSize);
ReplayData.push_back(pBuffer);
}

View file

@ -40,9 +40,13 @@
class FGFlightRecorder;
typedef struct {
double sim_time;
char raw_data;
/* more data here, hidden to the outside world */
// Our aircraft state.
std::vector<char> raw_data;
// Incoming multiplayer messages.
std::vector<std::shared_ptr<std::vector<char>>> multiplayer_messages;
} FGReplayData;
typedef struct {

View file

@ -963,6 +963,7 @@ FGMultiplayMgr::FGMultiplayMgr()
pMultiPlayTransmitPropertyBase = fgGetNode("/sim/multiplay/transmit-filter-property-base", true);
pMultiPlayRange = fgGetNode("/sim/multiplay/visibility-range-nm", true);
pMultiPlayRange->setIntValue(100);
pReplayState = fgGetNode("/sim/replay/replay-state", true);
} // FGMultiplayMgr::FGMultiplayMgr()
//////////////////////////////////////////////////////////////////////
@ -1692,6 +1693,127 @@ FGMultiplayMgr::SendTextMessage(const string &MsgText)
} // FGMultiplayMgr::SendTextMessage ()
//////////////////////////////////////////////////////////////////////
// If a message is available from mSocket, copies into <msgBuf>, converts
// endiness of the T_MsgHdr, and returns length.
//
// Otherwise returns 0.
//
int FGMultiplayMgr::GetMsgNetwork(MsgBuf& msgBuf, simgear::IPAddress& SenderAddress)
{
//////////////////////////////////////////////////
// Although the recv call asks for
// MAX_PACKET_SIZE of data, the number of bytes
// returned will only be that of the next
// packet waiting to be processed.
//////////////////////////////////////////////////
int RecvStatus = mSocket->recvfrom(msgBuf.Msg, sizeof(msgBuf.Msg), 0,
&SenderAddress);
//////////////////////////////////////////////////
// no Data received
//////////////////////////////////////////////////
if (RecvStatus == 0)
return 0;
// socket error reported?
// errno isn't thread-safe - so only check its value when
// socket return status < 0 really indicates a failure.
if ((RecvStatus < 0)&&
((errno == EAGAIN) || (errno == 0))) // MSVC output "NoError" otherwise
{
// ignore "normal" errors
return 0;
}
if (RecvStatus<0)
{
#ifdef _WIN32
if (::WSAGetLastError() != WSAEWOULDBLOCK) // this is normal on a receive when there is no data
{
// with Winsock the error will not be the actual problem.
SG_LOG(SG_NETWORK, SG_INFO, "FGMultiplayMgr::MP_ProcessData - Unable to receive data. WSAGetLastError=" << ::WSAGetLastError());
}
#else
SG_LOG(SG_NETWORK, SG_DEBUG, "FGMultiplayMgr::MP_ProcessData - Unable to receive data. "
<< strerror(errno) << "(errno " << errno << ")");
#endif
return 0;
}
T_MsgHdr* MsgHdr = msgBuf.msgHdr();
MsgHdr->Magic = XDR_decode_uint32 (MsgHdr->Magic);
MsgHdr->Version = XDR_decode_uint32 (MsgHdr->Version);
MsgHdr->MsgId = XDR_decode_uint32 (MsgHdr->MsgId);
MsgHdr->MsgLen = XDR_decode_uint32 (MsgHdr->MsgLen);
MsgHdr->ReplyPort = XDR_decode_uint32 (MsgHdr->ReplyPort);
MsgHdr->Callsign[MAX_CALLSIGN_LEN -1] = '\0';
return RecvStatus;
}
// Returns message in msgBuf out-param.
//
// If we are in replay mode, we return recorded messages (omitting recorded
// chat messages), and live chat messages from mSocket.
//
int FGMultiplayMgr::GetMsg(MsgBuf& msgBuf, simgear::IPAddress& SenderAddress)
{
if (pReplayState->getIntValue()) {
// We are replaying, so return non-chat multiplayer messages from
// mReplayMessageQueue and live chat messages from mSocket.
//
for(;;) {
if (mReplayMessageQueue.empty()) {
// No recorded messages available, so look for chat messages
// only from <mSocket>.
//
int RecvStatus = GetMsgNetwork(msgBuf, SenderAddress);
if (RecvStatus == 0) {
// No recorded messages, and no live messages, so return 0.
return 0;
}
if (msgBuf.Header.MsgId == CHAT_MSG_ID) {
return RecvStatus;
}
// If we get here, there is a live message but it is a
// multiplayer aircraft position so we ignore it while
// replaying.
//
}
else {
// Replay recorded message, unless it is a chat message.
//
std::shared_ptr<std::vector<char>> replayMessage = mReplayMessageQueue.front();
mReplayMessageQueue.pop_front();
assert(replayMessage->size() <= sizeof(msgBuf));
int length = replayMessage->size();
memcpy(&msgBuf.Msg, &replayMessage->front(), length);
// Don't return chat messages.
if (msgBuf.Header.MsgId != CHAT_MSG_ID) {
SG_LOG(SG_NETWORK, SG_INFO,
"replaying message length=" << replayMessage->size()
<< ". num remaining messages=" << mReplayMessageQueue.size()
);
return length;
}
}
}
}
else {
int length = GetMsgNetwork(msgBuf, SenderAddress);
// Make raw incoming packet available to recording code.
if (length) {
std::shared_ptr<std::vector<char>> data( new std::vector<char>(length));
memcpy( &data->front(), msgBuf.Msg, length);
mRecordMessageQueue.push_back(data);
}
return length;
}
}
//////////////////////////////////////////////////////////////////////
//
// Name: ProcessData
@ -1721,47 +1843,12 @@ FGMultiplayMgr::update(double dt)
//////////////////////////////////////////////////
ssize_t bytes;
do {
MsgBuf msgBuf;
//////////////////////////////////////////////////
// Although the recv call asks for
// MAX_PACKET_SIZE of data, the number of bytes
// returned will only be that of the next
// packet waiting to be processed.
//////////////////////////////////////////////////
MsgBuf msgBuf;
simgear::IPAddress SenderAddress;
int RecvStatus = mSocket->recvfrom(msgBuf.Msg, sizeof(msgBuf.Msg), 0,
&SenderAddress);
//////////////////////////////////////////////////
// no Data received
//////////////////////////////////////////////////
if (RecvStatus == 0)
break;
// socket error reported?
// errno isn't thread-safe - so only check its value when
// socket return status < 0 really indicates a failure.
if ((RecvStatus < 0)&&
((errno == EAGAIN) || (errno == 0))) // MSVC output "NoError" otherwise
{
// ignore "normal" errors
int RecvStatus = GetMsg(msgBuf, SenderAddress);
if (RecvStatus == 0) {
break;
}
if (RecvStatus<0)
{
#ifdef _WIN32
if (::WSAGetLastError() != WSAEWOULDBLOCK) // this is normal on a receive when there is no data
{
// with Winsock the error will not be the actual problem.
SG_LOG(SG_NETWORK, SG_INFO, "FGMultiplayMgr::MP_ProcessData - Unable to receive data. WSAGetLastError=" << ::WSAGetLastError());
}
#else
SG_LOG(SG_NETWORK, SG_DEBUG, "FGMultiplayMgr::MP_ProcessData - Unable to receive data. "
<< strerror(errno) << "(errno " << errno << ")");
#endif
break;
}
// status is positive: bytes received
bytes = (ssize_t) RecvStatus;
if (bytes <= static_cast<ssize_t>(sizeof(T_MsgHdr))) {
@ -1769,16 +1856,11 @@ FGMultiplayMgr::update(double dt)
<< "received message with insufficient data" );
break;
}
//////////////////////////////////////////////////
// Read header
//////////////////////////////////////////////////
T_MsgHdr* MsgHdr = msgBuf.msgHdr();
MsgHdr->Magic = XDR_decode_uint32 (MsgHdr->Magic);
MsgHdr->Version = XDR_decode_uint32 (MsgHdr->Version);
MsgHdr->MsgId = XDR_decode_uint32 (MsgHdr->MsgId);
MsgHdr->MsgLen = XDR_decode_uint32 (MsgHdr->MsgLen);
MsgHdr->ReplyPort = XDR_decode_uint32 (MsgHdr->ReplyPort);
MsgHdr->Callsign[MAX_CALLSIGN_LEN -1] = '\0';
if (MsgHdr->Magic != MSG_MAGIC) {
SG_LOG(SG_NETWORK, SG_INFO, "FGMultiplayMgr::MP_ProcessData - "
<< "message has invalid magic number!" );
@ -2265,6 +2347,24 @@ FGMultiplayMgr::ProcessPosMsg(const FGMultiplayMgr::MsgBuf& Msg,
mp = addMultiplayer(MsgHdr->Callsign, PosMsg->Model, fallback_model_index);
mp->addMotionInfo(motionInfo, stamp);
} // FGMultiplayMgr::ProcessPosMsg()
std::shared_ptr<std::vector<char>> FGMultiplayMgr::popMessageHistory()
{
if (mRecordMessageQueue.empty()) {
return nullptr;
}
std::shared_ptr<std::vector<char>> ret = mRecordMessageQueue.front();
mRecordMessageQueue.pop_front();
return ret;
}
void FGMultiplayMgr::pushMessageHistory(std::shared_ptr<std::vector<char>> message)
{
mReplayMessageQueue.push_back(message);
}
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

View file

@ -34,6 +34,7 @@
const int MIN_MP_PROTOCOL_VERSION = 1;
const int MAX_MP_PROTOCOL_VERSION = 2;
#include <deque>
#include <string>
#include <vector>
#include <memory>
@ -69,6 +70,10 @@ public:
// receiver
FGAIMultiplayer* getMultiplayer(const std::string& callsign);
std::shared_ptr<vector<char>> popMessageHistory();
void pushMessageHistory(std::shared_ptr<vector<char>> message);
private:
friend class MPPropertyListener;
@ -100,6 +105,8 @@ private:
long stamp);
void ProcessChatMsg(const MsgBuf& Msg, const simgear::IPAddress& SenderAddress);
bool isSane(const FGExternalMotionData& motionInfo);
int GetMsgNetwork(MsgBuf& msgBuf, simgear::IPAddress& SenderAddress);
int GetMsg(MsgBuf& msgBuf, simgear::IPAddress& SenderAddress);
/// maps from the callsign string to the FGAIMultiplayer
typedef std::map<std::string, SGSharedPtr<FGAIMultiplayer> > MultiPlayerMap;
@ -120,6 +127,7 @@ private:
SGPropertyNode *pMultiPlayDebugLevel;
SGPropertyNode *pMultiPlayRange;
SGPropertyNode *pMultiPlayTransmitPropertyBase;
SGPropertyNode *pReplayState;
typedef std::map<unsigned int, const struct IdPropertyList*> PropertyDefinitionMap;
PropertyDefinitionMap mPropertyDefinition;
@ -130,6 +138,9 @@ private:
double mDt; // reciprocal of /sim/multiplay/tx-rate-hz
double mTimeUntilSend;
std::deque<std::shared_ptr<std::vector<char>>> mRecordMessageQueue;
std::deque<std::shared_ptr<std::vector<char>>> mReplayMessageQueue;
};
#endif