Added support for analysing raw speed values when replaying.
Setting sim/replay/log-raw-speed logs raw speed to /sim/replay/log-raw-speed-values/value[]. 'scripts/python/recordreplay.py --test-motion' uses this to test for a bug where the user aircraft simtime and signals information don't quite match in continuous recordings.
This commit is contained in:
parent
797f02f9dd
commit
5c76f41743
5 changed files with 181 additions and 57 deletions
|
@ -26,6 +26,11 @@ Args:
|
|||
A second fgfs executable. If specified we run all tests twice, first
|
||||
using <fgfs-old> to create the recording and <fgfs> to replay it,
|
||||
second the other way round.
|
||||
--test-motion
|
||||
Checks that speed of aircraft on replay is not affected by frame rate.
|
||||
|
||||
We deliberately change frame rate while recording UFO moving at
|
||||
constant speed.
|
||||
|
||||
BOOLS is comma-sparated list of 0 or 1, with 1 activating the particular
|
||||
feature. So for example '--continuous 0' tests normal recording/replay',
|
||||
|
@ -77,20 +82,23 @@ class Fg:
|
|||
args += f' --telnet={port}'
|
||||
args2 = args.split()
|
||||
|
||||
environ = os.environ.copy()
|
||||
if isinstance(env, str):
|
||||
env2 = dict()
|
||||
for nv in env.split():
|
||||
n, v = nv.split('=', 1)
|
||||
env2[n] = v
|
||||
env = env2
|
||||
environ = os.environ.copy()
|
||||
if env:
|
||||
environ.update(env)
|
||||
environ[n] = v
|
||||
if 'DISPLAY' not in environ:
|
||||
environ['DISPLAY'] = ':0'
|
||||
|
||||
# Run flightgear in new process, telling it to open telnet server.
|
||||
self.child = subprocess.Popen(args2)
|
||||
#
|
||||
# We run not in a shell, otherwise self.child.terminate() doesn't
|
||||
# work - it would kill the shell but leave fgfs running (there are
|
||||
# workarounds for this, such as prefixing the command with 'exec').
|
||||
#
|
||||
log(f'Command is: {args}')
|
||||
log(f'Running: {args2}')
|
||||
self.child = subprocess.Popen(args2, env=environ)
|
||||
|
||||
# Connect to flightgear's telnet server.
|
||||
timeout = 15
|
||||
|
@ -278,11 +286,80 @@ def test_record_replay(
|
|||
log('Test passed')
|
||||
|
||||
|
||||
def test_motion(fgfs):
|
||||
|
||||
aircraft = 'ufo'
|
||||
fg = Fg( aircraft, f'{fgfs} --aircraft={aircraft}')
|
||||
path = f'{fg.aircraft}-continuous.fgtape'
|
||||
|
||||
if 0:
|
||||
items = fg.fg.ls( '/sim')
|
||||
log( '/sim is:')
|
||||
for item in items:
|
||||
log( f' {item}')
|
||||
fg.close()
|
||||
return
|
||||
|
||||
fg.waitfor('/sim/fdm-initialized', 1, timeout=45)
|
||||
|
||||
# Run UFO with constant speed, varying the framerate so we check whether
|
||||
# recorded speeds are affected.
|
||||
#
|
||||
fg.fg['/controls/engines/engine[0]/throttle'] = 0.1
|
||||
fg.fg['/sim/frame-rate-throttle-hz'] = 5
|
||||
|
||||
# Delay to let frame rate settle.
|
||||
time.sleep(10)
|
||||
|
||||
# Start recording.
|
||||
fg.fg['/sim/replay/record-continuous'] = 1
|
||||
time.sleep(5)
|
||||
|
||||
# Change frame rate.
|
||||
fg.fg['/sim/frame-rate-throttle-hz'] = 2
|
||||
time.sleep(5)
|
||||
|
||||
# Restore original frame rate.
|
||||
fg.fg['/sim/frame-rate-throttle-hz'] = 5
|
||||
time.sleep(5)
|
||||
|
||||
# Stop recording.
|
||||
fg.fg['/sim/replay/record-continuous'] = 0
|
||||
|
||||
fg.close()
|
||||
|
||||
path2 = os.readlink( path)
|
||||
g_cleanup.append(lambda: os.remove(path2))
|
||||
|
||||
fg = Fg( aircraft, f'{fgfs} --load-tape={path} --prop:/sim/replay/log-raw-speed=true')
|
||||
fg.waitfor('/sim/fdm-initialized', 1, timeout=45)
|
||||
fg.fg['/sim/frame-rate-throttle-hz'] = 10
|
||||
fg.waitfor('/sim/replay/replay-state', 1)
|
||||
fg.waitfor('/sim/replay/replay-state-eof', 1)
|
||||
|
||||
items0 = fg.fg.ls( '/sim/replay/log-raw-speed-values')
|
||||
items = []
|
||||
for item in items0:
|
||||
if item.name == 'value':
|
||||
items.append(item)
|
||||
num_errors = 0
|
||||
for item in items[:-1]: # Ignore last item because replay at end interpolates.
|
||||
speed = float(item.value)
|
||||
prefix = ' '
|
||||
if abs(speed - 200) > 0.5:
|
||||
num_errors += 1
|
||||
prefix = '*'
|
||||
log( f' {prefix} speed={speed} details: {item}')
|
||||
fg.close()
|
||||
assert num_errors == 0, 'Replay showed uneven speed.'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
fgfs = f'./build-walk/fgfs.exe-run.sh'
|
||||
fgfs_old = None
|
||||
|
||||
do_test = 'all'
|
||||
continuous_s = [0, 1]
|
||||
extra_properties_s = [0, 1]
|
||||
main_view_s = [0, 1]
|
||||
|
@ -319,60 +396,67 @@ if __name__ == '__main__':
|
|||
elif arg == '--f-old':
|
||||
fgfs_old = next(args)
|
||||
fgfs_reverse = [0, 1]
|
||||
elif arg == '--test-motion':
|
||||
do_test = 'motion'
|
||||
else:
|
||||
raise Exception(f'Unrecognised arg: {arg!r}')
|
||||
|
||||
try:
|
||||
if fgfs_old:
|
||||
for fgfs1, fgfs2 in [(fgfs, fgfs_old), (fgfs_old, fgfs)]:
|
||||
for multiplayer in 0, 1:
|
||||
test_record_replay(
|
||||
fgfs1,
|
||||
fgfs2,
|
||||
multiplayer,
|
||||
continuous=0,
|
||||
extra_properties=0,
|
||||
main_view=0,
|
||||
length=10,
|
||||
)
|
||||
else:
|
||||
its_max = len(multiplayer_s) * len(continuous_s) * len(extra_properties_s) * len(main_view_s) * len(fgfs_reverse_s)
|
||||
it = 0
|
||||
for multiplayer in multiplayer_s:
|
||||
for continuous in continuous_s:
|
||||
for extra_properties in extra_properties_s:
|
||||
for main_view in main_view_s:
|
||||
for fgfs_reverse in fgfs_reverse_s:
|
||||
if fgfs_reverse:
|
||||
fgfs_save = fgfs_old
|
||||
fgfs_load = fgfs
|
||||
else:
|
||||
fgfs_save = fgfs
|
||||
fgfs_load = fgfs_old
|
||||
if do_test == 'motion':
|
||||
test_motion( fgfs)
|
||||
elif do_test == 'all':
|
||||
try:
|
||||
if fgfs_old:
|
||||
for fgfs1, fgfs2 in [(fgfs, fgfs_old), (fgfs_old, fgfs)]:
|
||||
for multiplayer in 0, 1:
|
||||
test_record_replay(
|
||||
fgfs1,
|
||||
fgfs2,
|
||||
multiplayer,
|
||||
continuous=0,
|
||||
extra_properties=0,
|
||||
main_view=0,
|
||||
length=10,
|
||||
)
|
||||
else:
|
||||
its_max = len(multiplayer_s) * len(continuous_s) * len(extra_properties_s) * len(main_view_s) * len(fgfs_reverse_s)
|
||||
it = 0
|
||||
for multiplayer in multiplayer_s:
|
||||
for continuous in continuous_s:
|
||||
for extra_properties in extra_properties_s:
|
||||
for main_view in main_view_s:
|
||||
for fgfs_reverse in fgfs_reverse_s:
|
||||
if fgfs_reverse:
|
||||
fgfs_save = fgfs_old
|
||||
fgfs_load = fgfs
|
||||
else:
|
||||
fgfs_save = fgfs
|
||||
fgfs_load = fgfs_old
|
||||
|
||||
ok = True
|
||||
if it_min is not None:
|
||||
if it < it_min:
|
||||
ok = False
|
||||
if it_max is not None:
|
||||
if it >= it_max:
|
||||
ok = False
|
||||
log('')
|
||||
log(f'===')
|
||||
log(f'=== {it}/{its_max}')
|
||||
if ok:
|
||||
test_record_replay(
|
||||
fgfs_save,
|
||||
fgfs_load,
|
||||
multiplayer=multiplayer,
|
||||
continuous=continuous,
|
||||
extra_properties=extra_properties,
|
||||
main_view=main_view,
|
||||
length=10
|
||||
)
|
||||
it += 1
|
||||
finally:
|
||||
pass
|
||||
ok = True
|
||||
if it_min is not None:
|
||||
if it < it_min:
|
||||
ok = False
|
||||
if it_max is not None:
|
||||
if it >= it_max:
|
||||
ok = False
|
||||
log('')
|
||||
log(f'===')
|
||||
log(f'=== {it}/{its_max}')
|
||||
if ok:
|
||||
test_record_replay(
|
||||
fgfs_save,
|
||||
fgfs_load,
|
||||
multiplayer=multiplayer,
|
||||
continuous=continuous,
|
||||
extra_properties=extra_properties,
|
||||
main_view=main_view,
|
||||
length=10
|
||||
)
|
||||
it += 1
|
||||
finally:
|
||||
pass
|
||||
else:
|
||||
assert 0, f'do_test={do_test}'
|
||||
|
||||
# If everything passed, cleanup. Otherwise leave recordings in place, as
|
||||
# they can be useful for debugging.
|
||||
|
|
|
@ -230,6 +230,7 @@ FGFlightRecorder::FGFlightRecorder(const char* pConfigName) :
|
|||
m_ReplayMainWindowSize (fgGetNode("/sim/replay/replay-main-window-size", true)),
|
||||
m_RecordContinuous (fgGetNode("/sim/replay/record-continuous", true)),
|
||||
m_RecordExtraProperties (fgGetNode("/sim/replay/record-extra-properties", true)),
|
||||
m_LogRawSpeed (fgGetNode("/sim/replay/log-raw-speed", true)),
|
||||
m_TotalRecordSize(0),
|
||||
m_ConfigName(pConfigName),
|
||||
m_usingDefaultConfig(false),
|
||||
|
@ -775,6 +776,7 @@ FGFlightRecorder::replay(double SimTime, const FGReplayData* _pNextBuffer,
|
|||
const double* pDoubles = (const double*) &pBuffer[Offset];
|
||||
const double* pLastDoubles = (const double*) &pLastBuffer[Offset];
|
||||
unsigned int SignalCount = m_CaptureDouble.size();
|
||||
|
||||
for (unsigned int i=0; i<SignalCount; i++)
|
||||
{
|
||||
double v = pDoubles[i];
|
||||
|
@ -785,6 +787,38 @@ FGFlightRecorder::replay(double SimTime, const FGReplayData* _pNextBuffer,
|
|||
}
|
||||
m_CaptureDouble[i].Signal->setDoubleValue(v);
|
||||
}
|
||||
|
||||
if (m_LogRawSpeed->getBoolValue()) {
|
||||
// Log raw speed values to
|
||||
// /sim/replay/log-raw-speed-values/value[]. This is used by
|
||||
// scripts/python/recordreplay.py --test-motion.
|
||||
//
|
||||
SGGeod pos_geod = SGGeod::fromDegFt(
|
||||
fgGetDouble("/position/longitude-deg"),
|
||||
fgGetDouble("/position/latitude-deg"),
|
||||
fgGetDouble("/position/altitude-ft")
|
||||
);
|
||||
SGVec3d pos = SGVec3d::fromGeod(pos_geod);
|
||||
static SGVec3d pos_prev;
|
||||
static double simtime_prev = -1;
|
||||
double dt = SimTime - simtime_prev;
|
||||
if (simtime_prev != -1 && dt > 0) {
|
||||
double distance = length(pos - pos_prev);
|
||||
double speed = dt ? distance / dt : -1;
|
||||
SG_LOG(SG_GENERAL, SG_DEBUG, "User aircraft:"
|
||||
<< " pLastBuffer=" << ((void*) pLastBuffer)
|
||||
<< " SimTime=" << SimTime
|
||||
<< " dt=" << dt
|
||||
<< " distance=" << distance
|
||||
<< " speed=" << speed
|
||||
);
|
||||
SGPropertyNode* n = fgGetNode("/sim/replay/log-raw-speed-values", true /*create*/);
|
||||
n->addChild("value")->setDoubleValue(speed);
|
||||
}
|
||||
pos_prev = pos;
|
||||
simtime_prev = SimTime;
|
||||
}
|
||||
|
||||
Offset += SignalCount * sizeof(double);
|
||||
}
|
||||
|
||||
|
|
|
@ -95,6 +95,8 @@ private:
|
|||
SGPropertyNode_ptr m_RecordContinuous;
|
||||
SGPropertyNode_ptr m_RecordExtraProperties;
|
||||
|
||||
SGPropertyNode_ptr m_LogRawSpeed;
|
||||
|
||||
// This contains copy of all properties that we are recording, so that we
|
||||
// can send only differences.
|
||||
//
|
||||
|
|
|
@ -356,6 +356,7 @@ FGReplay::init()
|
|||
{
|
||||
disable_replay = fgGetNode("/sim/replay/disable", true);
|
||||
replay_master = fgGetNode("/sim/replay/replay-state", true);
|
||||
replay_master_eof = fgGetNode("/sim/replay/replay-state-eof", true);
|
||||
replay_time = fgGetNode("/sim/replay/time", true);
|
||||
replay_time_str = fgGetNode("/sim/replay/time-str", true);
|
||||
replay_looped = fgGetNode("/sim/replay/looped", true);
|
||||
|
@ -552,6 +553,7 @@ FGReplay::start(bool NewTape)
|
|||
if (0 == replay_master->getIntValue())
|
||||
{
|
||||
replay_master->setIntValue(1);
|
||||
replay_master_eof->setIntValue(0);
|
||||
if (NewTape)
|
||||
{
|
||||
// start replay at initial time, when loading a new tape
|
||||
|
@ -1010,6 +1012,7 @@ FGReplay::update( double dt )
|
|||
{
|
||||
if (!was_finished_already)
|
||||
{
|
||||
replay_master_eof->setIntValue(1);
|
||||
guiMessage("End of tape. 'Esc' to return.");
|
||||
was_finished_already = true;
|
||||
}
|
||||
|
|
|
@ -223,6 +223,7 @@ private:
|
|||
|
||||
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;
|
||||
|
|
Loading…
Reference in a new issue