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
|
A second fgfs executable. If specified we run all tests twice, first
|
||||||
using <fgfs-old> to create the recording and <fgfs> to replay it,
|
using <fgfs-old> to create the recording and <fgfs> to replay it,
|
||||||
second the other way round.
|
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
|
BOOLS is comma-sparated list of 0 or 1, with 1 activating the particular
|
||||||
feature. So for example '--continuous 0' tests normal recording/replay',
|
feature. So for example '--continuous 0' tests normal recording/replay',
|
||||||
|
@ -77,20 +82,23 @@ class Fg:
|
||||||
args += f' --telnet={port}'
|
args += f' --telnet={port}'
|
||||||
args2 = args.split()
|
args2 = args.split()
|
||||||
|
|
||||||
|
environ = os.environ.copy()
|
||||||
if isinstance(env, str):
|
if isinstance(env, str):
|
||||||
env2 = dict()
|
|
||||||
for nv in env.split():
|
for nv in env.split():
|
||||||
n, v = nv.split('=', 1)
|
n, v = nv.split('=', 1)
|
||||||
env2[n] = v
|
environ[n] = v
|
||||||
env = env2
|
|
||||||
environ = os.environ.copy()
|
|
||||||
if env:
|
|
||||||
environ.update(env)
|
|
||||||
if 'DISPLAY' not in environ:
|
if 'DISPLAY' not in environ:
|
||||||
environ['DISPLAY'] = ':0'
|
environ['DISPLAY'] = ':0'
|
||||||
|
|
||||||
# Run flightgear in new process, telling it to open telnet server.
|
# 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.
|
# Connect to flightgear's telnet server.
|
||||||
timeout = 15
|
timeout = 15
|
||||||
|
@ -278,11 +286,80 @@ def test_record_replay(
|
||||||
log('Test passed')
|
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__':
|
if __name__ == '__main__':
|
||||||
|
|
||||||
fgfs = f'./build-walk/fgfs.exe-run.sh'
|
fgfs = f'./build-walk/fgfs.exe-run.sh'
|
||||||
fgfs_old = None
|
fgfs_old = None
|
||||||
|
|
||||||
|
do_test = 'all'
|
||||||
continuous_s = [0, 1]
|
continuous_s = [0, 1]
|
||||||
extra_properties_s = [0, 1]
|
extra_properties_s = [0, 1]
|
||||||
main_view_s = [0, 1]
|
main_view_s = [0, 1]
|
||||||
|
@ -319,60 +396,67 @@ if __name__ == '__main__':
|
||||||
elif arg == '--f-old':
|
elif arg == '--f-old':
|
||||||
fgfs_old = next(args)
|
fgfs_old = next(args)
|
||||||
fgfs_reverse = [0, 1]
|
fgfs_reverse = [0, 1]
|
||||||
|
elif arg == '--test-motion':
|
||||||
|
do_test = 'motion'
|
||||||
else:
|
else:
|
||||||
raise Exception(f'Unrecognised arg: {arg!r}')
|
raise Exception(f'Unrecognised arg: {arg!r}')
|
||||||
|
|
||||||
try:
|
if do_test == 'motion':
|
||||||
if fgfs_old:
|
test_motion( fgfs)
|
||||||
for fgfs1, fgfs2 in [(fgfs, fgfs_old), (fgfs_old, fgfs)]:
|
elif do_test == 'all':
|
||||||
for multiplayer in 0, 1:
|
try:
|
||||||
test_record_replay(
|
if fgfs_old:
|
||||||
fgfs1,
|
for fgfs1, fgfs2 in [(fgfs, fgfs_old), (fgfs_old, fgfs)]:
|
||||||
fgfs2,
|
for multiplayer in 0, 1:
|
||||||
multiplayer,
|
test_record_replay(
|
||||||
continuous=0,
|
fgfs1,
|
||||||
extra_properties=0,
|
fgfs2,
|
||||||
main_view=0,
|
multiplayer,
|
||||||
length=10,
|
continuous=0,
|
||||||
)
|
extra_properties=0,
|
||||||
else:
|
main_view=0,
|
||||||
its_max = len(multiplayer_s) * len(continuous_s) * len(extra_properties_s) * len(main_view_s) * len(fgfs_reverse_s)
|
length=10,
|
||||||
it = 0
|
)
|
||||||
for multiplayer in multiplayer_s:
|
else:
|
||||||
for continuous in continuous_s:
|
its_max = len(multiplayer_s) * len(continuous_s) * len(extra_properties_s) * len(main_view_s) * len(fgfs_reverse_s)
|
||||||
for extra_properties in extra_properties_s:
|
it = 0
|
||||||
for main_view in main_view_s:
|
for multiplayer in multiplayer_s:
|
||||||
for fgfs_reverse in fgfs_reverse_s:
|
for continuous in continuous_s:
|
||||||
if fgfs_reverse:
|
for extra_properties in extra_properties_s:
|
||||||
fgfs_save = fgfs_old
|
for main_view in main_view_s:
|
||||||
fgfs_load = fgfs
|
for fgfs_reverse in fgfs_reverse_s:
|
||||||
else:
|
if fgfs_reverse:
|
||||||
fgfs_save = fgfs
|
fgfs_save = fgfs_old
|
||||||
fgfs_load = fgfs_old
|
fgfs_load = fgfs
|
||||||
|
else:
|
||||||
|
fgfs_save = fgfs
|
||||||
|
fgfs_load = fgfs_old
|
||||||
|
|
||||||
ok = True
|
ok = True
|
||||||
if it_min is not None:
|
if it_min is not None:
|
||||||
if it < it_min:
|
if it < it_min:
|
||||||
ok = False
|
ok = False
|
||||||
if it_max is not None:
|
if it_max is not None:
|
||||||
if it >= it_max:
|
if it >= it_max:
|
||||||
ok = False
|
ok = False
|
||||||
log('')
|
log('')
|
||||||
log(f'===')
|
log(f'===')
|
||||||
log(f'=== {it}/{its_max}')
|
log(f'=== {it}/{its_max}')
|
||||||
if ok:
|
if ok:
|
||||||
test_record_replay(
|
test_record_replay(
|
||||||
fgfs_save,
|
fgfs_save,
|
||||||
fgfs_load,
|
fgfs_load,
|
||||||
multiplayer=multiplayer,
|
multiplayer=multiplayer,
|
||||||
continuous=continuous,
|
continuous=continuous,
|
||||||
extra_properties=extra_properties,
|
extra_properties=extra_properties,
|
||||||
main_view=main_view,
|
main_view=main_view,
|
||||||
length=10
|
length=10
|
||||||
)
|
)
|
||||||
it += 1
|
it += 1
|
||||||
finally:
|
finally:
|
||||||
pass
|
pass
|
||||||
|
else:
|
||||||
|
assert 0, f'do_test={do_test}'
|
||||||
|
|
||||||
# If everything passed, cleanup. Otherwise leave recordings in place, as
|
# If everything passed, cleanup. Otherwise leave recordings in place, as
|
||||||
# they can be useful for debugging.
|
# 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_ReplayMainWindowSize (fgGetNode("/sim/replay/replay-main-window-size", true)),
|
||||||
m_RecordContinuous (fgGetNode("/sim/replay/record-continuous", true)),
|
m_RecordContinuous (fgGetNode("/sim/replay/record-continuous", true)),
|
||||||
m_RecordExtraProperties (fgGetNode("/sim/replay/record-extra-properties", true)),
|
m_RecordExtraProperties (fgGetNode("/sim/replay/record-extra-properties", true)),
|
||||||
|
m_LogRawSpeed (fgGetNode("/sim/replay/log-raw-speed", true)),
|
||||||
m_TotalRecordSize(0),
|
m_TotalRecordSize(0),
|
||||||
m_ConfigName(pConfigName),
|
m_ConfigName(pConfigName),
|
||||||
m_usingDefaultConfig(false),
|
m_usingDefaultConfig(false),
|
||||||
|
@ -775,6 +776,7 @@ FGFlightRecorder::replay(double SimTime, const FGReplayData* _pNextBuffer,
|
||||||
const double* pDoubles = (const double*) &pBuffer[Offset];
|
const double* pDoubles = (const double*) &pBuffer[Offset];
|
||||||
const double* pLastDoubles = (const double*) &pLastBuffer[Offset];
|
const double* pLastDoubles = (const double*) &pLastBuffer[Offset];
|
||||||
unsigned int SignalCount = m_CaptureDouble.size();
|
unsigned int SignalCount = m_CaptureDouble.size();
|
||||||
|
|
||||||
for (unsigned int i=0; i<SignalCount; i++)
|
for (unsigned int i=0; i<SignalCount; i++)
|
||||||
{
|
{
|
||||||
double v = pDoubles[i];
|
double v = pDoubles[i];
|
||||||
|
@ -785,6 +787,38 @@ FGFlightRecorder::replay(double SimTime, const FGReplayData* _pNextBuffer,
|
||||||
}
|
}
|
||||||
m_CaptureDouble[i].Signal->setDoubleValue(v);
|
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);
|
Offset += SignalCount * sizeof(double);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -95,6 +95,8 @@ private:
|
||||||
SGPropertyNode_ptr m_RecordContinuous;
|
SGPropertyNode_ptr m_RecordContinuous;
|
||||||
SGPropertyNode_ptr m_RecordExtraProperties;
|
SGPropertyNode_ptr m_RecordExtraProperties;
|
||||||
|
|
||||||
|
SGPropertyNode_ptr m_LogRawSpeed;
|
||||||
|
|
||||||
// This contains copy of all properties that we are recording, so that we
|
// This contains copy of all properties that we are recording, so that we
|
||||||
// can send only differences.
|
// can send only differences.
|
||||||
//
|
//
|
||||||
|
|
|
@ -356,6 +356,7 @@ FGReplay::init()
|
||||||
{
|
{
|
||||||
disable_replay = fgGetNode("/sim/replay/disable", true);
|
disable_replay = fgGetNode("/sim/replay/disable", true);
|
||||||
replay_master = fgGetNode("/sim/replay/replay-state", 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 = fgGetNode("/sim/replay/time", true);
|
||||||
replay_time_str = fgGetNode("/sim/replay/time-str", true);
|
replay_time_str = fgGetNode("/sim/replay/time-str", true);
|
||||||
replay_looped = fgGetNode("/sim/replay/looped", true);
|
replay_looped = fgGetNode("/sim/replay/looped", true);
|
||||||
|
@ -552,6 +553,7 @@ FGReplay::start(bool NewTape)
|
||||||
if (0 == replay_master->getIntValue())
|
if (0 == replay_master->getIntValue())
|
||||||
{
|
{
|
||||||
replay_master->setIntValue(1);
|
replay_master->setIntValue(1);
|
||||||
|
replay_master_eof->setIntValue(0);
|
||||||
if (NewTape)
|
if (NewTape)
|
||||||
{
|
{
|
||||||
// start replay at initial time, when loading a new tape
|
// start replay at initial time, when loading a new tape
|
||||||
|
@ -1010,6 +1012,7 @@ FGReplay::update( double dt )
|
||||||
{
|
{
|
||||||
if (!was_finished_already)
|
if (!was_finished_already)
|
||||||
{
|
{
|
||||||
|
replay_master_eof->setIntValue(1);
|
||||||
guiMessage("End of tape. 'Esc' to return.");
|
guiMessage("End of tape. 'Esc' to return.");
|
||||||
was_finished_already = true;
|
was_finished_already = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -223,6 +223,7 @@ private:
|
||||||
|
|
||||||
SGPropertyNode_ptr disable_replay;
|
SGPropertyNode_ptr disable_replay;
|
||||||
SGPropertyNode_ptr replay_master;
|
SGPropertyNode_ptr replay_master;
|
||||||
|
SGPropertyNode_ptr replay_master_eof;
|
||||||
SGPropertyNode_ptr replay_time;
|
SGPropertyNode_ptr replay_time;
|
||||||
SGPropertyNode_ptr replay_time_str;
|
SGPropertyNode_ptr replay_time_str;
|
||||||
SGPropertyNode_ptr replay_looped;
|
SGPropertyNode_ptr replay_looped;
|
||||||
|
|
Loading…
Reference in a new issue