Refactoring of record/replay code.
The main FGReplay subsystem class now contains just the public API for record/replay, plus a pointer to instance of new ReplayInternal implementation class. Also moved some Continuous-specific code into new source file. src/Aircraft/CMakeLists.txt Updated for new files. src/Aircraft/continuous.cxx src/Aircraft/continuous.hxx New, code specific to Continuous record/replay. src/Aircraft/flightrecorder.hxx Minor changes. src/Aircraft/replay-internal.cxx src/Aircraft/replay-internal.hxx New, contains implementation of record/replay. src/Aircraft/replay.cxx src/Aircraft/replay.hxx Modified to be just concerned with public record/replay API. The implementation now defers to the ReplayInternal instance. Added support for auto create-video when loading Continuous recording. src/Main/fg_init.cxx resetStatisticsProperties() is now in FGReplay, not FGReplayData - the latter is not in the replay.hxx header any more.
This commit is contained in:
parent
c1a34ac4ce
commit
6d3254647a
9 changed files with 3375 additions and 2855 deletions
|
@ -4,18 +4,22 @@ set(SOURCES
|
|||
controls.cxx
|
||||
replay.cxx
|
||||
flightrecorder.cxx
|
||||
FlightHistory.cxx
|
||||
initialstate.cxx
|
||||
FlightHistory.cxx
|
||||
initialstate.cxx
|
||||
AircraftPerformance.cxx
|
||||
replay-internal.cxx
|
||||
continuous.cxx
|
||||
)
|
||||
|
||||
set(HEADERS
|
||||
controls.hxx
|
||||
replay.hxx
|
||||
flightrecorder.hxx
|
||||
FlightHistory.hxx
|
||||
initialstate.hxx
|
||||
FlightHistory.hxx
|
||||
initialstate.hxx
|
||||
AircraftPerformance.hxx
|
||||
continuous.hxx
|
||||
replay-internal.hxx
|
||||
)
|
||||
|
||||
|
||||
|
|
865
src/Aircraft/continuous.cxx
Normal file
865
src/Aircraft/continuous.cxx
Normal file
|
@ -0,0 +1,865 @@
|
|||
#include "continuous.hxx"
|
||||
|
||||
#include <Aircraft/flightrecorder.hxx>
|
||||
#include <Main/fg_props.hxx>
|
||||
#include <MultiPlayer/mpmessages.hxx>
|
||||
#include <Viewer/FGEventHandler.hxx>
|
||||
#include <Viewer/renderer.hxx>
|
||||
#include <Viewer/viewmgr.hxx>
|
||||
|
||||
#include <simgear/io/iostreams/zlibstream.hxx>
|
||||
#include <simgear/props/props_io.hxx>
|
||||
#include <simgear/structure/commands.hxx>
|
||||
|
||||
#include <osgViewer/ViewerBase>
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
Continuous::Continuous(std::shared_ptr<FGFlightRecorder> flight_recorder)
|
||||
:
|
||||
m_flight_recorder(flight_recorder)
|
||||
{
|
||||
SGPropertyNode* record_continuous = fgGetNode("/sim/replay/record-continuous", true);
|
||||
SGPropertyNode* fdm_initialized = fgGetNode("/sim/signals/fdm-initialized", true);
|
||||
record_continuous->addChangeListener(this, true /*initial*/);
|
||||
fdm_initialized->addChangeListener(this, true /*initial*/);
|
||||
}
|
||||
|
||||
// Reads binary data from a stream into an instance of a type.
|
||||
template<typename T>
|
||||
static void readRaw(std::istream& in, T& data)
|
||||
{
|
||||
in.read(reinterpret_cast<char*>(&data), sizeof(data));
|
||||
}
|
||||
|
||||
// Writes instance of a type as binary data to a stream.
|
||||
template<typename T>
|
||||
static void writeRaw(std::ostream& out, const T& data)
|
||||
{
|
||||
out.write(reinterpret_cast<const char*>(&data), sizeof(data));
|
||||
}
|
||||
|
||||
// Reads uncompressed vector<char> from file. Throws if length field is longer
|
||||
// than <max_length>.
|
||||
template<typename SizeType>
|
||||
static SizeType VectorRead(std::istream& in, std::vector<char>& out, uint32_t max_length=(1<<31))
|
||||
{
|
||||
SizeType length;
|
||||
readRaw(in, length);
|
||||
if (sizeof(length) + length > max_length)
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_ALERT, "recording data vector too long."
|
||||
<< " max_length=" << max_length
|
||||
<< " sizeof(length)=" << sizeof(length)
|
||||
<< " length=" << length
|
||||
);
|
||||
throw std::runtime_error("Failed to read vector in recording");
|
||||
}
|
||||
out.resize(length);
|
||||
in.read(&out.front(), length);
|
||||
return sizeof(length) + length;
|
||||
}
|
||||
|
||||
static int16_t read_int16(std::istream& in, size_t& pos)
|
||||
{
|
||||
int16_t a;
|
||||
readRaw(in, a);
|
||||
pos += sizeof(a);
|
||||
return a;
|
||||
}
|
||||
static std::string read_string(std::istream& in, size_t& pos)
|
||||
{
|
||||
int16_t length = read_int16(in, pos);
|
||||
std::vector<char> path(length);
|
||||
in.read(&path[0], length);
|
||||
pos += length;
|
||||
std::string ret(&path[0], length);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int PropertiesWrite(SGPropertyNode* root, std::ostream& out)
|
||||
{
|
||||
stringstream buffer;
|
||||
writeProperties(buffer, root, true /*write_all*/);
|
||||
uint32_t buffer_len = buffer.str().size() + 1;
|
||||
writeRaw(out, buffer_len);
|
||||
out.write(buffer.str().c_str(), buffer_len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Reads extra-property change items in next <length> bytes. Throws if we don't
|
||||
// exactly read <length> bytes.
|
||||
static void ReadFGReplayDataExtraProperties(std::istream& in, FGReplayData* replay_data, uint32_t length)
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_BULK, "reading extra-properties. length=" << length);
|
||||
size_t pos=0;
|
||||
for(;;)
|
||||
{
|
||||
if (pos == length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (pos > length)
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_ALERT, "Overrun while reading extra-properties:"
|
||||
" length=" << length << ": pos=" << pos);
|
||||
in.setstate(std::ios_base::failbit);
|
||||
break;
|
||||
}
|
||||
SG_LOG(SG_SYSTEMS, SG_BULK, "length=" << length<< " pos=" << pos);
|
||||
std::string path = read_string(in, pos);
|
||||
if (path == "")
|
||||
{
|
||||
path = read_string(in, pos);
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "property deleted: " << path);
|
||||
replay_data->replay_extra_property_removals.push_back(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string value = read_string(in, pos);
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "property changed: " << path << "=" << value);
|
||||
replay_data->replay_extra_property_changes[path] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool ReadFGReplayData2(
|
||||
std::istream& in,
|
||||
SGPropertyNode* config,
|
||||
bool load_signals,
|
||||
bool load_multiplayer,
|
||||
bool load_extra_properties,
|
||||
FGReplayData* ret
|
||||
)
|
||||
{
|
||||
ret->raw_data.resize(0);
|
||||
for (auto data: config->getChildren("data"))
|
||||
{
|
||||
const char* data_type = data->getStringValue();
|
||||
SG_LOG(SG_SYSTEMS, SG_BULK, "in.tellg()=" << in.tellg() << " data_type=" << data_type);
|
||||
uint32_t length;
|
||||
readRaw(in, length);
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "length=" << length);
|
||||
if (!in) break;
|
||||
if (load_signals && !strcmp(data_type, "signals"))
|
||||
{
|
||||
ret->raw_data.resize(length);
|
||||
in.read(&ret->raw_data.front(), ret->raw_data.size());
|
||||
}
|
||||
else if (load_multiplayer && !strcmp(data_type, "multiplayer"))
|
||||
{
|
||||
/* Multiplayer information is a vector of vectors. */
|
||||
ret->multiplayer_messages.clear();
|
||||
uint32_t pos = 0;
|
||||
for(;;)
|
||||
{
|
||||
assert(pos <= length);
|
||||
if (pos == length) break;
|
||||
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"))
|
||||
{
|
||||
ReadFGReplayDataExtraProperties(in, ret, length);
|
||||
}
|
||||
else
|
||||
{
|
||||
SG_LOG(SG_GENERAL, SG_BULK, "Skipping unrecognised/unwanted data: " << data_type);
|
||||
in.seekg(length, std::ios_base::cur);
|
||||
}
|
||||
if (!in) break;
|
||||
}
|
||||
if (!in)
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read fgtape data");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<FGReplayData> ReadFGReplayData(
|
||||
std::ifstream& in,
|
||||
size_t pos,
|
||||
SGPropertyNode* config,
|
||||
bool load_signals,
|
||||
bool load_multiplayer,
|
||||
bool load_extra_properties,
|
||||
int in_compression
|
||||
)
|
||||
{
|
||||
/* Need to clear any eof bit, otherwise seekg() will not work (which is
|
||||
pretty unhelpful). E.g. see:
|
||||
https://stackoverflow.com/questions/16364301/whats-wrong-with-the-ifstream-seekg
|
||||
*/
|
||||
SG_LOG(SG_SYSTEMS, SG_BULK, "reading frame. pos=" << pos);
|
||||
in.clear();
|
||||
in.seekg(pos);
|
||||
|
||||
std::unique_ptr<FGReplayData> ret(new FGReplayData);
|
||||
|
||||
readRaw(in, ret->sim_time);
|
||||
if (!in)
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read fgtape frame at offset " << pos);
|
||||
return nullptr;
|
||||
}
|
||||
bool ok;
|
||||
if (in_compression)
|
||||
{
|
||||
uint8_t flags;
|
||||
uint32_t compressed_size;
|
||||
in.read((char*) &flags, sizeof(flags));
|
||||
in.read((char*) &compressed_size, sizeof(compressed_size));
|
||||
simgear::ZlibDecompressorIStream in_decompress(in, SGPath(), simgear::ZLibCompressionFormat::ZLIB_RAW);
|
||||
ok = ReadFGReplayData2(in_decompress, config, load_signals, load_multiplayer, load_extra_properties, ret.get());
|
||||
}
|
||||
else
|
||||
{
|
||||
ok = ReadFGReplayData2(in, config, load_signals, load_multiplayer, load_extra_properties, ret.get());
|
||||
}
|
||||
if (!ok)
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read fgtape frame at offset " << pos);
|
||||
return nullptr;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
// streambuf that compresses using deflate().
|
||||
struct compression_streambuf : std::streambuf
|
||||
{
|
||||
compression_streambuf(
|
||||
std::ostream& out,
|
||||
size_t buffer_uncompressed_size,
|
||||
size_t buffer_compressed_size
|
||||
)
|
||||
:
|
||||
std::streambuf(),
|
||||
out(out),
|
||||
buffer_uncompressed(new char[buffer_uncompressed_size]),
|
||||
buffer_uncompressed_size(buffer_uncompressed_size),
|
||||
buffer_compressed(new char[buffer_compressed_size]),
|
||||
buffer_compressed_size(buffer_compressed_size)
|
||||
{
|
||||
zstream.zalloc = nullptr;
|
||||
zstream.zfree = nullptr;
|
||||
zstream.opaque = nullptr;
|
||||
|
||||
zstream.next_in = nullptr;
|
||||
zstream.avail_in = 0;
|
||||
|
||||
zstream.next_out = (unsigned char*) &buffer_compressed[0];
|
||||
zstream.avail_out = buffer_compressed_size;
|
||||
|
||||
int e = deflateInit2(
|
||||
&zstream,
|
||||
Z_DEFAULT_COMPRESSION,
|
||||
Z_DEFLATED,
|
||||
-15 /*windowBits*/,
|
||||
8 /*memLevel*/,
|
||||
Z_DEFAULT_STRATEGY
|
||||
);
|
||||
if (e != Z_OK)
|
||||
{
|
||||
throw std::runtime_error("deflateInit2() failed");
|
||||
}
|
||||
// We leave space for one character to simplify overflow().
|
||||
setp(&buffer_uncompressed[0], &buffer_uncompressed[0] + buffer_uncompressed_size - 1);
|
||||
}
|
||||
|
||||
// Flush compressed data to .out and reset zstream.next_out.
|
||||
void _flush()
|
||||
{
|
||||
// Send all data in .buffer_compressed to .out.
|
||||
size_t n = (char*) zstream.next_out - &buffer_compressed[0];
|
||||
out.write(&buffer_compressed[0], n);
|
||||
zstream.next_out = (unsigned char*) &buffer_compressed[0];
|
||||
zstream.avail_out = buffer_compressed_size;
|
||||
}
|
||||
|
||||
// Compresses specified bytes from buffer_uncompressed into
|
||||
// buffer_compressed, flushing to .out as necessary. Returns true if we get
|
||||
// EOF writing to .out.
|
||||
bool _deflate(size_t n, bool flush)
|
||||
{
|
||||
assert(this->pbase() == &buffer_uncompressed[0]);
|
||||
zstream.next_in = (unsigned char*) &buffer_uncompressed[0];
|
||||
zstream.avail_in = n;
|
||||
for(;;)
|
||||
{
|
||||
if (!flush && !zstream.avail_in) break;
|
||||
if (!zstream.avail_out) _flush();
|
||||
int e = deflate(&zstream, (!zstream.avail_in && flush) ? Z_FINISH : Z_NO_FLUSH);
|
||||
if (e != Z_OK && e != Z_STREAM_END)
|
||||
{
|
||||
throw std::runtime_error("zip_deflate() failed");
|
||||
}
|
||||
if (e == Z_STREAM_END) break;
|
||||
}
|
||||
if (flush) _flush();
|
||||
// We leave space for one character to simplify overflow().
|
||||
setp(&buffer_uncompressed[0], &buffer_uncompressed[0] + buffer_uncompressed_size - 1);
|
||||
if (!out) return true; // EOF.
|
||||
return false;
|
||||
}
|
||||
|
||||
int overflow(int c) override
|
||||
{
|
||||
// We've deliberately left space for one character, into which we write <c>.
|
||||
assert(this->pptr() == &buffer_uncompressed[0] + buffer_uncompressed_size - 1);
|
||||
*this->pptr() = (char) c;
|
||||
if (_deflate(buffer_uncompressed_size, false /*flush*/)) return EOF;
|
||||
return c;
|
||||
}
|
||||
|
||||
int sync() override
|
||||
{
|
||||
_deflate(pptr() - &buffer_uncompressed[0], true /*flush*/);
|
||||
return 0;
|
||||
}
|
||||
|
||||
~compression_streambuf()
|
||||
{
|
||||
deflateEnd(&zstream);
|
||||
}
|
||||
|
||||
std::ostream& out;
|
||||
z_stream zstream;
|
||||
std::unique_ptr<char[]> buffer_uncompressed;
|
||||
size_t buffer_uncompressed_size;
|
||||
std::unique_ptr<char[]> buffer_compressed;
|
||||
size_t buffer_compressed_size;
|
||||
};
|
||||
|
||||
|
||||
// Accepts uncompressed data via .write(), operator<< etc, and writes
|
||||
// compressed data to the supplied std::ostream.
|
||||
struct compression_ostream : std::ostream
|
||||
{
|
||||
compression_ostream(
|
||||
std::ostream& out,
|
||||
size_t buffer_uncompressed_size,
|
||||
size_t buffer_compressed_size
|
||||
)
|
||||
:
|
||||
std::ostream(&streambuf),
|
||||
streambuf(out, buffer_uncompressed_size, buffer_compressed_size)
|
||||
{
|
||||
}
|
||||
|
||||
compression_streambuf streambuf;
|
||||
};
|
||||
|
||||
|
||||
static void writeFrame2(FGReplayData* r, std::ostream& out, SGPropertyNode_ptr config)
|
||||
{
|
||||
for (auto data: config->getChildren("data"))
|
||||
{
|
||||
const char* data_type = data->getStringValue();
|
||||
if (!strcmp(data_type, "signals"))
|
||||
{
|
||||
uint32_t signals_size = r->raw_data.size();
|
||||
writeRaw(out, signals_size);
|
||||
out.write(&r->raw_data.front(), r->raw_data.size());
|
||||
}
|
||||
else if (!strcmp(data_type, "multiplayer"))
|
||||
{
|
||||
uint32_t length = 0;
|
||||
for (auto message: r->multiplayer_messages)
|
||||
{
|
||||
length += sizeof(uint16_t) + message->size();
|
||||
}
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "data_type=" << data_type << " out.tellp()=" << out.tellp()
|
||||
<< " length=" << length);
|
||||
writeRaw(out, length);
|
||||
for (auto message: r->multiplayer_messages)
|
||||
{
|
||||
uint16_t message_size = message->size();
|
||||
writeRaw(out, message_size);
|
||||
out.write(&message->front(), message_size);
|
||||
}
|
||||
}
|
||||
else if (!strcmp(data_type, "extra-properties"))
|
||||
{
|
||||
uint32_t length = r->extra_properties.size();
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "data_type=" << data_type << " out.tellp()=" << out.tellp()
|
||||
<< " length=" << length);
|
||||
writeRaw(out, length);
|
||||
out.write(&r->extra_properties[0], length);
|
||||
}
|
||||
else
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_ALERT, "unrecognised data_type=" << data_type);
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool continuousWriteFrame(Continuous& continuous, FGReplayData* r, std::ostream& out, SGPropertyNode_ptr config)
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_BULK, "writing frame."
|
||||
<< " out.tellp()=" << out.tellp()
|
||||
<< " r->sim_time=" << r->sim_time
|
||||
);
|
||||
// Don't write frame if no data to write.
|
||||
//bool r_has_data = false;
|
||||
bool has_signals = false;
|
||||
bool has_multiplayer = false;
|
||||
bool has_extra_properties = false;
|
||||
for (auto data: config->getChildren("data"))
|
||||
{
|
||||
const char* data_type = data->getStringValue();
|
||||
if (!strcmp(data_type, "signals"))
|
||||
{
|
||||
has_signals = true;
|
||||
}
|
||||
else if (!strcmp(data_type, "multiplayer"))
|
||||
{
|
||||
if (!r->multiplayer_messages.empty())
|
||||
{
|
||||
has_multiplayer = true;
|
||||
}
|
||||
}
|
||||
else if (!strcmp(data_type, "extra-properties"))
|
||||
{
|
||||
if (!r->extra_properties.empty())
|
||||
{
|
||||
has_extra_properties = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_ALERT, "unrecognised data_type=" << data_type);
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
if (!has_signals && !has_multiplayer && !has_extra_properties)
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Not writing frame because no data to write");
|
||||
return true;
|
||||
}
|
||||
|
||||
writeRaw(out, r->sim_time);
|
||||
|
||||
if (continuous.m_out_compression)
|
||||
{
|
||||
uint8_t flags = 0;
|
||||
if (has_signals) flags |= 1;
|
||||
if (has_multiplayer) flags |= 2;
|
||||
if (has_extra_properties) flags |= 4;
|
||||
out.write((char*) &flags, sizeof(flags));
|
||||
|
||||
/* We need to first write the size of the compressed data so compress
|
||||
to a temporary ostringstream first. */
|
||||
std::ostringstream compressed;
|
||||
compression_ostream out_compressing(compressed, 1024, 1024);
|
||||
writeFrame2(r, out_compressing, config);
|
||||
out_compressing.flush();
|
||||
|
||||
uint32_t compressed_size = compressed.str().size();
|
||||
out.write((char*) &compressed_size, sizeof(compressed_size));
|
||||
out.write((char*) compressed.str().c_str(), compressed.str().size());
|
||||
}
|
||||
else
|
||||
{
|
||||
writeFrame2(r, out, config);
|
||||
}
|
||||
bool ok = true;
|
||||
if (!out) ok = false;
|
||||
return ok;
|
||||
}
|
||||
|
||||
SGPropertyNode_ptr continuousWriteHeader(
|
||||
Continuous& continuous,
|
||||
FGFlightRecorder* flight_recorder,
|
||||
std::ofstream& out,
|
||||
const SGPath& path,
|
||||
FGTapeType tape_type
|
||||
)
|
||||
{
|
||||
continuous.m_out_compression = fgGetInt("/sim/replay/record-continuous-compression");
|
||||
SGPropertyNode_ptr config = saveSetup(NULL /*Extra*/, path, 0 /*Duration*/,
|
||||
tape_type, continuous.m_out_compression);
|
||||
SGPropertyNode* signals = config->getNode("signals", true /*create*/);
|
||||
flight_recorder->getConfig(signals);
|
||||
|
||||
out.open(path.c_str(), std::ofstream::binary | std::ofstream::trunc);
|
||||
out.write(FlightRecorderFileMagic, strlen(FlightRecorderFileMagic)+1);
|
||||
PropertiesWrite(config, out);
|
||||
|
||||
if (tape_type == FGTapeType_CONTINUOUS)
|
||||
{
|
||||
// Ensure that all recorded properties are written in first frame.
|
||||
//
|
||||
flight_recorder->resetExtraProperties();
|
||||
}
|
||||
|
||||
if (!out)
|
||||
{
|
||||
out.close();
|
||||
config = nullptr;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
bool replayContinuousInternal(
|
||||
Continuous& continuous,
|
||||
FGFlightRecorder* recorder,
|
||||
double time,
|
||||
size_t offset,
|
||||
size_t offset_old,
|
||||
bool replay_signals,
|
||||
bool replay_multiplayer,
|
||||
bool replay_extra_properties,
|
||||
int* xpos,
|
||||
int* ypos,
|
||||
int* xsize,
|
||||
int* ysize
|
||||
)
|
||||
{
|
||||
std::unique_ptr<FGReplayData> replay_data = ReadFGReplayData(
|
||||
continuous.m_in,
|
||||
offset,
|
||||
continuous.m_in_config,
|
||||
replay_signals,
|
||||
replay_multiplayer,
|
||||
replay_extra_properties,
|
||||
continuous.m_in_compression
|
||||
);
|
||||
if (!replay_data)
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to read fgtape frame at offset=" << offset << " time=" << time);
|
||||
return false;
|
||||
}
|
||||
assert(replay_data.get());
|
||||
std::unique_ptr<FGReplayData> replay_data_old;
|
||||
if (offset_old)
|
||||
{
|
||||
replay_data_old = ReadFGReplayData(
|
||||
continuous.m_in,
|
||||
offset_old,
|
||||
continuous.m_in_config,
|
||||
replay_signals,
|
||||
replay_multiplayer,
|
||||
replay_extra_properties,
|
||||
continuous.m_in_compression
|
||||
);
|
||||
}
|
||||
if (replay_extra_properties) SG_LOG(SG_SYSTEMS, SG_BULK,
|
||||
"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()
|
||||
);
|
||||
recorder->replay(time, replay_data.get(), replay_data_old.get(), xpos, ypos, xsize, ysize);
|
||||
return true;
|
||||
}
|
||||
|
||||
// fixme: this is duplicated in replay.cxx.
|
||||
static void popupTip(const char* message, int delay)
|
||||
{
|
||||
SGPropertyNode_ptr args(new SGPropertyNode);
|
||||
args->setStringValue("label", message);
|
||||
args->setIntValue("delay", delay);
|
||||
globals->get_commands()->execute("show-message", args);
|
||||
}
|
||||
|
||||
bool replayContinuous(FGReplayInternal& self, double time)
|
||||
{
|
||||
// We need to detect whether replay() updates the values for the main
|
||||
// window's position and size.
|
||||
int xpos0 = self.m_sim_startup_xpos->getIntValue();
|
||||
int ypos0 = self.m_sim_startup_xpos->getIntValue();
|
||||
int xsize0 = self.m_sim_startup_xpos->getIntValue();
|
||||
int ysize0 = self.m_sim_startup_xpos->getIntValue();
|
||||
|
||||
int xpos = xpos0;
|
||||
int ypos = ypos0;
|
||||
int xsize = xsize0;
|
||||
int ysize = ysize0;
|
||||
|
||||
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 = self.m_continuous->m_in_frame_time_last;
|
||||
|
||||
if (time < self.m_continuous->m_in_time_last)
|
||||
{
|
||||
// We have gone backwards, e.g. user has clicked on the back
|
||||
// buttons in the Replay dialogue.
|
||||
//
|
||||
|
||||
if (self.m_continuous->m_in_multiplayer)
|
||||
{
|
||||
// Continuous recording has multiplayer data, so replay recent
|
||||
// ones.
|
||||
//
|
||||
t_begin = time - multiplayer_recent;
|
||||
}
|
||||
|
||||
if (self.m_continuous->m_in_extra_properties)
|
||||
{
|
||||
// Continuous recording has property changes. we need to replay
|
||||
// all property changes from the beginning.
|
||||
//
|
||||
t_begin = -1;
|
||||
}
|
||||
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Have gone backwards."
|
||||
<< " m_in_time_last=" << self.m_continuous->m_in_time_last
|
||||
<< " time=" << time
|
||||
<< " t_begin=" << t_begin
|
||||
<< " m_in_extra_properties=" << self.m_continuous->m_in_extra_properties
|
||||
);
|
||||
}
|
||||
|
||||
// Prepare to replay signals from Continuoue recording file. We want
|
||||
// to find a pair of frames that straddle the requested <time> so that
|
||||
// we can interpolate.
|
||||
//
|
||||
auto p = self.m_continuous->m_in_time_to_frameinfo.lower_bound(time);
|
||||
bool ret = false;
|
||||
|
||||
size_t offset;
|
||||
size_t offset_prev = 0;
|
||||
|
||||
if (p == self.m_continuous->m_in_time_to_frameinfo.end())
|
||||
{
|
||||
// We are at end of recording; replay last frame.
|
||||
--p;
|
||||
offset = p->second.offset;
|
||||
ret = true;
|
||||
}
|
||||
else if (p->first > time)
|
||||
{
|
||||
// Look for preceding item.
|
||||
if (p == self.m_continuous->m_in_time_to_frameinfo.begin())
|
||||
{
|
||||
// <time> is before beginning of recording.
|
||||
offset = p->second.offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Interpolate between pair of items that straddle <time>.
|
||||
auto prev = p;
|
||||
--prev;
|
||||
offset_prev = prev->second.offset;
|
||||
offset = p->second.offset;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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.
|
||||
//
|
||||
// todo: figure out how to interpolate view position/direction, to
|
||||
// smooth things out if replay fps is different from record fps e.g.
|
||||
// with new fixed dt support.
|
||||
//
|
||||
for (auto p_before = self.m_continuous->m_in_time_to_frameinfo.upper_bound(t_begin);
|
||||
p_before != self.m_continuous->m_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->m_in_time_last=" << self.m_continuous->m_in_time_last
|
||||
<< " m_continuous->m_in_frame_time_last=" << self.m_continuous->m_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)
|
||||
{
|
||||
bool ok = replayContinuousInternal(
|
||||
*self.m_continuous,
|
||||
self.m_flight_recorder.get(),
|
||||
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
|
||||
);
|
||||
if (!ok)
|
||||
{
|
||||
if (!self.m_replay_error->getBoolValue())
|
||||
{
|
||||
popupTip("Replay failed: cannot read fgtape data", 10);
|
||||
self.m_replay_error->setBoolValue(true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Now replay signals, interpolating between frames atoffset_prev and
|
||||
offset. */
|
||||
bool ok = replayContinuousInternal(
|
||||
*self.m_continuous,
|
||||
self.m_flight_recorder.get(),
|
||||
time,
|
||||
offset,
|
||||
offset_prev /*offset_old*/,
|
||||
true /*replay_signals*/,
|
||||
true /*replay_multiplayer*/,
|
||||
true /*replay_extra_properties*/,
|
||||
&xpos,
|
||||
&ypos,
|
||||
&xsize,
|
||||
&ysize
|
||||
);
|
||||
if (!ok)
|
||||
{
|
||||
if (!self.m_replay_error->getBoolValue())
|
||||
{
|
||||
popupTip("Replay failed: cannot read fgtape data", 10);
|
||||
self.m_replay_error->setBoolValue(true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (0
|
||||
|| xpos != xpos0
|
||||
|| ypos != ypos0
|
||||
|| xsize != xsize0
|
||||
|| ysize != ysize0
|
||||
)
|
||||
{
|
||||
// Move/resize the main window to reflect the updated values.
|
||||
globals->get_props()->setIntValue("/sim/startup/xpos", xpos);
|
||||
globals->get_props()->setIntValue("/sim/startup/ypos", ypos);
|
||||
globals->get_props()->setIntValue("/sim/startup/xsize", xsize);
|
||||
globals->get_props()->setIntValue("/sim/startup/ysize", ysize);
|
||||
|
||||
osgViewer::ViewerBase* viewer_base = globals->get_renderer()->getViewerBase();
|
||||
if (viewer_base)
|
||||
{
|
||||
std::vector<osgViewer::GraphicsWindow*> windows;
|
||||
viewer_base->getWindows(windows);
|
||||
osgViewer::GraphicsWindow* window = windows[0];
|
||||
|
||||
// We use FGEventHandler::setWindowRectangle() to move the
|
||||
// window, because it knows how to convert from window work-area
|
||||
// coordinates to window-including-furniture coordinates.
|
||||
//
|
||||
flightgear::FGEventHandler* event_handler = globals->get_renderer()->getEventHandler();
|
||||
event_handler->setWindowRectangleInteriorWithCorrection(window, xpos, ypos, xsize, ysize);
|
||||
}
|
||||
}
|
||||
|
||||
self.m_continuous->m_in_time_last = time;
|
||||
self.m_continuous->m_in_frame_time_last = p->first;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* SGPropertyChangeListener callback for detecing when FDM is initialised and
|
||||
for when continuous recording is started or stopped. */
|
||||
void Continuous::valueChanged(SGPropertyNode * node)
|
||||
{
|
||||
bool prop_continuous = fgGetBool("/sim/replay/record-continuous");
|
||||
bool prop_fdm = fgGetBool("/sim/signals/fdm-initialized");
|
||||
|
||||
bool continuous = prop_continuous && prop_fdm;
|
||||
if (continuous == (m_out.is_open() ? true : false))
|
||||
{
|
||||
// No change.
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_out.is_open())
|
||||
{
|
||||
// Stop existing continuous recording.
|
||||
SG_LOG(SG_SYSTEMS, SG_ALERT, "Stopping continuous recording");
|
||||
m_out.close();
|
||||
popupTip("Continuous record to file stopped", 5 /*delay*/);
|
||||
}
|
||||
|
||||
if (continuous)
|
||||
{
|
||||
// Start continuous recording.
|
||||
SGPath path_timeless;
|
||||
SGPath path = makeSavePath(FGTapeType_CONTINUOUS, &path_timeless);
|
||||
m_out_config = continuousWriteHeader(
|
||||
*this,
|
||||
m_flight_recorder.get(),
|
||||
m_out,
|
||||
path,
|
||||
FGTapeType_CONTINUOUS
|
||||
);
|
||||
if (!m_out_config)
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to start continuous recording");
|
||||
popupTip("Continuous record to file failed to start", 5 /*delay*/);
|
||||
return;
|
||||
}
|
||||
|
||||
SG_LOG(SG_SYSTEMS, SG_ALERT, "Starting continuous recording");
|
||||
|
||||
/* Make a convenience link to the recording. E.g.
|
||||
harrier-gr3-continuous.fgtape -> harrier-gr3-20201224-005034-continuous.fgtape.
|
||||
|
||||
Link destination is in same directory as link so we use leafname
|
||||
path.file(). */
|
||||
path_timeless.remove();
|
||||
bool ok = path_timeless.makeLink(path.file());
|
||||
if (!ok)
|
||||
{
|
||||
SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to create link " << path_timeless.c_str() << " => " << path.file());
|
||||
}
|
||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Starting continuous recording to " << path);
|
||||
if (m_out_compression)
|
||||
{
|
||||
popupTip("Continuous+compressed record to file started", 5 /*delay*/);
|
||||
}
|
||||
else
|
||||
{
|
||||
popupTip("Continuous record to file started", 5 /*delay*/);
|
||||
}
|
||||
}
|
||||
}
|
123
src/Aircraft/continuous.hxx
Normal file
123
src/Aircraft/continuous.hxx
Normal file
|
@ -0,0 +1,123 @@
|
|||
#pragma once
|
||||
|
||||
#include "replay-internal.hxx"
|
||||
|
||||
#include <simgear/props/props.hxx>
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
|
||||
struct Continuous : SGPropertyChangeListener
|
||||
{
|
||||
Continuous(std::shared_ptr<FGFlightRecorder> flight_recorder);
|
||||
|
||||
/* Callback for SGPropertyChangeListener. */
|
||||
void valueChanged(SGPropertyNode * node) override;
|
||||
|
||||
std::shared_ptr<FGFlightRecorder> m_flight_recorder;
|
||||
|
||||
std::ifstream m_in;
|
||||
bool m_in_multiplayer = false;
|
||||
bool m_in_extra_properties = false;
|
||||
std::mutex m_in_time_to_frameinfo_lock;
|
||||
std::map<double, FGFrameInfo> m_in_time_to_frameinfo;
|
||||
SGPropertyNode_ptr m_in_config;
|
||||
double m_in_time_last = 0;
|
||||
double m_in_frame_time_last = 0;
|
||||
|
||||
std::ifstream m_indexing_in;
|
||||
std::streampos m_indexing_pos;
|
||||
|
||||
// Only used for gathering statistics that are then written into
|
||||
// properties.
|
||||
//
|
||||
int m_num_frames_extra_properties = 0;
|
||||
int m_num_frames_multiplayer = 0;
|
||||
|
||||
// For writing Continuous fgtape file.
|
||||
SGPropertyNode_ptr m_out_config;
|
||||
std::ofstream m_out;
|
||||
int m_out_compression = 0;
|
||||
int m_in_compression = 0;
|
||||
};
|
||||
|
||||
// Attempts to load Continuous recording header properties into
|
||||
// <properties>. If in is null we use internal std::fstream, otherwise we
|
||||
// use *in.
|
||||
//
|
||||
// Returns 0 on success, +1 if we may succeed after further download, or -1
|
||||
// if recording is not a Continuous recording.
|
||||
//
|
||||
int loadContinuousHeader(const std::string& path, std::istream* in, SGPropertyNode* properties);
|
||||
|
||||
|
||||
// Reads all or part of a FGReplayData from Continuous file.
|
||||
std::unique_ptr<FGReplayData> ReadFGReplayData(
|
||||
std::ifstream& in,
|
||||
size_t pos,
|
||||
SGPropertyNode* config,
|
||||
bool load_signals,
|
||||
bool load_multiplayer,
|
||||
bool load_extra_properties,
|
||||
int in_compression
|
||||
);
|
||||
|
||||
// Writes one frame of continuous record information.
|
||||
//
|
||||
bool continuousWriteFrame(
|
||||
Continuous& continuous,
|
||||
FGReplayData* r,
|
||||
std::ostream& out,
|
||||
SGPropertyNode_ptr config
|
||||
);
|
||||
|
||||
// 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().
|
||||
//
|
||||
SGPropertyNode_ptr continuousWriteHeader(
|
||||
Continuous& continuous,
|
||||
FGFlightRecorder* m_pRecorder,
|
||||
std::ofstream& out,
|
||||
const SGPath& path,
|
||||
FGTapeType tape_type
|
||||
);
|
||||
|
||||
// Replays one frame from Continuous recording. <offset> and <offset_old>
|
||||
// are offsets in file of frames that are >= and < <time> respectively.
|
||||
// <offset_old> may be 0, in which case it is ignored.
|
||||
//
|
||||
// We load the frame(s) from disc, omitting some data depending on
|
||||
// replay_signals, replay_multiplayer and replay_extra_properties. Then call
|
||||
// m_pRecorder->replay(), which updates the global state.
|
||||
//
|
||||
// Returns true on success, otherwise we failed to read from Continuous
|
||||
// recording.
|
||||
//
|
||||
bool replayContinuousInternal(
|
||||
Continuous& continuous,
|
||||
FGFlightRecorder* recorder,
|
||||
double time,
|
||||
size_t offset,
|
||||
size_t offset_old,
|
||||
bool replay_signals,
|
||||
bool replay_multiplayer,
|
||||
bool replay_extra_properties,
|
||||
int* xpos,
|
||||
int* ypos,
|
||||
int* xsize,
|
||||
int* ysize
|
||||
);
|
||||
|
||||
bool replayContinuous(FGReplayInternal& self, double time);
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <MultiPlayer/multiplaymgr.hxx>
|
||||
#include "replay.hxx"
|
||||
#include "replay-internal.hxx"
|
||||
|
||||
namespace FlightRecorder
|
||||
{
|
||||
|
|
2058
src/Aircraft/replay-internal.cxx
Normal file
2058
src/Aircraft/replay-internal.cxx
Normal file
File diff suppressed because it is too large
Load diff
245
src/Aircraft/replay-internal.hxx
Normal file
245
src/Aircraft/replay-internal.hxx
Normal file
|
@ -0,0 +1,245 @@
|
|||
// replay.hxx - a system to record and replay FlightGear flights
|
||||
//
|
||||
// Written by Curtis Olson, started July 2003.
|
||||
//
|
||||
// Copyright (C) 2003 Curtis L. Olson - http://www.flightgear.org/~curt
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// $Id$
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <mutex>
|
||||
#include <simgear/compiler.h>
|
||||
|
||||
#include <simgear/math/sg_types.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <simgear/structure/subsystem_mgr.hxx>
|
||||
#include <simgear/io/iostreams/gzcontainerfile.hxx>
|
||||
#include <simgear/io/HTTPFileRequest.hxx>
|
||||
|
||||
#include <MultiPlayer/multiplaymgr.hxx>
|
||||
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
|
||||
|
||||
class FGFlightRecorder;
|
||||
|
||||
/** Magic string to verify valid FG flight recorder tapes. */
|
||||
extern const char* const FlightRecorderFileMagic;
|
||||
|
||||
/* Data for a single frame. */
|
||||
struct FGReplayData
|
||||
{
|
||||
double sim_time;
|
||||
|
||||
// Our aircraft state.
|
||||
std::vector<char> raw_data;
|
||||
|
||||
// Incoming multiplayer messages, if any.
|
||||
std::vector<std::shared_ptr<std::vector<char>>> multiplayer_messages;
|
||||
|
||||
// Serialised information about extra property changes, only used when
|
||||
// making a Continuous recording - we write this raw data into frame data
|
||||
// in the Continuous recording file.
|
||||
std::vector<char> extra_properties;
|
||||
|
||||
// Information about extra property changes, only used when replaying. We
|
||||
// populate these when loading a frame.
|
||||
std::map<std::string, std::string> replay_extra_property_changes;
|
||||
std::vector<std::string> replay_extra_property_removals;
|
||||
|
||||
// Updates static statistics defined below.
|
||||
void UpdateStats();
|
||||
|
||||
// Resets out static property nodes; to be called by fgStartNewReset().
|
||||
static void resetStatisticsProperties();
|
||||
|
||||
FGReplayData();
|
||||
~FGReplayData();
|
||||
|
||||
size_t m_bytes_raw_data = 0;
|
||||
size_t m_bytes_multiplayer_messages = 0;
|
||||
size_t m_num_multiplayer_messages = 0;
|
||||
|
||||
// Statistics about replay data, also properties /sim/replay/datastats_*.
|
||||
static size_t s_num;
|
||||
static size_t s_bytes_raw_data;
|
||||
static size_t s_bytes_multiplayer_messages;
|
||||
static size_t s_num_multiplayer_messages;
|
||||
static SGPropertyNode_ptr s_prop_num;
|
||||
static SGPropertyNode_ptr s_prop_bytes_raw_data;
|
||||
static SGPropertyNode_ptr s_prop_bytes_multiplayer_messages;
|
||||
static SGPropertyNode_ptr s_prop_num_multiplayer_messages;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
double sim_time;
|
||||
std::string message;
|
||||
std::string speaker;
|
||||
} FGReplayMessages;
|
||||
|
||||
enum FGTapeType
|
||||
{
|
||||
FGTapeType_NORMAL,
|
||||
FGTapeType_CONTINUOUS,
|
||||
FGTapeType_RECOVERY,
|
||||
};
|
||||
|
||||
|
||||
/* Index entry when replaying Continuous recording. */
|
||||
struct FGFrameInfo
|
||||
{
|
||||
size_t offset;
|
||||
bool has_signals = false;
|
||||
bool has_multiplayer = false;
|
||||
bool has_extra_properties = false;
|
||||
};
|
||||
|
||||
std::ostream& operator << (std::ostream& out, const FGFrameInfo& frame_info);
|
||||
|
||||
|
||||
struct FGReplayInternal
|
||||
{
|
||||
FGReplayInternal();
|
||||
virtual ~FGReplayInternal();
|
||||
|
||||
/* Methods that implement the FGReplay API. */
|
||||
|
||||
void bind();
|
||||
void init();
|
||||
void reinit() ;
|
||||
void unbind();
|
||||
void update(double dt);
|
||||
|
||||
static const char* staticSubsystemClassId() { return "replay"; }
|
||||
|
||||
bool start(bool NewTape=false);
|
||||
|
||||
bool saveTape(const SGPropertyNode* ConfigData);
|
||||
bool loadTape(const SGPropertyNode* ConfigData);
|
||||
|
||||
static int loadContinuousHeader(
|
||||
const std::string& path,
|
||||
std::istream* in,
|
||||
SGPropertyNode* properties
|
||||
);
|
||||
|
||||
bool loadTape(
|
||||
const SGPath& filename,
|
||||
bool preview,
|
||||
SGPropertyNode& meta_meta,
|
||||
simgear::HTTP::FileRequestRef file_request=nullptr
|
||||
);
|
||||
|
||||
static std::string makeTapePath(const std::string& tape_name);
|
||||
|
||||
|
||||
/* Callback for SGPropertyChangeListener. */
|
||||
//void valueChanged(SGPropertyNode * node) override;
|
||||
|
||||
/* Internal state. */
|
||||
|
||||
double m_sim_time;
|
||||
|
||||
double m_last_mt_time;
|
||||
double m_last_lt_time;
|
||||
double m_last_msg_time;
|
||||
int m_last_replay_state;
|
||||
bool m_was_finished_already;
|
||||
|
||||
std::deque<FGReplayData*> m_short_term;
|
||||
std::deque<FGReplayData*> m_medium_term;
|
||||
std::deque<FGReplayData*> m_long_term;
|
||||
std::deque<FGReplayData*> m_recycler;
|
||||
|
||||
std::vector<FGReplayMessages> m_replay_messages;
|
||||
std::vector<FGReplayMessages>::iterator m_current_msg;
|
||||
|
||||
SGPropertyNode_ptr m_disable_replay;
|
||||
SGPropertyNode_ptr m_replay_master;
|
||||
SGPropertyNode_ptr m_replay_master_eof;
|
||||
SGPropertyNode_ptr m_replay_time;
|
||||
SGPropertyNode_ptr m_replay_time_str;
|
||||
SGPropertyNode_ptr m_replay_looped;
|
||||
SGPropertyNode_ptr m_replay_duration_act;
|
||||
SGPropertyNode_ptr m_speed_up;
|
||||
SGPropertyNode_ptr m_replay_multiplayer;
|
||||
SGPropertyNode_ptr m_recovery_period;
|
||||
SGPropertyNode_ptr m_replay_error;
|
||||
SGPropertyNode_ptr m_record_normal_begin; // Time of first in-memory recorded frame.
|
||||
SGPropertyNode_ptr m_record_normal_end;
|
||||
SGPropertyNode_ptr m_log_frame_times;
|
||||
|
||||
SGPropertyNode_ptr m_sim_startup_xpos;
|
||||
SGPropertyNode_ptr m_sim_startup_ypos;
|
||||
SGPropertyNode_ptr m_sim_startup_xsize;
|
||||
SGPropertyNode_ptr m_sim_startup_ysize;
|
||||
SGPropertyNode_ptr m_simple_time_enabled;
|
||||
|
||||
double m_replay_time_prev; // Used to detect jumps while replaying.
|
||||
|
||||
/* short term sample rate is as every frame. */
|
||||
double m_high_res_time; // default: 60 secs of high res data
|
||||
double m_medium_res_time; // default: 10 mins of 1 fps data
|
||||
double m_low_res_time; // default: 1 hr of 10 spf data
|
||||
double m_medium_sample_rate; // medium term sample rate (sec)
|
||||
double m_long_sample_rate; // long term sample rate (sec)
|
||||
|
||||
std::shared_ptr<FGFlightRecorder> m_flight_recorder;
|
||||
|
||||
/* Things for Continuous recording/replay support. */
|
||||
std::unique_ptr<struct Continuous> m_continuous;
|
||||
|
||||
FGMultiplayMgr* m_MultiplayMgr;
|
||||
};
|
||||
|
||||
|
||||
/* 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.
|
||||
tape_type:
|
||||
.
|
||||
continuous_compression:
|
||||
Whether to use compression if tape_type is FGTapeType_CONTINUOUS.
|
||||
Returns:
|
||||
A new SGPropertyNode suitable as prefix of recording. If
|
||||
extra:user-data exists, it will appear as meta/user-data.
|
||||
*/
|
||||
SGPropertyNode_ptr saveSetup(
|
||||
const SGPropertyNode* extra,
|
||||
const SGPath& path,
|
||||
double duration,
|
||||
FGTapeType tape_type,
|
||||
int continuous_compression=0
|
||||
);
|
||||
|
||||
/* 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
|
||||
*/
|
||||
SGPath makeSavePath(FGTapeType type, SGPath* path_timeless=nullptr);
|
File diff suppressed because it is too large
Load diff
|
@ -20,268 +20,81 @@
|
|||
//
|
||||
// $Id$
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef _FG_REPLAY_HXX
|
||||
#define _FG_REPLAY_HXX 1
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include <mutex>
|
||||
#include <simgear/compiler.h>
|
||||
|
||||
#include <simgear/math/sg_types.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <simgear/structure/subsystem_mgr.hxx>
|
||||
#include <simgear/io/iostreams/gzcontainerfile.hxx>
|
||||
#include <simgear/io/HTTPFileRequest.hxx>
|
||||
|
||||
#include <MultiPlayer/multiplaymgr.hxx>
|
||||
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
|
||||
class FGFlightRecorder;
|
||||
|
||||
struct FGReplayData {
|
||||
|
||||
double sim_time;
|
||||
// Our aircraft state.
|
||||
std::vector<char> raw_data;
|
||||
|
||||
// Incoming multiplayer messages.
|
||||
std::vector<std::shared_ptr<std::vector<char>>> multiplayer_messages;
|
||||
std::vector<char> extra_properties;
|
||||
std::map<std::string, std::string> replay_extra_property_changes;
|
||||
std::vector<std::string> replay_extra_property_removals;
|
||||
|
||||
// Updates static statistics defined below.
|
||||
void UpdateStats();
|
||||
|
||||
// Resets out static property nodes; to be called by fgStartNewReset().
|
||||
static void resetStatisticsProperties();
|
||||
|
||||
FGReplayData();
|
||||
~FGReplayData();
|
||||
|
||||
size_t m_bytes_raw_data = 0;
|
||||
size_t m_bytes_multiplayer_messages = 0;
|
||||
size_t m_num_multiplayer_messages = 0;
|
||||
|
||||
// Statistics about replay data, also properties /sim/replay/datastats_*.
|
||||
static size_t s_num;
|
||||
static size_t s_bytes_raw_data;
|
||||
static size_t s_bytes_multiplayer_messages;
|
||||
static size_t s_num_multiplayer_messages;
|
||||
static SGPropertyNode_ptr s_prop_num;
|
||||
static SGPropertyNode_ptr s_prop_bytes_raw_data;
|
||||
static SGPropertyNode_ptr s_prop_bytes_multiplayer_messages;
|
||||
static SGPropertyNode_ptr s_prop_num_multiplayer_messages;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
double sim_time;
|
||||
std::string message;
|
||||
std::string speaker;
|
||||
} FGReplayMessages;
|
||||
|
||||
enum FGTapeType
|
||||
/* A recording/replay module for FlightGear flights. */
|
||||
struct FGReplay : SGSubsystem
|
||||
{
|
||||
FGTapeType_NORMAL,
|
||||
FGTapeType_CONTINUOUS,
|
||||
FGTapeType_RECOVERY,
|
||||
};
|
||||
|
||||
struct FGFrameInfo;
|
||||
|
||||
typedef std::deque < FGReplayData *> replay_list_type;
|
||||
typedef std::vector < FGReplayMessages > replay_messages_type;
|
||||
|
||||
/**
|
||||
* A recording/replay module for FlightGear flights
|
||||
*
|
||||
*/
|
||||
|
||||
class FGReplay : public SGSubsystem, SGPropertyChangeListener
|
||||
{
|
||||
public:
|
||||
FGReplay ();
|
||||
virtual ~FGReplay();
|
||||
|
||||
// Subsystem API.
|
||||
/* Subsystem API. */
|
||||
void bind() override;
|
||||
void init() override;
|
||||
void reinit() override;
|
||||
void unbind() override;
|
||||
void update(double dt) override;
|
||||
|
||||
// Subsystem identification.
|
||||
/* Subsystem identification. */
|
||||
static const char* staticSubsystemClassId() { return "replay"; }
|
||||
|
||||
bool start(bool NewTape=false);
|
||||
/* For built-in 'replay' command - replay using in-memory Normal recording.
|
||||
|
||||
new_tape: If true, we start at beginning of tape, otherwise we start at
|
||||
loop interval.
|
||||
*/
|
||||
bool start(bool new_tape=false);
|
||||
|
||||
/* For save and load tape operations from Flightgear GUI. */
|
||||
bool saveTape(const SGPropertyNode* ConfigData);
|
||||
bool loadTape(const SGPropertyNode* ConfigData);
|
||||
|
||||
// If filerequest is set, the local file is a Continuous recording and
|
||||
// it might increase in size as downloading progresses, so we need to
|
||||
// incrementally index the file until the file request has finished the
|
||||
// download.
|
||||
//
|
||||
/* Attempts to load Continuous recording header properties into
|
||||
<properties>. If in is null we use internal std::fstream, otherwise we use
|
||||
*in.
|
||||
|
||||
Returns 0 on success, +1 if we may succeed after further download, or -1 if
|
||||
recording is not a Continuous recording.
|
||||
|
||||
For command line --load-tape=...
|
||||
*/
|
||||
static int loadContinuousHeader(const std::string& path, std::istream* in, SGPropertyNode* properties);
|
||||
|
||||
/* Start replaying a flight recorder tape from disk.
|
||||
filename
|
||||
Path of recording.
|
||||
preview
|
||||
If true we read the header (and return it in <meta_meta> but do not
|
||||
start replaying.
|
||||
meta_meta
|
||||
Filled in with contents of recording header's "meta" tree.
|
||||
filerequest
|
||||
If not null we use this to get called back as download of file
|
||||
progresses, so that we can index the recording. Only useful for
|
||||
Continuous recordings.
|
||||
*/
|
||||
bool loadTape(
|
||||
const SGPath& Filename,
|
||||
bool Preview,
|
||||
SGPropertyNode& MetaMeta,
|
||||
simgear::HTTP::FileRequestRef filerequest=nullptr
|
||||
const SGPath& filename,
|
||||
bool preview,
|
||||
SGPropertyNode& meta_meta,
|
||||
simgear::HTTP::FileRequestRef file_request=nullptr
|
||||
);
|
||||
|
||||
// Attempts to load Continuous recording header properties into
|
||||
// <properties>. If in is null we use internal std::fstream, otherwise we
|
||||
// use *in.
|
||||
//
|
||||
// Returns 0 on success, +1 if we may succeed after further download, or -1
|
||||
// if recording is not a Continuous recording.
|
||||
//
|
||||
static int loadContinuousHeader(const std::string& path, std::istream* in, SGPropertyNode* properties);
|
||||
/* Prepends /sim/replay/tape-directory and/or appends .fgtape etc.
|
||||
|
||||
// Prepends /sim/replay/tape-directory and/or appends .fgtape etc.
|
||||
//
|
||||
For command line --load-tape=... */
|
||||
static std::string makeTapePath(const std::string& tape_name);
|
||||
|
||||
private:
|
||||
void clear();
|
||||
FGReplayData* record(double time);
|
||||
void interpolate(double time, const replay_list_type &list);
|
||||
void replay(
|
||||
double time,
|
||||
size_t offset,
|
||||
size_t offset_old,
|
||||
bool replay_signals,
|
||||
bool replay_multiplayer,
|
||||
bool replay_extra_properties,
|
||||
int* xpos=nullptr,
|
||||
int* ypos=nullptr,
|
||||
int* xsize=nullptr,
|
||||
int* ysize=nullptr
|
||||
);
|
||||
void replay(
|
||||
double time,
|
||||
FGReplayData* pCurrentFrame,
|
||||
FGReplayData* pOldFrame=nullptr,
|
||||
int* xpos=nullptr,
|
||||
int* ypos=nullptr,
|
||||
int* xsize=nullptr,
|
||||
int* ysize=nullptr
|
||||
);
|
||||
void guiMessage(const char* message);
|
||||
void loadMessages();
|
||||
void fillRecycler();
|
||||
|
||||
bool replay( double time );
|
||||
void replayMessage( double time );
|
||||
|
||||
double get_start_time();
|
||||
double get_end_time();
|
||||
|
||||
bool listTapes(bool SameAircraftFilter, const SGPath& tapeDirectory);
|
||||
bool saveTape(const SGPath& Filename, SGPropertyNode_ptr MetaData);
|
||||
/* Resets out static property nodes; to be called by fgStartNewReset(). */
|
||||
static void resetStatisticsProperties();
|
||||
|
||||
// Build up in-memory cache of simulator time to file offset, so we can
|
||||
// handle random access.
|
||||
//
|
||||
// We also cache any frames that modify extra-properties.
|
||||
//
|
||||
// Can be called multiple times, e.g. if recording is being downlaoded.
|
||||
//
|
||||
void indexContinuousRecording(const void* data, size_t numbytes);
|
||||
|
||||
// Callback for use with simgear::HTTP::FileRequest::setCallback().
|
||||
//
|
||||
static void call_indexContinuousRecording(void* ref, const void* data, size_t numbytes);
|
||||
|
||||
SGPropertyNode_ptr continuousWriteHeader(
|
||||
std::ofstream& out,
|
||||
const SGPath& path,
|
||||
FGTapeType tape_type
|
||||
);
|
||||
bool continuousWriteFrame(FGReplayData* r, std::ostream& out, SGPropertyNode_ptr meta);
|
||||
|
||||
double sim_time;
|
||||
double last_mt_time;
|
||||
double last_lt_time;
|
||||
double last_msg_time;
|
||||
replay_messages_type::iterator current_msg;
|
||||
int last_replay_state;
|
||||
bool was_finished_already;
|
||||
|
||||
replay_list_type short_term;
|
||||
replay_list_type medium_term;
|
||||
replay_list_type long_term;
|
||||
replay_list_type recycler;
|
||||
replay_messages_type replay_messages;
|
||||
|
||||
SGPropertyNode_ptr disable_replay;
|
||||
SGPropertyNode_ptr replay_master;
|
||||
SGPropertyNode_ptr replay_master_eof;
|
||||
SGPropertyNode_ptr replay_time;
|
||||
SGPropertyNode_ptr replay_time_str;
|
||||
SGPropertyNode_ptr replay_looped;
|
||||
SGPropertyNode_ptr replay_duration_act;
|
||||
SGPropertyNode_ptr speed_up;
|
||||
SGPropertyNode_ptr replay_multiplayer;
|
||||
SGPropertyNode_ptr recovery_period;
|
||||
SGPropertyNode_ptr replay_error;
|
||||
SGPropertyNode_ptr m_record_normal_begin; // Time of first in-memory recorded frame.
|
||||
SGPropertyNode_ptr m_record_normal_end;
|
||||
SGPropertyNode_ptr log_frame_times;
|
||||
|
||||
SGPropertyNode_ptr m_sim_startup_xpos;
|
||||
SGPropertyNode_ptr m_sim_startup_ypos;
|
||||
SGPropertyNode_ptr m_sim_startup_xsize;
|
||||
SGPropertyNode_ptr m_sim_startup_ysize;
|
||||
|
||||
double replay_time_prev; // Used to detect jumps while replaying.
|
||||
|
||||
double m_high_res_time; // default: 60 secs of high res data
|
||||
double m_medium_res_time; // default: 10 mins of 1 fps data
|
||||
double m_low_res_time; // default: 1 hr of 10 spf data
|
||||
// short term sample rate is as every frame
|
||||
double m_medium_sample_rate; // medium term sample rate (sec)
|
||||
double m_long_sample_rate; // long term sample rate (sec)
|
||||
|
||||
FGFlightRecorder* m_pRecorder;
|
||||
|
||||
FGMultiplayMgr* m_MultiplayMgr;
|
||||
|
||||
void valueChanged(SGPropertyNode * node);
|
||||
|
||||
// Things for replaying from uncompressed fgtape file.
|
||||
std::ifstream m_continuous_in;
|
||||
bool m_continuous_in_multiplayer;
|
||||
bool m_continuous_in_extra_properties;
|
||||
std::mutex m_continuous_in_time_to_frameinfo_lock;
|
||||
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;
|
||||
|
||||
// Only used for gathering statistics that are then written into
|
||||
// properties.
|
||||
//
|
||||
int m_num_frames_extra_properties = 0;
|
||||
int m_num_frames_multiplayer = 0;
|
||||
|
||||
// For writing uncompressed fgtape file.
|
||||
SGPropertyNode_ptr m_continuous_out_config;
|
||||
std::ofstream m_continuous_out;
|
||||
int m_continuous_out_compression;
|
||||
int m_continuous_in_compression;
|
||||
|
||||
SGPropertyNode_ptr m_simple_time_enabled;
|
||||
std::unique_ptr<struct FGReplayInternal> m_internal;
|
||||
};
|
||||
|
||||
#endif // _FG_REPLAY_HXX
|
||||
|
|
|
@ -1349,7 +1349,7 @@ void fgStartNewReset()
|
|||
globals->set_matlib(NULL);
|
||||
|
||||
flightgear::unregisterMainLoopProperties();
|
||||
FGReplayData::resetStatisticsProperties();
|
||||
FGReplay::resetStatisticsProperties();
|
||||
|
||||
simgear::clearSharedTreeGeometry();
|
||||
simgear::clearEffectCache();
|
||||
|
|
Loading…
Reference in a new issue