Added support for compressed continuous recordings.
src/Aircraft/replay.*: If /sim/replay/record-continuous-compression is true, we compress each frame's data as a separate raw zlib stream. Requires latest simgear's simgear/io/iostreams/zlibstream.cxx for decompression with ZLibCompressionFormat::ZLIB_RAW. Haven't figured out how to extend simgear's code to provide a compressing ostream so for now we have our own local compression code. We open popup and set sim/replay/replay-error=true if we fail to read compressed data. scripts/python/recordreplay.py: Added test of compressed continuous recordings. docs-mini/README-recordings.md: Added information about compressed format.
This commit is contained in:
parent
08390be391
commit
7d414886e0
4 changed files with 410 additions and 116 deletions
|
@ -14,7 +14,7 @@ As of 2020-12-22, there are three kinds of recordings:
|
||||||
|
|
||||||
Normal recordings are compressed and contain frames at varying intervals with more recent frames being closer together in time. They are generated from the in-memory recording that Flightgear always maintains. They may contain multiplayer information.
|
Normal recordings are compressed and contain frames at varying intervals with more recent frames being closer together in time. They are generated from the in-memory recording that Flightgear always maintains. They may contain multiplayer information.
|
||||||
|
|
||||||
Continuous recordings are uncompressed and written directly to a file while Flightgear runs, giving high resolution with near unlimited recording time. They may contain multiplayer information. As of 2020-12-23 they may contain information about extra properties, allowing replay of views and main window position/size.
|
Continuous recordings are written directly to a file while Flightgear runs, giving high resolution with near unlimited recording time. They may contain multiplayer information. As of 2020-12-23 they may contain information about extra properties, allowing replay of views and main window position/size. As of 2021-06-26 each frame's data can be compressed.
|
||||||
|
|
||||||
Recovery recordings are essentially single-frame Continuous recordings. When enabled, Flightgear creates them periodically to allow recovery of a session if Flightgear crashes.
|
Recovery recordings are essentially single-frame Continuous recordings. When enabled, Flightgear creates them periodically to allow recovery of a session if Flightgear crashes.
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ Recovery recordings are essentially single-frame Continuous recordings. When ena
|
||||||
* `/sim/replay/record-continuous` - if true, do continuous record to file.
|
* `/sim/replay/record-continuous` - if true, do continuous record to file.
|
||||||
* `/sim/replay/record-signals` - if true (the default), include signals for user aircraft - these are the core values used to replay the user aircraft.
|
* `/sim/replay/record-signals` - if true (the default), include signals for user aircraft - these are the core values used to replay the user aircraft.
|
||||||
* `/sim/replay/record-extra-properties` - if true, we include selected properties in recordings.
|
* `/sim/replay/record-extra-properties` - if true, we include selected properties in recordings.
|
||||||
|
* `/sim/replay/record-continuous-compression` - if 1, we compress each frame's data.
|
||||||
* Recovery recordings:
|
* Recovery recordings:
|
||||||
* `/sim/replay/record-recovery-period` - if non-zero, we update recovery recording in specified interval.
|
* `/sim/replay/record-recovery-period` - if non-zero, we update recovery recording in specified interval.
|
||||||
|
|
||||||
|
@ -74,15 +75,13 @@ Normal recordings are written as a compressed gzip stream using `simgear::gzCont
|
||||||
|
|
||||||
### Continuous recordings
|
### Continuous recordings
|
||||||
|
|
||||||
Continuous recordings do not use compression, in order to simplify random access when replaying.
|
|
||||||
|
|
||||||
* Header:
|
* Header:
|
||||||
|
|
||||||
* A zero-terminated magic string: `FlightGear Flight Recorder Tape` (variable `FlightRecorderFileMagic`).
|
* A zero-terminated magic string: `FlightGear Flight Recorder Tape` (variable `FlightRecorderFileMagic`).
|
||||||
|
|
||||||
* A property tree represented as a `uint32` length followed by zero-terminated text. This contains:
|
* A property tree represented as a `uint32` length followed by zero-terminated text. This contains:
|
||||||
|
|
||||||
* A `meta` node with various child nodes.
|
* A `meta` node with various child nodes. If this contains `continuous-compression` with value `1`, then each frame's data is compressed..
|
||||||
|
|
||||||
* `data[]` nodes describing the data items in each frame in the order in which they occur. Supported values are:
|
* `data[]` nodes describing the data items in each frame in the order in which they occur. Supported values are:
|
||||||
|
|
||||||
|
@ -98,7 +97,15 @@ Continuous recordings do not use compression, in order to simplify random access
|
||||||
|
|
||||||
* Frame time as a binary double.
|
* Frame time as a binary double.
|
||||||
|
|
||||||
* A list of ordered `<length:32><data>` items as described by the `data[]` nodes in the header. This format allows Flightgear to skip data items that it doesn't understand if loading a recording that was created by a newer version.
|
* If compression is used:
|
||||||
|
|
||||||
|
* uint8_t flags.
|
||||||
|
* Bit 0: this frame has signals.
|
||||||
|
* Bit 1: this frame has multiplayer information.
|
||||||
|
* Bit 2: this frame has extrap properties.
|
||||||
|
* uint32_t compressed-size.
|
||||||
|
|
||||||
|
* Frame data (can be compressed): a list of ordered `<length:32><data>` items as described by the `data[]` nodes in the header. This format allows Flightgear to skip data items that it doesn't understand if loading a recording that was created by a newer version.
|
||||||
|
|
||||||
* For `signals`, `<data>` is binary data for the core aircraft properties.
|
* For `signals`, `<data>` is binary data for the core aircraft properties.
|
||||||
|
|
||||||
|
@ -115,6 +122,8 @@ Continuous recordings do not use compression, in order to simplify random access
|
||||||
|
|
||||||
When a Continuous recording is loaded, `FGReplay::loadTape()` first steps through the entire file, building up an index in memory that maps from frame times to a struct containing the offset of the frame in the file plus information on whether the frame has multiplayer and/or extra-properties information. This allows us to support the user jumping forwards and backwards in the recording.
|
When a Continuous recording is loaded, `FGReplay::loadTape()` first steps through the entire file, building up an index in memory that maps from frame times to a struct containing the offset of the frame in the file plus information on whether the frame has multiplayer and/or extra-properties information. This allows us to support the user jumping forwards and backwards in the recording.
|
||||||
|
|
||||||
|
If the recording uses compression, indexing uses the uint8_t flags and uint32_t compressed-size fields and does not need to decompress each frame's data.
|
||||||
|
|
||||||
If we are replaying from a URL, indexing takes place in the background (by requesting callbacks from the download's `simgear::HTTP::FileRequest`) and replay starts immediately. Thus we avoid having to wait until the entire recording has been downloaded before starting replay.
|
If we are replaying from a URL, indexing takes place in the background (by requesting callbacks from the download's `simgear::HTTP::FileRequest`) and replay starts immediately. Thus we avoid having to wait until the entire recording has been downloaded before starting replay.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -230,7 +230,7 @@ class Fg:
|
||||||
|
|
||||||
def make_recording(
|
def make_recording(
|
||||||
fg,
|
fg,
|
||||||
continuous=0,
|
continuous=0, # 2 means continuous with compression.
|
||||||
extra_properties=0,
|
extra_properties=0,
|
||||||
main_view=0,
|
main_view=0,
|
||||||
length=5,
|
length=5,
|
||||||
|
@ -244,13 +244,15 @@ def make_recording(
|
||||||
fg.fg['/sim/replay/record-signals'] = True # Just in case they are disabled by user.
|
fg.fg['/sim/replay/record-signals'] = True # Just in case they are disabled by user.
|
||||||
if continuous:
|
if continuous:
|
||||||
assert not fg.fg['/sim/replay/record-continuous']
|
assert not fg.fg['/sim/replay/record-continuous']
|
||||||
|
if continuous == 2:
|
||||||
|
fg.fg['/sim/replay/record-continuous-compression'] = 1
|
||||||
fg.fg['/sim/replay/record-continuous'] = 1
|
fg.fg['/sim/replay/record-continuous'] = 1
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
while 1:
|
while 1:
|
||||||
if time.time() > t0 + length:
|
if time.time() > t0 + length:
|
||||||
break
|
break
|
||||||
fg.run_command('run view-step step=1')
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
fg.run_command('run view-step step=1')
|
||||||
fg.fg['/sim/replay/record-continuous'] = 0
|
fg.fg['/sim/replay/record-continuous'] = 0
|
||||||
path = f'{g_tapedir}/{fg.aircraft}-continuous.fgtape'
|
path = f'{g_tapedir}/{fg.aircraft}-continuous.fgtape'
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
@ -332,26 +334,44 @@ def test_record_replay(
|
||||||
fg.waitfor('/sim/fdm-initialized', 1, timeout=45)
|
fg.waitfor('/sim/fdm-initialized', 1, timeout=45)
|
||||||
fg.waitfor('/sim/replay/replay-state', 1)
|
fg.waitfor('/sim/replay/replay-state', 1)
|
||||||
|
|
||||||
|
t0 = time.time()
|
||||||
|
|
||||||
# Check replay time is ok.
|
# Check replay time is ok.
|
||||||
rtime_begin = fg.fg['/sim/replay/start-time']
|
rtime_begin = fg.fg['/sim/replay/start-time']
|
||||||
rtime_end = fg.fg['/sim/replay/end-time']
|
rtime_end = fg.fg['/sim/replay/end-time']
|
||||||
rtime = rtime_end - rtime_begin
|
rtime = rtime_end - rtime_begin
|
||||||
log(f'rtime={rtime_begin}..{rtime_end}, recording length: {rtime}, length={length}')
|
log(f'rtime={rtime_begin}..{rtime_end}, recording length: {rtime}, length={length}')
|
||||||
assert rtime > length-1 and rtime < length+2, \
|
assert rtime > length-1 and rtime <= length+2, \
|
||||||
f'length={length} rtime_begin={rtime_begin} rtime_end={rtime_end} rtime={rtime}'
|
f'length={length} rtime_begin={rtime_begin} rtime_end={rtime_end} rtime={rtime}'
|
||||||
|
|
||||||
num_frames_extra_properties = fg.fg['/sim/replay/continuous-stats-num-frames-extra-properties']
|
num_frames_extra_properties = fg.fg['/sim/replay/continuous-stats-num-frames-extra-properties']
|
||||||
log(f'num_frames_extra_properties={num_frames_extra_properties}')
|
log(f'num_frames_extra_properties={num_frames_extra_properties}')
|
||||||
if continuous:
|
if continuous:
|
||||||
if main_view:
|
if main_view:
|
||||||
assert num_frames_extra_properties > 1
|
assert num_frames_extra_properties > 1, f'num_frames_extra_properties={num_frames_extra_properties}'
|
||||||
else:
|
else:
|
||||||
assert num_frames_extra_properties == 0
|
assert num_frames_extra_properties == 0
|
||||||
else:
|
else:
|
||||||
assert num_frames_extra_properties in (0, None), \
|
assert num_frames_extra_properties in (0, None), \
|
||||||
f'num_frames_extra_properties={num_frames_extra_properties}'
|
f'num_frames_extra_properties={num_frames_extra_properties}'
|
||||||
|
|
||||||
time.sleep(length)
|
fg.run_command('run dialog-show dialog-name=replay')
|
||||||
|
|
||||||
|
while 1:
|
||||||
|
t = time.time()
|
||||||
|
if t < t0 + length - 1:
|
||||||
|
pass
|
||||||
|
# Disabled because it seems that Flightgear starts replaying before
|
||||||
|
# we see replay-state set to 1 because scenery loading blocks
|
||||||
|
# things.
|
||||||
|
#
|
||||||
|
#assert not fg.fg['/sim/replay/replay-state-eof'], f'Replay has finished too early; lenth={length} t-t0={t-t0}'
|
||||||
|
if t > t0 + length + 1:
|
||||||
|
assert fg.fg['/sim/replay/replay-state-eof'], f'Replay has not finished on time; lenth={length} t-t0={t-t0}'
|
||||||
|
break
|
||||||
|
e = fg.fg['sim/replay/replay-error']
|
||||||
|
assert not e, f'Replay failed: e={e}'
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
fg.close()
|
fg.close()
|
||||||
|
|
||||||
|
@ -676,7 +696,7 @@ if __name__ == '__main__':
|
||||||
fgfs_old = None
|
fgfs_old = None
|
||||||
|
|
||||||
do_test = 'all'
|
do_test = 'all'
|
||||||
continuous_s = [0, 1]
|
continuous_s = [0, 1, 2] # 2 is continuous with compression.
|
||||||
extra_properties_s = [0, 1]
|
extra_properties_s = [0, 1]
|
||||||
main_view_s = [0, 1]
|
main_view_s = [0, 1]
|
||||||
multiplayer_s = [0, 1]
|
multiplayer_s = [0, 1]
|
||||||
|
@ -698,19 +718,20 @@ if __name__ == '__main__':
|
||||||
elif arg == '--carrier':
|
elif arg == '--carrier':
|
||||||
do_test = 'carrier'
|
do_test = 'carrier'
|
||||||
elif arg == '--continuous':
|
elif arg == '--continuous':
|
||||||
continuous_s = map(int, next(args).split(','))
|
continuous_s = [int(x) for x in next(args).split(',')]
|
||||||
|
log(f'continuous_s={continuous_s}')
|
||||||
elif arg == '--tape-dir':
|
elif arg == '--tape-dir':
|
||||||
g_tapedir = next(args)
|
g_tapedir = next(args)
|
||||||
elif arg == '--extra-properties':
|
elif arg == '--extra-properties':
|
||||||
extra_properties_s = map(int, next(args).split(','))
|
extra_properties_s = [int(x) for x in next(args).split(',')]
|
||||||
elif arg == '--it-max':
|
elif arg == '--it-max':
|
||||||
it_max = int(next(args))
|
it_max = int(next(args))
|
||||||
elif arg == '--it-min':
|
elif arg == '--it-min':
|
||||||
it_min = int(next(args))
|
it_min = int(next(args))
|
||||||
elif arg == '--main-view':
|
elif arg == '--main-view':
|
||||||
main_view_s = map(int, next(args).split(','))
|
main_view_s = [int(x) for x in next(args).split(',')]
|
||||||
elif arg == '--multiplayer':
|
elif arg == '--multiplayer':
|
||||||
multiplayer_s = map(int, next(args).split(','))
|
multiplayer_s = [int(x) for x in next(args).split(',')]
|
||||||
elif arg == '-f':
|
elif arg == '-f':
|
||||||
fgfs = next(args)
|
fgfs = next(args)
|
||||||
elif arg == '--f-old':
|
elif arg == '--f-old':
|
||||||
|
@ -748,6 +769,7 @@ if __name__ == '__main__':
|
||||||
length=10,
|
length=10,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
log(f'continuous_s={continuous_s}')
|
||||||
its_max = len(multiplayer_s) * len(continuous_s) * len(extra_properties_s) * len(main_view_s) * len(fgfs_reverse_s)
|
its_max = len(multiplayer_s) * len(continuous_s) * len(extra_properties_s) * len(main_view_s) * len(fgfs_reverse_s)
|
||||||
it = 0
|
it = 0
|
||||||
for multiplayer in multiplayer_s:
|
for multiplayer in multiplayer_s:
|
||||||
|
|
|
@ -29,12 +29,20 @@
|
||||||
#include <float.h>
|
#include <float.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <iostream>
|
||||||
|
#include <streambuf>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include <zlib.h>
|
||||||
|
|
||||||
#include <osgViewer/ViewerBase>
|
#include <osgViewer/ViewerBase>
|
||||||
|
|
||||||
#include <simgear/constants.h>
|
#include <simgear/constants.h>
|
||||||
#include <simgear/structure/exception.hxx>
|
#include <simgear/structure/exception.hxx>
|
||||||
#include <simgear/props/props_io.hxx>
|
#include <simgear/props/props_io.hxx>
|
||||||
#include <simgear/io/iostreams/gzcontainerfile.hxx>
|
#include <simgear/io/iostreams/gzcontainerfile.hxx>
|
||||||
|
#include <simgear/io/iostreams/zlibstream.hxx>
|
||||||
#include <simgear/misc/sg_dir.hxx>
|
#include <simgear/misc/sg_dir.hxx>
|
||||||
#include <simgear/misc/stdint.hxx>
|
#include <simgear/misc/stdint.hxx>
|
||||||
#include <simgear/misc/strutils.hxx>
|
#include <simgear/misc/strutils.hxx>
|
||||||
|
@ -151,6 +159,7 @@ FGReplay::FGReplay() :
|
||||||
last_lt_time(0.0),
|
last_lt_time(0.0),
|
||||||
last_msg_time(0),
|
last_msg_time(0),
|
||||||
last_replay_state(0),
|
last_replay_state(0),
|
||||||
|
replay_error(fgGetNode("sim/replay/replay-error", true)),
|
||||||
m_sim_startup_xpos(fgGetNode("sim/startup/xpos", true)),
|
m_sim_startup_xpos(fgGetNode("sim/startup/xpos", true)),
|
||||||
m_sim_startup_ypos(fgGetNode("sim/startup/ypos", true)),
|
m_sim_startup_ypos(fgGetNode("sim/startup/ypos", true)),
|
||||||
m_sim_startup_xsize(fgGetNode("sim/startup/xsize", true)),
|
m_sim_startup_xsize(fgGetNode("sim/startup/xsize", true)),
|
||||||
|
@ -299,8 +308,13 @@ void FGReplay::valueChanged(SGPropertyNode * node)
|
||||||
SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to create link " << path_timeless.c_str() << " => " << path.file());
|
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);
|
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Starting continuous recording to " << path);
|
||||||
|
if (m_continuous_out_compression) {
|
||||||
|
popupTip("Continuous+compressed record to file started", 5 /*delay*/);
|
||||||
|
}
|
||||||
|
else {
|
||||||
popupTip("Continuous record to file started", 5 /*delay*/);
|
popupTip("Continuous record to file started", 5 /*delay*/);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -683,6 +697,133 @@ saveRawReplayData(gzContainerWriter& output, const replay_list_type& ReplayData,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Sets things up for writing to a normal or continuous fgtape file.
|
// Sets things up for writing to a normal or continuous fgtape file.
|
||||||
//
|
//
|
||||||
// extra:
|
// extra:
|
||||||
|
@ -702,7 +843,8 @@ static SGPropertyNode_ptr saveSetup(
|
||||||
const SGPropertyNode* extra,
|
const SGPropertyNode* extra,
|
||||||
const SGPath& path,
|
const SGPath& path,
|
||||||
double duration,
|
double duration,
|
||||||
FGTapeType tape_type
|
FGTapeType tape_type,
|
||||||
|
int continuous_compression=0
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
SGPropertyNode_ptr config;
|
SGPropertyNode_ptr config;
|
||||||
|
@ -722,7 +864,9 @@ static SGPropertyNode_ptr saveSetup(
|
||||||
meta->setStringValue("aircraft-fdm", fgGetString("/sim/flight-model", ""));
|
meta->setStringValue("aircraft-fdm", fgGetString("/sim/flight-model", ""));
|
||||||
meta->setStringValue("closest-airport-id", fgGetString("/sim/airport/closest-airport-id", ""));
|
meta->setStringValue("closest-airport-id", fgGetString("/sim/airport/closest-airport-id", ""));
|
||||||
meta->setStringValue("aircraft-version", fgGetString("/sim/aircraft-version", "(undefined)"));
|
meta->setStringValue("aircraft-version", fgGetString("/sim/aircraft-version", "(undefined)"));
|
||||||
|
if (tape_type == FGTapeType_CONTINUOUS) {
|
||||||
|
meta->setIntValue("continuous-compression", continuous_compression);
|
||||||
|
}
|
||||||
// add information on the tape's recording duration
|
// add information on the tape's recording duration
|
||||||
meta->setDoubleValue("tape-duration", duration);
|
meta->setDoubleValue("tape-duration", duration);
|
||||||
char StrBuffer[30];
|
char StrBuffer[30];
|
||||||
|
@ -783,7 +927,9 @@ SGPropertyNode_ptr FGReplay::continuousWriteHeader(
|
||||||
FGTapeType tape_type
|
FGTapeType tape_type
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
SGPropertyNode_ptr config = saveSetup(NULL /*Extra*/, path, 0 /*Duration*/, tape_type);
|
m_continuous_out_compression = fgGetInt("/sim/replay/record-continuous-compression");
|
||||||
|
SGPropertyNode_ptr config = saveSetup(NULL /*Extra*/, path, 0 /*Duration*/,
|
||||||
|
tape_type, m_continuous_out_compression);
|
||||||
SGPropertyNode* signals = config->getNode("signals", true /*create*/);
|
SGPropertyNode* signals = config->getNode("signals", true /*create*/);
|
||||||
m_pRecorder->getConfig(signals);
|
m_pRecorder->getConfig(signals);
|
||||||
|
|
||||||
|
@ -804,48 +950,8 @@ SGPropertyNode_ptr FGReplay::continuousWriteHeader(
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void writeFrame2(FGReplayData* r, std::ostream& out, SGPropertyNode_ptr config)
|
||||||
// Writes one frame of continuous record information.
|
|
||||||
//
|
|
||||||
bool
|
|
||||||
FGReplay::continuousWriteFrame(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;
|
|
||||||
for (auto data: config->getChildren("data")) {
|
|
||||||
const char* data_type = data->getStringValue();
|
|
||||||
if (!strcmp(data_type, "signals")) {
|
|
||||||
r_has_data = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (!strcmp(data_type, "multiplayer")) {
|
|
||||||
if (!r->multiplayer_messages.empty()) {
|
|
||||||
r_has_data = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!strcmp(data_type, "extra-properties")) {
|
|
||||||
if (!r->extra_properties.empty()) {
|
|
||||||
r_has_data = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
SG_LOG(SG_SYSTEMS, SG_ALERT, "unrecognised data_type=" << data_type);
|
|
||||||
assert(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!r_has_data) {
|
|
||||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Not writing frame because no data to write");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
out.write(reinterpret_cast<char*>(&r->sim_time), sizeof(r->sim_time));
|
|
||||||
|
|
||||||
for (auto data: config->getChildren("data")) {
|
for (auto data: config->getChildren("data")) {
|
||||||
const char* data_type = data->getStringValue();
|
const char* data_type = data->getStringValue();
|
||||||
if (!strcmp(data_type, "signals")) {
|
if (!strcmp(data_type, "signals")) {
|
||||||
|
@ -880,6 +986,71 @@ FGReplay::continuousWriteFrame(FGReplayData* r, std::ostream& out, SGPropertyNod
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writes one frame of continuous record information.
|
||||||
|
//
|
||||||
|
bool
|
||||||
|
FGReplay::continuousWriteFrame(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.write(reinterpret_cast<char*>(&r->sim_time), sizeof(r->sim_time));
|
||||||
|
|
||||||
|
if (m_continuous_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;
|
bool ok = true;
|
||||||
if (!out) ok = false;
|
if (!out) ok = false;
|
||||||
return ok;
|
return ok;
|
||||||
|
@ -1553,14 +1724,14 @@ FGReplay::replay(double time, FGReplayData* pCurrentFrame, FGReplayData* pOldFra
|
||||||
m_pRecorder->replay(time, pCurrentFrame, pOldFrame, xpos, ypos, xsize, ysize);
|
m_pRecorder->replay(time, pCurrentFrame, pOldFrame, xpos, ypos, xsize, ysize);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int16_t read_int16(std::ifstream& in, size_t& pos)
|
static int16_t read_int16(std::istream& in, size_t& pos)
|
||||||
{
|
{
|
||||||
int16_t a;
|
int16_t a;
|
||||||
in.read(reinterpret_cast<char*>(&a), sizeof(a));
|
in.read(reinterpret_cast<char*>(&a), sizeof(a));
|
||||||
pos += sizeof(a);
|
pos += sizeof(a);
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
static std::string read_string(std::ifstream& in, size_t& pos)
|
static std::string read_string(std::istream& in, size_t& pos)
|
||||||
{
|
{
|
||||||
int16_t length = read_int16(in, pos);
|
int16_t length = read_int16(in, pos);
|
||||||
std::vector<char> path(length);
|
std::vector<char> path(length);
|
||||||
|
@ -1572,7 +1743,7 @@ static std::string read_string(std::ifstream& in, size_t& pos)
|
||||||
|
|
||||||
/* Reads extra-property change items in next <length> bytes. Throws if we don't
|
/* Reads extra-property change items in next <length> bytes. Throws if we don't
|
||||||
exactly read <length> bytes. */
|
exactly read <length> bytes. */
|
||||||
static void ReadFGReplayDataExtraProperties(std::ifstream& in, FGReplayData* replay_data, uint32_t length)
|
static void ReadFGReplayDataExtraProperties(std::istream& in, FGReplayData* replay_data, uint32_t length)
|
||||||
{
|
{
|
||||||
SG_LOG(SG_SYSTEMS, SG_BULK, "reading extra-properties. length=" << length);
|
SG_LOG(SG_SYSTEMS, SG_BULK, "reading extra-properties. length=" << length);
|
||||||
size_t pos=0;
|
size_t pos=0;
|
||||||
|
@ -1600,34 +1771,23 @@ static void ReadFGReplayDataExtraProperties(std::ifstream& in, FGReplayData* rep
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reads all or part of a FGReplayData from uncompressed file. */
|
static bool ReadFGReplayData2(
|
||||||
static std::unique_ptr<FGReplayData> ReadFGReplayData(
|
std::istream& in,
|
||||||
std::ifstream& in,
|
|
||||||
size_t pos,
|
|
||||||
SGPropertyNode* config,
|
SGPropertyNode* config,
|
||||||
bool load_signals,
|
bool load_signals,
|
||||||
bool load_multiplayer,
|
bool load_multiplayer,
|
||||||
bool load_extra_properties
|
bool load_extra_properties,
|
||||||
|
FGReplayData* ret
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
/* 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);
|
|
||||||
|
|
||||||
in.read(reinterpret_cast<char*>(&ret->sim_time), sizeof(ret->sim_time));
|
|
||||||
ret->raw_data.resize(0);
|
ret->raw_data.resize(0);
|
||||||
for (auto data: config->getChildren("data")) {
|
for (auto data: config->getChildren("data")) {
|
||||||
const char* data_type = data->getStringValue();
|
const char* data_type = data->getStringValue();
|
||||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "in.tellg()=" << in.tellg() << " data_type=" << data_type);
|
SG_LOG(SG_SYSTEMS, SG_BULK, "in.tellg()=" << in.tellg() << " data_type=" << data_type);
|
||||||
uint32_t length;
|
uint32_t length;
|
||||||
in.read(reinterpret_cast<char*>(&length), sizeof(length));
|
in.read(reinterpret_cast<char*>(&length), sizeof(length));
|
||||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "length=" << length);
|
SG_LOG(SG_SYSTEMS, SG_DEBUG, "length=" << length);
|
||||||
|
if (!in) break;
|
||||||
if (load_signals && !strcmp(data_type, "signals")) {
|
if (load_signals && !strcmp(data_type, "signals")) {
|
||||||
ret->raw_data.resize(length);
|
ret->raw_data.resize(length);
|
||||||
in.read(&ret->raw_data.front(), ret->raw_data.size());
|
in.read(&ret->raw_data.front(), ret->raw_data.size());
|
||||||
|
@ -1653,14 +1813,64 @@ static std::unique_ptr<FGReplayData> ReadFGReplayData(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (load_extra_properties && !strcmp(data_type, "extra-properties")) {
|
else if (load_extra_properties && !strcmp(data_type, "extra-properties")) {
|
||||||
ReadFGReplayDataExtraProperties(in, ret.get(), length);
|
ReadFGReplayDataExtraProperties(in, ret, length);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
SG_LOG(SG_GENERAL, SG_BULK, "Skipping unrecognised data: " << data_type);
|
SG_LOG(SG_GENERAL, SG_BULK, "Skipping unrecognised/unwanted data: " << data_type);
|
||||||
in.seekg(length, std::ios_base::cur);
|
in.seekg(length, std::ios_base::cur);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!in) {
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read fgtape data");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reads all or part of a FGReplayData from uncompressed file. */
|
||||||
|
static std::unique_ptr<FGReplayData> ReadFGReplayData(
|
||||||
|
std::ifstream& in,
|
||||||
|
size_t pos,
|
||||||
|
SGPropertyNode* config,
|
||||||
|
bool load_signals,
|
||||||
|
bool load_multiplayer,
|
||||||
|
bool load_extra_properties,
|
||||||
|
int m_continuous_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);
|
||||||
|
|
||||||
|
in.read(reinterpret_cast<char*>(&ret->sim_time), sizeof(ret->sim_time));
|
||||||
|
if (!in) {
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read fgtape frame at offset " << pos);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
bool ok;
|
||||||
|
if (m_continuous_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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1691,8 +1901,17 @@ void FGReplay::replay(
|
||||||
m_continuous_in_config,
|
m_continuous_in_config,
|
||||||
replay_signals,
|
replay_signals,
|
||||||
replay_multiplayer,
|
replay_multiplayer,
|
||||||
replay_extra_properties
|
replay_extra_properties,
|
||||||
|
m_continuous_in_compression
|
||||||
);
|
);
|
||||||
|
if (!replay_data) {
|
||||||
|
if (!replay_error->getBoolValue()) {
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to read fgtape frame at offset=" << offset << " time=" << time);
|
||||||
|
popupTip("Replay failed: cannot read fgtape data", 10);
|
||||||
|
replay_error->setBoolValue(true);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
assert(replay_data.get());
|
assert(replay_data.get());
|
||||||
std::unique_ptr<FGReplayData> replay_data_old;
|
std::unique_ptr<FGReplayData> replay_data_old;
|
||||||
if (offset_old) {
|
if (offset_old) {
|
||||||
|
@ -1702,7 +1921,8 @@ void FGReplay::replay(
|
||||||
m_continuous_in_config,
|
m_continuous_in_config,
|
||||||
replay_signals,
|
replay_signals,
|
||||||
replay_multiplayer,
|
replay_multiplayer,
|
||||||
replay_extra_properties
|
replay_extra_properties,
|
||||||
|
m_continuous_in_compression
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (replay_extra_properties) SG_LOG(SG_SYSTEMS, SG_BULK,
|
if (replay_extra_properties) SG_LOG(SG_SYSTEMS, SG_BULK,
|
||||||
|
@ -1964,7 +2184,7 @@ int FGReplay::loadContinuousHeader(const std::string& path, std::istream* in, SG
|
||||||
ok = true;
|
ok = true;
|
||||||
}
|
}
|
||||||
catch (std::exception& e) {
|
catch (std::exception& e) {
|
||||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read Config properties in: " << path);
|
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read Config properties in: " << path << ": " << e.what());
|
||||||
}
|
}
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
// Failed to read properties, so indicate that further download is needed.
|
// Failed to read properties, so indicate that further download is needed.
|
||||||
|
@ -1981,6 +2201,7 @@ void FGReplay::indexContinuousRecording(const void* data, size_t numbytes)
|
||||||
<< " data=" << data
|
<< " data=" << data
|
||||||
<< " numbytes=" << numbytes
|
<< " numbytes=" << numbytes
|
||||||
<< " m_continuous_indexing_pos=" << m_continuous_indexing_pos
|
<< " m_continuous_indexing_pos=" << m_continuous_indexing_pos
|
||||||
|
<< " m_continuous_in_compression=" << m_continuous_in_compression
|
||||||
<< " m_continuous_in_time_to_frameinfo.size()=" << m_continuous_in_time_to_frameinfo.size()
|
<< " m_continuous_in_time_to_frameinfo.size()=" << m_continuous_in_time_to_frameinfo.size()
|
||||||
);
|
);
|
||||||
time_t t0 = time(NULL);
|
time_t t0 = time(NULL);
|
||||||
|
@ -2011,9 +2232,44 @@ void FGReplay::indexContinuousRecording(const void* data, size_t numbytes)
|
||||||
<< " m_continuous_indexing_in.tellg()=" << m_continuous_indexing_in.tellg()
|
<< " m_continuous_indexing_in.tellg()=" << m_continuous_indexing_in.tellg()
|
||||||
<< " sim_time=" << sim_time
|
<< " sim_time=" << sim_time
|
||||||
);
|
);
|
||||||
|
|
||||||
FGFrameInfo frameinfo;
|
FGFrameInfo frameinfo;
|
||||||
frameinfo.offset = m_continuous_indexing_pos;
|
frameinfo.offset = m_continuous_indexing_pos;
|
||||||
|
if (m_continuous_in_compression)
|
||||||
|
{
|
||||||
|
// Skip compressed frame data without decompressing it.
|
||||||
|
uint8_t flags;
|
||||||
|
m_continuous_indexing_in.read((char*) &flags, sizeof(flags));
|
||||||
|
frameinfo.has_signals = flags & 1;
|
||||||
|
frameinfo.has_multiplayer = flags & 2;
|
||||||
|
frameinfo.has_extra_properties = flags & 4;
|
||||||
|
|
||||||
|
if (frameinfo.has_signals)
|
||||||
|
{
|
||||||
|
stats["signals"].num_frames += 1;
|
||||||
|
}
|
||||||
|
if (frameinfo.has_multiplayer)
|
||||||
|
{
|
||||||
|
stats["multiplayer"].num_frames += 1;
|
||||||
|
++m_num_frames_multiplayer;
|
||||||
|
m_continuous_in_multiplayer = true;
|
||||||
|
}
|
||||||
|
if (frameinfo.has_extra_properties)
|
||||||
|
{
|
||||||
|
stats["extra-properties"].num_frames += 1;
|
||||||
|
++m_num_frames_extra_properties;
|
||||||
|
m_continuous_in_extra_properties = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t compressed_size;
|
||||||
|
m_continuous_indexing_in.read((char*) &compressed_size, sizeof(compressed_size));
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_BULK, "compressed_size=" << compressed_size);
|
||||||
|
|
||||||
|
m_continuous_indexing_in.seekg(compressed_size, std::ios_base::cur);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Skip frame data.
|
||||||
auto datas = m_continuous_in_config->getChildren("data");
|
auto datas = m_continuous_in_config->getChildren("data");
|
||||||
SG_LOG(SG_SYSTEMS, SG_BULK, "datas.size()=" << datas.size());
|
SG_LOG(SG_SYSTEMS, SG_BULK, "datas.size()=" << datas.size());
|
||||||
for (auto data: datas) {
|
for (auto data: datas) {
|
||||||
|
@ -2048,6 +2304,7 @@ void FGReplay::indexContinuousRecording(const void* data, size_t numbytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
SG_LOG(SG_SYSTEMS, SG_BULK, ""
|
SG_LOG(SG_SYSTEMS, SG_BULK, ""
|
||||||
<< " pos=" << m_continuous_indexing_pos
|
<< " pos=" << m_continuous_indexing_pos
|
||||||
<< " sim_time=" << sim_time
|
<< " sim_time=" << sim_time
|
||||||
|
@ -2134,7 +2391,8 @@ FGReplay::loadTape(const SGPath& Filename, bool Preview, SGPropertyNode& MetaMet
|
||||||
{
|
{
|
||||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "loading Preview=" << Preview << " Filename=" << Filename);
|
SG_LOG(SG_SYSTEMS, SG_DEBUG, "loading Preview=" << Preview << " Filename=" << Filename);
|
||||||
|
|
||||||
/* Try to load as uncompressed Continuous recording first. */
|
/* Try to load a Continuous recording first. */
|
||||||
|
replay_error->setBoolValue(false);
|
||||||
std::ifstream in_preview;
|
std::ifstream in_preview;
|
||||||
std::ifstream& in(Preview ? in_preview : m_continuous_in);
|
std::ifstream& in(Preview ? in_preview : m_continuous_in);
|
||||||
in.open(Filename.str());
|
in.open(Filename.str());
|
||||||
|
@ -2165,6 +2423,8 @@ FGReplay::loadTape(const SGPath& Filename, bool Preview, SGPropertyNode& MetaMet
|
||||||
m_num_frames_multiplayer = 0;
|
m_num_frames_multiplayer = 0;
|
||||||
m_continuous_indexing_in.open(Filename.str());
|
m_continuous_indexing_in.open(Filename.str());
|
||||||
m_continuous_indexing_pos = in.tellg();
|
m_continuous_indexing_pos = in.tellg();
|
||||||
|
m_continuous_in_compression = m_continuous_in_config->getNode("meta/continuous-compression", true /*create*/)->getIntValue();
|
||||||
|
SG_LOG(SG_SYSTEMS, SG_DEBUG, "m_continuous_in_compression=" << m_continuous_in_compression);
|
||||||
SG_LOG(SG_SYSTEMS, SG_DEBUG, "filerequest=" << filerequest.get());
|
SG_LOG(SG_SYSTEMS, SG_DEBUG, "filerequest=" << filerequest.get());
|
||||||
|
|
||||||
// Make an in-memory index of the recording.
|
// Make an in-memory index of the recording.
|
||||||
|
|
|
@ -231,6 +231,7 @@ private:
|
||||||
SGPropertyNode_ptr speed_up;
|
SGPropertyNode_ptr speed_up;
|
||||||
SGPropertyNode_ptr replay_multiplayer;
|
SGPropertyNode_ptr replay_multiplayer;
|
||||||
SGPropertyNode_ptr recovery_period;
|
SGPropertyNode_ptr recovery_period;
|
||||||
|
SGPropertyNode_ptr replay_error;
|
||||||
|
|
||||||
SGPropertyNode_ptr m_sim_startup_xpos;
|
SGPropertyNode_ptr m_sim_startup_xpos;
|
||||||
SGPropertyNode_ptr m_sim_startup_ypos;
|
SGPropertyNode_ptr m_sim_startup_ypos;
|
||||||
|
@ -274,6 +275,8 @@ private:
|
||||||
// For writing uncompressed fgtape file.
|
// For writing uncompressed fgtape file.
|
||||||
SGPropertyNode_ptr m_continuous_out_config;
|
SGPropertyNode_ptr m_continuous_out_config;
|
||||||
std::ofstream m_continuous_out;
|
std::ofstream m_continuous_out;
|
||||||
|
int m_continuous_out_compression;
|
||||||
|
int m_continuous_in_compression;
|
||||||
|
|
||||||
SGPropertyNode_ptr m_simple_time_enabled;
|
SGPropertyNode_ptr m_simple_time_enabled;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue