1
0
Fork 0

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:
Julian Smith 2021-03-14 21:06:57 +00:00
parent 797f02f9dd
commit 5c76f41743
5 changed files with 181 additions and 57 deletions

View file

@ -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.

View file

@ -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);
}

View file

@ -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.
//

View file

@ -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;
}

View file

@ -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;