Bugfixes and improvements to the Failure Manager
- Fix: runtime exception in remove_failure_mode() - Fix: keep failure & trigger status on teleport. - Fix: allow random failures from the gui to be enabled/disabled multiple times. - Fix: mcbf/mtbf are set to zero when they fire, so they can be reactivated from the gui. - Fix: string casts of several trigger types had syntax errors. - Usability: screen messages related to failures now use positive logic: "condition 100%" instead of "failure level 0%" - Performance: Time triggers now use internal timers, instead of requiring being polled. - Reviewed Trigger interface for more rational usage. reset() is replaced by arm()/disarm() - Added a subscription interface to listen to FailureMgr events. - Added an internal log buffer to keep a record of relevant events and present them to gui elements. - Several usability improvements to the FailureMgr Nasal API.
This commit is contained in:
parent
6a763a0d77
commit
c108f3b988
5 changed files with 445 additions and 152 deletions
|
@ -70,32 +70,13 @@ var compat_modes = [
|
||||||
|
|
||||||
var compat_listener = func(prop) {
|
var compat_listener = func(prop) {
|
||||||
|
|
||||||
var new_trigger = func {
|
|
||||||
if (name == "mtbf") {
|
|
||||||
MtbfTrigger.new(value);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var control = id;
|
|
||||||
|
|
||||||
forindex(var i; compat_modes) {
|
|
||||||
var mode = compat_modes[i];
|
|
||||||
if (mode.id == id and contains(compat_modes[i], "mcbf_prop")) {
|
|
||||||
control = mode.mcbf_prop;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
McbfTrigger.new(control, value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var name = prop.getName();
|
var name = prop.getName();
|
||||||
var value = prop.getValue();
|
var value = prop.getValue();
|
||||||
var id = string.replace(io.dirname(prop.getPath()), FailureMgr.proproot, "");
|
var id = string.replace(io.dirname(prop.getPath()), FailureMgr.proproot, "");
|
||||||
id = string.trim(id, 0, func(c) c == `/`);
|
id = string.trim(id, 0, func(c) c == `/`);
|
||||||
|
|
||||||
if (name == "serviceable") {
|
if (name == "serviceable") {
|
||||||
FailureMgr.set_failure_level(id, 1 - value);
|
FailureMgr.set_failure_level(id, value ? 0 : 1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,17 +88,33 @@ var compat_listener = func(prop) {
|
||||||
# mtbf and mcbf parameter handling
|
# mtbf and mcbf parameter handling
|
||||||
var trigger = FailureMgr.get_trigger(id);
|
var trigger = FailureMgr.get_trigger(id);
|
||||||
|
|
||||||
if (value == 0) {
|
if (trigger == nil or (trigger.type != "mcbf" and trigger.type != "mtbf"))
|
||||||
trigger != nil and FailureMgr.set_trigger(id, nil);
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (trigger == nil) {
|
if (value != 0)
|
||||||
FailureMgr.set_trigger(id, new_trigger());
|
trigger.set_param(name, value) and trigger.arm();
|
||||||
|
else
|
||||||
|
trigger.disarm();
|
||||||
|
}
|
||||||
|
|
||||||
|
##
|
||||||
|
# Listens to FailureMgr events. Resets mcbf/mtbf params to zero so they can
|
||||||
|
# be rearmed from the GUI.
|
||||||
|
|
||||||
|
var trigger_listener = func(event) {
|
||||||
|
var trigger = event.trigger;
|
||||||
|
|
||||||
|
# Only control modes in our compat list, i.e. do not interfere
|
||||||
|
# with custom scripts.
|
||||||
|
|
||||||
|
if (trigger.type != "mtbf" and trigger.type != "mcbf")
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var m; compat_modes) {
|
||||||
|
if (m.id == event.mode_id) {
|
||||||
|
trigger.set_param(trigger.type, 0);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
trigger.set_param(name, value);
|
|
||||||
trigger.reset();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,10 +197,21 @@ var compat_setup = func {
|
||||||
setlistener(n, compat_listener, 0, 0);
|
setlistener(n, compat_listener, 0, 0);
|
||||||
setlistener(prop ~ "/failure-level", compat_listener, 0, 0);
|
setlistener(prop ~ "/failure-level", compat_listener, 0, 0);
|
||||||
|
|
||||||
var trigger_type = (m.type == MTBF) ? "/mtbf" : "/mcbf";
|
if (m.type == MTBF) {
|
||||||
|
var trigger_type = "/mtbf";
|
||||||
|
FailureMgr.set_trigger(m.id, MtbfTrigger.new(0));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var trigger_type = "/mcbf";
|
||||||
|
var control = contains(m, "mcbf_prop")? m.mcbf_prop : m.id;
|
||||||
|
FailureMgr.set_trigger(m.id, McbfTrigger.new(control, 0));
|
||||||
|
}
|
||||||
|
|
||||||
setprop(prop ~ trigger_type, 0);
|
setprop(prop ~ trigger_type, 0);
|
||||||
setlistener(prop ~ trigger_type, compat_listener, 0, 0);
|
setlistener(prop ~ trigger_type, compat_listener, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FailureMgr.events["trigger-fired"].subscribe(trigger_listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -121,6 +121,7 @@ var norm_rand = func(mean, std) {
|
||||||
var AltitudeTrigger = {
|
var AltitudeTrigger = {
|
||||||
|
|
||||||
parents: [FailureMgr.Trigger],
|
parents: [FailureMgr.Trigger],
|
||||||
|
type: "altitude",
|
||||||
requires_polling: 1,
|
requires_polling: 1,
|
||||||
|
|
||||||
new: func(min, max) {
|
new: func(min, max) {
|
||||||
|
@ -136,9 +137,12 @@ var AltitudeTrigger = {
|
||||||
},
|
},
|
||||||
|
|
||||||
to_str: func {
|
to_str: func {
|
||||||
# TODO: Handle min or max == nil
|
var min = me.params["min-altitude-ft"];
|
||||||
sprintf("Altitude between %d and %d ft",
|
var max = me.params["max-altitude-ft"];
|
||||||
int(me.params["min-altitude-ft"]), int(me.params["max-altitude-ft"]))
|
|
||||||
|
if (min == nil) sprintf("Altitude below %d ft", int(max));
|
||||||
|
elsif (max == nil) sprintf("Altitude above %d ft", int(min));
|
||||||
|
else sprintf("Altitude between %d and %d ft", int(min), int(max));
|
||||||
},
|
},
|
||||||
|
|
||||||
update: func {
|
update: func {
|
||||||
|
@ -159,6 +163,7 @@ var AltitudeTrigger = {
|
||||||
var WaypointTrigger = {
|
var WaypointTrigger = {
|
||||||
|
|
||||||
parents: [FailureMgr.Trigger],
|
parents: [FailureMgr.Trigger],
|
||||||
|
type: "waypoint",
|
||||||
requires_polling: 1,
|
requires_polling: 1,
|
||||||
|
|
||||||
new: func(lat, lon, distance) {
|
new: func(lat, lon, distance) {
|
||||||
|
@ -174,15 +179,15 @@ var WaypointTrigger = {
|
||||||
return m;
|
return m;
|
||||||
},
|
},
|
||||||
|
|
||||||
reset: func {
|
arm: func {
|
||||||
call(FailureMgr.Trigger.reset, [], me);
|
call(FailureMgr.Trigger.arm, [], me);
|
||||||
me.waypoint.set_latlon(me.params["latitude-deg"],
|
me.waypoint.set_latlon(me.params["latitude-deg"],
|
||||||
me.params["longitude-deg"]);
|
me.params["longitude-deg"]);
|
||||||
},
|
},
|
||||||
|
|
||||||
to_str: func {
|
to_str: func {
|
||||||
sprintf("Within %.2f miles of %s", me.params["distance-nm"],
|
sprintf("Within %.2f miles of %s", me.params["distance-nm"],
|
||||||
geo.format(me.waypoint.lat, me.waypoint.lon));
|
geo.format(me.waypoint.lat(), me.waypoint.lon()));
|
||||||
},
|
},
|
||||||
|
|
||||||
update: func {
|
update: func {
|
||||||
|
@ -197,27 +202,41 @@ var WaypointTrigger = {
|
||||||
var MtbfTrigger = {
|
var MtbfTrigger = {
|
||||||
|
|
||||||
parents: [FailureMgr.Trigger],
|
parents: [FailureMgr.Trigger],
|
||||||
# TODO: make this trigger async
|
type: "mtbf",
|
||||||
requires_polling: 1,
|
requires_polling: 0,
|
||||||
|
|
||||||
new: func(mtbf) {
|
new: func(mtbf) {
|
||||||
var m = FailureMgr.Trigger.new();
|
var m = FailureMgr.Trigger.new();
|
||||||
m.parents = [MtbfTrigger];
|
m.parents = [MtbfTrigger];
|
||||||
m.params["mtbf"] = mtbf;
|
m.params["mtbf"] = mtbf;
|
||||||
m.fire_time = 0;
|
m.timer = maketimer(0, func m.on_fire());
|
||||||
m._time_prop = "/sim/time/elapsed-sec";
|
m.timer.singleShot = 1;
|
||||||
return m;
|
return m;
|
||||||
},
|
},
|
||||||
|
|
||||||
reset: func {
|
enable: func {
|
||||||
call(FailureMgr.Trigger.reset, [], me);
|
me.armed and me.timer.start();
|
||||||
# TODO: use an elapsed time prop that accounts for speed-up and pause
|
me.enabled = 1;
|
||||||
me.fire_time = getprop(me._time_prop)
|
},
|
||||||
+ norm_rand(me.params["mtbf"], me.params["mtbf"] / 10);
|
|
||||||
|
disable: func {
|
||||||
|
me.timer.stop();
|
||||||
|
me.enabled = 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
arm: func {
|
||||||
|
call(FailureMgr.Trigger.arm, [], me);
|
||||||
|
me.timer.restart(norm_rand(me.params["mtbf"], me.params["mtbf"] / 10));
|
||||||
|
me.enabled and me.timer.start();
|
||||||
|
},
|
||||||
|
|
||||||
|
disarm: func {
|
||||||
|
call(FailureMgr.Trigger.disarm, [], me);
|
||||||
|
me.timer.stop();
|
||||||
},
|
},
|
||||||
|
|
||||||
to_str: func {
|
to_str: func {
|
||||||
sprintf("Mean time between failures: %f.1 mins", me.params["mtbf"] / 60);
|
sprintf("Mean time between failures: %.1f mins", me.params["mtbf"] / 60);
|
||||||
},
|
},
|
||||||
|
|
||||||
update: func {
|
update: func {
|
||||||
|
@ -231,22 +250,37 @@ var MtbfTrigger = {
|
||||||
var TimeoutTrigger = {
|
var TimeoutTrigger = {
|
||||||
|
|
||||||
parents: [FailureMgr.Trigger],
|
parents: [FailureMgr.Trigger],
|
||||||
# TODO: make this trigger async
|
type: "timeout",
|
||||||
requires_polling: 1,
|
requires_polling: 0,
|
||||||
|
|
||||||
new: func(timeout) {
|
new: func(timeout) {
|
||||||
var m = FailureMgr.Trigger.new();
|
var m = FailureMgr.Trigger.new();
|
||||||
m.parents = [TimeoutTrigger];
|
m.parents = [TimeoutTrigger];
|
||||||
m.params["timeout-sec"] = timeout;
|
m.params["timeout-sec"] = timeout;
|
||||||
fire_time = 0;
|
m.timer = maketimer(0, func m.on_fire());
|
||||||
|
m.timer.singleShot = 1;
|
||||||
return m;
|
return m;
|
||||||
},
|
},
|
||||||
|
|
||||||
reset: func {
|
enable: func {
|
||||||
call(FailureMgr.Trigger.reset, [], me);
|
me.armed and me.timer.start();
|
||||||
# TODO: use an elapsed time prop that accounts for speed-up and pause
|
me.enabled = 1;
|
||||||
me.fire_time = getprop("/sim/time/elapsed-sec")
|
},
|
||||||
+ me.params["timeout-sec"];
|
|
||||||
|
disable: func {
|
||||||
|
me.timer.stop();
|
||||||
|
me.enabled = 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
arm: func {
|
||||||
|
call(FailureMgr.Trigger.arm, [], me);
|
||||||
|
me.timer.restart(me.params["timeout-sec"]);
|
||||||
|
me.enabled and me.timer.start();
|
||||||
|
},
|
||||||
|
|
||||||
|
disarm: func {
|
||||||
|
call(FailureMgr.Trigger.disarm, [], me);
|
||||||
|
me.timer.stop();
|
||||||
},
|
},
|
||||||
|
|
||||||
to_str: func {
|
to_str: func {
|
||||||
|
@ -284,7 +318,10 @@ var CycleCounter = {
|
||||||
},
|
},
|
||||||
|
|
||||||
disable: func {
|
disable: func {
|
||||||
if (me._lsnr != nil) removelistener(me._lsnr);
|
if (me._lsnr != nil) {
|
||||||
|
removelistener(me._lsnr);
|
||||||
|
me._lsnr = nil;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
reset: func {
|
reset: func {
|
||||||
|
@ -319,6 +356,7 @@ var CycleCounter = {
|
||||||
var McbfTrigger = {
|
var McbfTrigger = {
|
||||||
|
|
||||||
parents: [FailureMgr.Trigger],
|
parents: [FailureMgr.Trigger],
|
||||||
|
type: "mcbf",
|
||||||
requires_polling: 0,
|
requires_polling: 0,
|
||||||
|
|
||||||
new: func(property, mcbf) {
|
new: func(property, mcbf) {
|
||||||
|
@ -327,7 +365,6 @@ var McbfTrigger = {
|
||||||
m.params["mcbf"] = mcbf;
|
m.params["mcbf"] = mcbf;
|
||||||
m.counter = CycleCounter.new(property, func(c) call(m._on_cycle, [c], m));
|
m.counter = CycleCounter.new(property, func(c) call(m._on_cycle, [c], m));
|
||||||
m.activation_cycles = 0;
|
m.activation_cycles = 0;
|
||||||
m.enabled = 0;
|
|
||||||
return m;
|
return m;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -341,8 +378,8 @@ var McbfTrigger = {
|
||||||
me.enabled = 0;
|
me.enabled = 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
reset: func {
|
arm: func {
|
||||||
call(FailureMgr.Trigger.reset, [], me);
|
call(FailureMgr.Trigger.arm, [], me);
|
||||||
me.counter.reset();
|
me.counter.reset();
|
||||||
me.activation_cycles =
|
me.activation_cycles =
|
||||||
norm_rand(me.params["mcbf"], me.params["mcbf"] / 10);
|
norm_rand(me.params["mcbf"], me.params["mcbf"] / 10);
|
||||||
|
@ -350,14 +387,17 @@ var McbfTrigger = {
|
||||||
me.enabled and me.counter.enable();
|
me.enabled and me.counter.enable();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
disarm: func {
|
||||||
|
call(FailureMgr.Trigger.disarm, [], me);
|
||||||
|
me.enabled and me.counter.disable();
|
||||||
|
},
|
||||||
|
|
||||||
to_str: func {
|
to_str: func {
|
||||||
sprintf("Mean cycles between failures: %.2f", me.params["mcbf"]);
|
sprintf("Mean cycles between failures: %.2f", me.params["mcbf"]);
|
||||||
},
|
},
|
||||||
|
|
||||||
_on_cycle: func(cycles) {
|
_on_cycle: func(cycles) {
|
||||||
if (!me.fired and cycles > me.activation_cycles) {
|
if (!me.fired and cycles > me.activation_cycles) {
|
||||||
# TODO: Why this doesn't work?
|
|
||||||
# me.counter.disable();
|
|
||||||
me.fired = 1;
|
me.fired = 1;
|
||||||
me.on_fire();
|
me.on_fire();
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Represents one way things can go wrong, for example "a blown tire".
|
# Represents one way things can go wrong, for example "a blown tire".
|
||||||
|
|
||||||
|
@ -53,9 +54,7 @@ var FailureMode = {
|
||||||
# and 1 total failure.
|
# and 1 total failure.
|
||||||
|
|
||||||
set_failure_level: func(level) {
|
set_failure_level: func(level) {
|
||||||
me._path != nil or
|
assert(me._path != nil, "FailureMode.set_failure_level: unbound mode");
|
||||||
die("FailureMode.set_failure_level: Unbound failure mode");
|
|
||||||
|
|
||||||
setprop(me._path ~ me.id ~ "/failure-level", level);
|
setprop(me._path ~ me.id ~ "/failure-level", level);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -64,8 +63,7 @@ var FailureMode = {
|
||||||
|
|
||||||
_set_failure_level: func(level) {
|
_set_failure_level: func(level) {
|
||||||
me.actuator.set_failure_level(level);
|
me.actuator.set_failure_level(level);
|
||||||
me._log_failure(sprintf("%s failure level %d%%",
|
_failmgr.log(sprintf("%s condition %d%%", me.description, (1-level)*100));
|
||||||
me.description, level*100));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -80,7 +78,7 @@ var FailureMode = {
|
||||||
# path/failure-level (double, rw)
|
# path/failure-level (double, rw)
|
||||||
|
|
||||||
bind: func(path) {
|
bind: func(path) {
|
||||||
me._path == nil or die("FailureMode.bind: mode already bound");
|
assert(me._path == nil, "FailureMode.bind: mode already bound");
|
||||||
|
|
||||||
var prop = path ~ me.id ~ "/failure-level";
|
var prop = path ~ me.id ~ "/failure-level";
|
||||||
props.globals.initNode(prop, me.actuator.get_failure_level(), "DOUBLE");
|
props.globals.initNode(prop, me.actuator.get_failure_level(), "DOUBLE");
|
||||||
|
@ -95,37 +93,31 @@ var FailureMode = {
|
||||||
me._path != nil and props.globals.getNode(me._path ~ me.id).remove();
|
me._path != nil and props.globals.getNode(me._path ~ me.id).remove();
|
||||||
me._path = nil;
|
me._path = nil;
|
||||||
},
|
},
|
||||||
|
|
||||||
##
|
|
||||||
# Send a message to the logging facilities, currently the screen and
|
|
||||||
# the console.
|
|
||||||
|
|
||||||
_log_failure: func(message) {
|
|
||||||
print(getprop("/sim/time/gmt-string") ~ " : " ~ message);
|
|
||||||
if (getprop(proproot ~ "/display-on-screen"))
|
|
||||||
screen.log.write(message, 1.0, 0.0, 0.0);
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
##
|
##
|
||||||
# Implements the FailureMgr functionality.
|
# Implements the FailureMgr functionality.
|
||||||
#
|
#
|
||||||
# It is wrapped into an object to leave the door open to several evolution
|
# It is wrapped into an object to leave the door open to several evolution
|
||||||
# approaches, for example moving the implementation down to the C++ engine,
|
# approaches, for example moving the implementation down to the C++ engine.
|
||||||
# or supporting several independent instances of the failure manager.
|
# Additionally, it also serves to isolate implementation details into its own
|
||||||
# Additionally, it also serves to isolate low level implementation details
|
# namespace.
|
||||||
# into its own namespace.
|
|
||||||
|
|
||||||
var _failmgr = {
|
var _failmgr = {
|
||||||
|
|
||||||
|
pollable_trigger_count: 0,
|
||||||
|
enable_after_teleport: 0,
|
||||||
|
|
||||||
timer: nil,
|
timer: nil,
|
||||||
update_period: 10, # 0.1 Hz
|
update_period: 10, # 0.1 Hz
|
||||||
|
|
||||||
failure_modes: {},
|
failure_modes: {},
|
||||||
pollable_trigger_count: 0,
|
logbuf: events.LogBuffer.new(echo: 1),
|
||||||
|
|
||||||
init: func {
|
init: func {
|
||||||
me.timer = maketimer(me.update_period, func me._update());
|
me.timer = maketimer(me.update_period, func me._update());
|
||||||
setlistener("sim/signals/reinit", func me._on_reinit());
|
setlistener("sim/signals/reinit", func(n) me._on_teleport(n));
|
||||||
|
setlistener("sim/signals/fdm-initialized", func(n) me._on_teleport(n));
|
||||||
|
|
||||||
props.globals.initNode(proproot ~ "display-on-screen", 1, "BOOL");
|
props.globals.initNode(proproot ~ "display-on-screen", 1, "BOOL");
|
||||||
props.globals.initNode(proproot ~ "enabled", 1, "BOOL");
|
props.globals.initNode(proproot ~ "enabled", 1, "BOOL");
|
||||||
|
@ -133,10 +125,6 @@ var _failmgr = {
|
||||||
func (n) { n.getValue() ? me._enable() : me._disable() });
|
func (n) { n.getValue() ? me._enable() : me._disable() });
|
||||||
},
|
},
|
||||||
|
|
||||||
##
|
|
||||||
# Subscribe a new failure mode to the system.
|
|
||||||
# mode: FailureMode object.
|
|
||||||
|
|
||||||
add_failure_mode: func(mode) {
|
add_failure_mode: func(mode) {
|
||||||
contains(me.failure_modes, mode.id) and
|
contains(me.failure_modes, mode.id) and
|
||||||
die("add_failure_mode: failure mode already exists: " ~ id);
|
die("add_failure_mode: failure mode already exists: " ~ id);
|
||||||
|
@ -145,37 +133,40 @@ var _failmgr = {
|
||||||
mode.bind(proproot);
|
mode.bind(proproot);
|
||||||
},
|
},
|
||||||
|
|
||||||
##
|
get_failure_modes: func {
|
||||||
# Remove a failure mode from the system.
|
var modes = [];
|
||||||
# id: FailureMode id string, e.g. "systems/pitot"
|
|
||||||
|
foreach (var k; keys(me.failure_modes)) {
|
||||||
|
var m = me.failure_modes[k];
|
||||||
|
append(modes, {
|
||||||
|
id: k,
|
||||||
|
description: m.mode.description });
|
||||||
|
}
|
||||||
|
|
||||||
|
return modes;
|
||||||
|
},
|
||||||
|
|
||||||
remove_failure_mode: func(id) {
|
remove_failure_mode: func(id) {
|
||||||
contains(me.failure_modes, id) or
|
contains(me.failure_modes, id) or
|
||||||
die("remove_failure_mode: failure mode does not exist: " ~ mode_id);
|
die("remove_failure_mode: failure mode does not exist: " ~ id);
|
||||||
|
|
||||||
var trigger = me.failure_modes[id].trigger;
|
var trigger = me.failure_modes[id].trigger;
|
||||||
if (trigger != nil)
|
if (trigger != nil)
|
||||||
me._discard_trigger(trigger);
|
me._discard_trigger(trigger);
|
||||||
|
|
||||||
me.failure_modes[id].unbind();
|
me.failure_modes[id].mode.unbind();
|
||||||
props.globals.getNode(proproot ~ id).remove();
|
|
||||||
delete(me.failure_modes, id);
|
delete(me.failure_modes, id);
|
||||||
},
|
},
|
||||||
|
|
||||||
##
|
|
||||||
# Removes all failure modes from the system.
|
|
||||||
|
|
||||||
remove_all: func {
|
remove_all: func {
|
||||||
foreach(var id; keys(me.failure_modes))
|
foreach(var id; keys(me.failure_modes))
|
||||||
me.remove_failure_mode(id);
|
me.remove_failure_mode(id);
|
||||||
},
|
},
|
||||||
|
|
||||||
##
|
repair_all: func {
|
||||||
# Attach a trigger to the given failure mode. Discards the current trigger
|
foreach(var id; keys(me.failure_modes))
|
||||||
# if any.
|
me.failure_modes[id].mode.set_failure_level(0);
|
||||||
#
|
},
|
||||||
# mode_id: FailureMode id string, e.g. "systems/pitot"
|
|
||||||
# trigger: Trigger object or nil.
|
|
||||||
|
|
||||||
set_trigger: func(mode_id, trigger) {
|
set_trigger: func(mode_id, trigger) {
|
||||||
contains(me.failure_modes, mode_id) or
|
contains(me.failure_modes, mode_id) or
|
||||||
|
@ -191,7 +182,6 @@ var _failmgr = {
|
||||||
|
|
||||||
trigger.bind(proproot ~ mode_id);
|
trigger.bind(proproot ~ mode_id);
|
||||||
trigger.on_fire = func _failmgr.on_trigger_activated(trigger);
|
trigger.on_fire = func _failmgr.on_trigger_activated(trigger);
|
||||||
trigger.reset();
|
|
||||||
|
|
||||||
if (trigger.requires_polling) {
|
if (trigger.requires_polling) {
|
||||||
me.pollable_trigger_count += 1;
|
me.pollable_trigger_count += 1;
|
||||||
|
@ -200,13 +190,10 @@ var _failmgr = {
|
||||||
me.timer.start();
|
me.timer.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (me.enabled())
|
||||||
trigger.enable();
|
trigger.enable();
|
||||||
},
|
},
|
||||||
|
|
||||||
##
|
|
||||||
# Returns the trigger object attached to the given failure mode.
|
|
||||||
# mode_id: FailureMode id string, e.g. "systems/pitot"
|
|
||||||
|
|
||||||
get_trigger: func(mode_id) {
|
get_trigger: func(mode_id) {
|
||||||
contains(me.failure_modes, mode_id) or
|
contains(me.failure_modes, mode_id) or
|
||||||
die("get_trigger: failure mode does not exist: " ~ mode_id);
|
die("get_trigger: failure mode does not exist: " ~ mode_id);
|
||||||
|
@ -216,24 +203,32 @@ var _failmgr = {
|
||||||
|
|
||||||
##
|
##
|
||||||
# Observer interface. Called from asynchronous triggers when they fire.
|
# Observer interface. Called from asynchronous triggers when they fire.
|
||||||
# trigger: Reference to the calling trigger.
|
# trigger: Reference to the firing trigger.
|
||||||
|
|
||||||
on_trigger_activated: func(trigger) {
|
on_trigger_activated: func(trigger) {
|
||||||
|
assert(me.enabled(), "A " ~ trigger.type ~ " trigger fired while the FailureMgr was disabled");
|
||||||
var found = 0;
|
var found = 0;
|
||||||
|
|
||||||
foreach (var id; keys(me.failure_modes)) {
|
foreach (var id; keys(me.failure_modes)) {
|
||||||
if (me.failure_modes[id].trigger == trigger) {
|
if (me.failure_modes[id].trigger == trigger) {
|
||||||
me.failure_modes[id].mode.set_failure_level(1);
|
|
||||||
found = 1;
|
found = 1;
|
||||||
|
me.failure_modes[id].mode.set_failure_level(1);
|
||||||
|
trigger.disarm();
|
||||||
|
FailureMgr.events["trigger-fired"].notify(
|
||||||
|
{ mode_id: id, trigger: trigger });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
found or die("FailureMgr.on_trigger_activated: trigger not found");
|
assert(found, "FailureMgr.on_trigger_activated: trigger not found");
|
||||||
},
|
},
|
||||||
|
|
||||||
##
|
##
|
||||||
# Enable the failure manager.
|
# Enable the failure manager. Starts the trigger poll timer and enables
|
||||||
|
# all triggers.
|
||||||
|
#
|
||||||
|
# Called from /sim/failure-manager/enabled and during a teleport if the
|
||||||
|
# FM was enabled when the teleport was initiated.
|
||||||
|
|
||||||
_enable: func {
|
_enable: func {
|
||||||
foreach(var id; keys(me.failure_modes)) {
|
foreach(var id; keys(me.failure_modes)) {
|
||||||
|
@ -248,6 +243,7 @@ var _failmgr = {
|
||||||
##
|
##
|
||||||
# Suspends failure manager activity. Pollable triggers will not be updated
|
# Suspends failure manager activity. Pollable triggers will not be updated
|
||||||
# and all triggers will be disabled.
|
# and all triggers will be disabled.
|
||||||
|
# Called from /sim/failure-manager/enabled and during a teleport.
|
||||||
|
|
||||||
_disable: func {
|
_disable: func {
|
||||||
me.timer.stop();
|
me.timer.stop();
|
||||||
|
@ -259,13 +255,16 @@ var _failmgr = {
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
##
|
|
||||||
# Returns enabled status.
|
|
||||||
|
|
||||||
enabled: func {
|
enabled: func {
|
||||||
getprop(proproot ~ "enabled");
|
getprop(proproot ~ "enabled");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
log: func(message) {
|
||||||
|
me.logbuf.push(message);
|
||||||
|
if (getprop(proproot ~ "/display-on-screen"))
|
||||||
|
screen.log.write(message, 1.0, 0.0, 0.0);
|
||||||
|
},
|
||||||
|
|
||||||
##
|
##
|
||||||
# Poll loop. Updates pollable triggers and applies a failure level
|
# Poll loop. Updates pollable triggers and applies a failure level
|
||||||
# when they fire.
|
# when they fire.
|
||||||
|
@ -273,18 +272,23 @@ var _failmgr = {
|
||||||
_update: func {
|
_update: func {
|
||||||
foreach (var id; keys(me.failure_modes)) {
|
foreach (var id; keys(me.failure_modes)) {
|
||||||
var failure = me.failure_modes[id];
|
var failure = me.failure_modes[id];
|
||||||
|
var trigger = failure.trigger;
|
||||||
|
|
||||||
if (failure.trigger != nil and !failure.trigger.fired) {
|
if (trigger == nil or !trigger.requires_polling or !trigger.armed)
|
||||||
var level = failure.trigger.update();
|
continue;
|
||||||
if (level > 0 and level != failure.mode.get_failure_level())
|
|
||||||
|
var level = trigger.update();
|
||||||
|
if (level == 0) continue;
|
||||||
|
|
||||||
|
if (level != failure.mode.get_failure_level())
|
||||||
failure.mode.set_failure_level(level);
|
failure.mode.set_failure_level(level);
|
||||||
}
|
trigger.disarm();
|
||||||
|
|
||||||
|
FailureMgr.events["trigger-fired"].notify(
|
||||||
|
{ mode_id: id, trigger: trigger });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
##
|
|
||||||
# Detaches a trigger from the system.
|
|
||||||
|
|
||||||
_discard_trigger: func(trigger) {
|
_discard_trigger: func(trigger) {
|
||||||
trigger.disable();
|
trigger.disable();
|
||||||
trigger.unbind();
|
trigger.unbind();
|
||||||
|
@ -296,17 +300,47 @@ var _failmgr = {
|
||||||
},
|
},
|
||||||
|
|
||||||
##
|
##
|
||||||
# Reinit listener. Sets all failure modes to "working fine".
|
# Teleport listener. During repositioning, all triggers are disabled to
|
||||||
|
# avoid them firing in a possibly inconsistent state.
|
||||||
|
|
||||||
_on_reinit: func {
|
_on_teleport: func(pnode) {
|
||||||
foreach (var id; keys(me.failure_modes)) {
|
|
||||||
var failure = me.failure_modes[id];
|
|
||||||
|
|
||||||
failure.mode.set_failure_level(0);
|
if (pnode.getName() == "fdm-initialized") {
|
||||||
|
if (me.enable_after_teleport) {
|
||||||
|
me._enable();
|
||||||
|
me.enable_after_teleport = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
# then, it's /sim/signals/reinit
|
||||||
|
# only react when the signal raises to true.
|
||||||
|
if (pnode.getValue() == 1) {
|
||||||
|
me.enable_after_teleport = me.enabled();
|
||||||
|
me._disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
if (failure.trigger != nil) {
|
dump_status: func(mode_ids=nil) {
|
||||||
me._discard_trigger(failure.trigger);
|
|
||||||
failure.trigger = nil;
|
if (mode_ids == nil)
|
||||||
|
mode_ids = keys(me.failure_modes);
|
||||||
|
|
||||||
|
print("\nFailureMgr Status\n----------------------------------------");
|
||||||
|
|
||||||
|
foreach(var id; mode_ids) {
|
||||||
|
var mode = me.failure_modes[id].mode;
|
||||||
|
var trigger = me.failure_modes[id].trigger;
|
||||||
|
|
||||||
|
print(id, ": failure level ", mode.get_failure_level());
|
||||||
|
|
||||||
|
if (trigger == nil) {
|
||||||
|
print(" no trigger");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
print(" ", trigger.type, " trigger (",
|
||||||
|
trigger.enabled? "enabled, " : "disabled, ",
|
||||||
|
trigger.armed? "armed)" : "disarmed)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,46 @@
|
||||||
var proproot = "sim/failure-manager/";
|
var proproot = "sim/failure-manager/";
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# Nasal modules can subscribe to FailureMgr events.
|
||||||
|
# Each event has an independent dispatcher so modules can subscribe only to
|
||||||
|
# the events they are interested in. This also simplifies processing at client
|
||||||
|
# side by being able to subscibe different callbacks to different events.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
#
|
||||||
|
# var handle = FailureMgr.events["trigger-fired"].subscribe(my_callback);
|
||||||
|
|
||||||
|
var events = {};
|
||||||
|
|
||||||
|
# Event: trigger-fired
|
||||||
|
# Format: { mode_id: <failure mode id>, trigger: <trigger that fired> }
|
||||||
|
events["trigger-fired"] = globals.events.EventDispatcher.new();
|
||||||
|
|
||||||
|
##
|
||||||
|
# Encodes a pair "category" and "failure_mode" into a "mode_id".
|
||||||
|
#
|
||||||
|
# These just have the simple form "category/mode", and are used to refer to
|
||||||
|
# failure modes throughout the FailureMgr API and to create a path within the
|
||||||
|
# sim/failure-manager property tree for the failure mode.
|
||||||
|
#
|
||||||
|
# examples of categories:
|
||||||
|
# structural, instrumentation, controls, sensors, etc...
|
||||||
|
#
|
||||||
|
# examples of failure modes:
|
||||||
|
# altimeter, pitot, left-tire, landing-light, etc...
|
||||||
|
|
||||||
|
var get_id = func(category, failure_mode) {
|
||||||
|
return sprintf("%s/%s", string.normpath(category), failure_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
##
|
||||||
|
# Returns a vector containing: [category, failure_mode]
|
||||||
|
|
||||||
|
var split_id = func(mode_id) {
|
||||||
|
return [string.normpath(io.dirname(mode_id)), io.basename(mode_id)];
|
||||||
|
}
|
||||||
|
|
||||||
##
|
##
|
||||||
# Subscribe a new failure mode to the system.
|
# Subscribe a new failure mode to the system.
|
||||||
#
|
#
|
||||||
|
@ -38,6 +78,15 @@ var add_failure_mode = func(id, description, actuator) {
|
||||||
FailureMode.new(id, description, actuator));
|
FailureMode.new(id, description, actuator));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
##
|
||||||
|
# Returns a vector with all failure modes in the system.
|
||||||
|
# Each vector entry is a hash with the following keys:
|
||||||
|
# { id, description }
|
||||||
|
|
||||||
|
var get_failure_modes = func() {
|
||||||
|
_failmgr.get_failure_modes();
|
||||||
|
}
|
||||||
|
|
||||||
##
|
##
|
||||||
# Remove a failure mode from the system.
|
# Remove a failure mode from the system.
|
||||||
# id: FailureMode id string, e.g. "systems/pitot"
|
# id: FailureMode id string, e.g. "systems/pitot"
|
||||||
|
@ -79,10 +128,36 @@ var get_trigger = func(mode_id) {
|
||||||
# level: Floating point number in the range [0, 1]
|
# level: Floating point number in the range [0, 1]
|
||||||
# Zero represents no failure and one means total failure.
|
# Zero represents no failure and one means total failure.
|
||||||
|
|
||||||
var set_failure_level = func (mode_id, level) {
|
var set_failure_level = func(mode_id, level) {
|
||||||
setprop(proproot ~ mode_id ~ "/failure-level", level);
|
setprop(proproot ~ mode_id ~ "/failure-level", level);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
##
|
||||||
|
# Returns the current failure level for the given failure mode.
|
||||||
|
# mode_id: Failure mode id string.
|
||||||
|
|
||||||
|
var get_failure_level = func(mode_id) {
|
||||||
|
getprop(proproot ~ mode_id ~ "/failure-level");
|
||||||
|
}
|
||||||
|
|
||||||
|
##
|
||||||
|
# Restores all failure modes to level = 0
|
||||||
|
|
||||||
|
var repair_all = func {
|
||||||
|
_failmgr.repair_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
##
|
||||||
|
# Returns a vector of timestamped failure manager events, such as the
|
||||||
|
# messages shown in the console when there are changes to the failure conditions.
|
||||||
|
#
|
||||||
|
# Each entry in the vector has the following format:
|
||||||
|
# { time: <time stamp>, message: <event description> }
|
||||||
|
|
||||||
|
var get_log_buffer = func {
|
||||||
|
_failmgr.logbuf.get_buffer();
|
||||||
|
}
|
||||||
|
|
||||||
##
|
##
|
||||||
# Allows applications to disable the failure manager and restore it later on.
|
# Allows applications to disable the failure manager and restore it later on.
|
||||||
# While disabled, no failure modes will be activated from the failure manager.
|
# While disabled, no failure modes will be activated from the failure manager.
|
||||||
|
@ -109,13 +184,16 @@ var disable = func setprop(proproot ~ "enabled", 0);
|
||||||
|
|
||||||
var Trigger = {
|
var Trigger = {
|
||||||
|
|
||||||
|
type: nil,
|
||||||
# 1 for pollable triggers, 0 for async triggers.
|
# 1 for pollable triggers, 0 for async triggers.
|
||||||
requires_polling: 0,
|
requires_polling: 0,
|
||||||
|
enabled: 0,
|
||||||
|
|
||||||
new: func {
|
new: func {
|
||||||
return {
|
return {
|
||||||
parents: [Trigger],
|
parents: [Trigger],
|
||||||
params: {},
|
params: {},
|
||||||
|
armed: 0,
|
||||||
fired: 0,
|
fired: 0,
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -127,13 +205,6 @@ var Trigger = {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
##
|
|
||||||
# Enables/disables the trigger. While a trigger is disabled, any timer
|
|
||||||
# or listener that could potentially own shall be disabled.
|
|
||||||
|
|
||||||
enable: func,
|
|
||||||
disable: func,
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Forces a check of the firing conditions. Returns 1 if the trigger fired,
|
# Forces a check of the firing conditions. Returns 1 if the trigger fired,
|
||||||
# 0 otherwise.
|
# 0 otherwise.
|
||||||
|
@ -150,52 +221,84 @@ var Trigger = {
|
||||||
# call to reset()
|
# call to reset()
|
||||||
|
|
||||||
set_param: func(param, value) {
|
set_param: func(param, value) {
|
||||||
|
assert(me._path != nil, "Trigger.set_param: unbound trigger");
|
||||||
|
|
||||||
contains(me.params, param) or
|
contains(me.params, param) or
|
||||||
die("Trigger.set_param: undefined param: " ~ param);
|
die("Trigger.set_param: undefined param: " ~ param);
|
||||||
|
|
||||||
me._path != nil or
|
|
||||||
die("Trigger.set_param: Unbound trigger");
|
|
||||||
|
|
||||||
setprop(sprintf("%s/%s",me._path, param), value);
|
setprop(sprintf("%s/%s",me._path, param), value);
|
||||||
},
|
},
|
||||||
|
|
||||||
##
|
##
|
||||||
# Reload trigger parameters and reset internal state, i.e. start from
|
# Load trigger parameters and reset internal state. Once armed, the trigger
|
||||||
# scratch. If the trigger was fired, the trigger is set to not fired.
|
# will fire as soon as the right conditions are met. It can be called after
|
||||||
|
# the trigger fires to rearm it again.
|
||||||
|
#
|
||||||
|
# The "armed" condition survives enable/disable calls.
|
||||||
|
|
||||||
reset: func {
|
arm: func {
|
||||||
me._path or die("Trigger.reset: unbound trigger");
|
assert(me._path != nil, "Trigger.arm: unbound trigger");
|
||||||
|
setprop(me._path ~ "/armed", 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
_arm: func {
|
||||||
foreach (var p; keys(me.params))
|
foreach (var p; keys(me.params))
|
||||||
me.params[p] = getprop(sprintf("%s/%s", me._path, p));
|
me.params[p] = getprop(sprintf("%s/%s", me._path, p));
|
||||||
|
|
||||||
me.fired = 0;
|
me.fired = 0;
|
||||||
me._path != nil and setprop(me._path ~ "/reset", 0);
|
me.armed = 1;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
##
|
||||||
|
# Deactivate the trigger. The trigger will not fire until rearmed again.
|
||||||
|
|
||||||
|
disarm: func {
|
||||||
|
assert(me._path != nil, "Trigger.disarm: unbound trigger");
|
||||||
|
setprop(me._path ~ "/armed", 0);
|
||||||
|
},
|
||||||
|
|
||||||
|
_disarm: func {
|
||||||
|
me.armed = 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
##
|
||||||
|
# Enables/disables the trigger. While a trigger is disabled, any timer
|
||||||
|
# or listener that could potentially own shall be disabled.
|
||||||
|
#
|
||||||
|
# The FailureMgr calls these methods when the entire system is
|
||||||
|
# enabled/disabled. By keeping enabled/disabled state separated from
|
||||||
|
# armed/disarmed allows the FailureMgr to keep its configuration while
|
||||||
|
# disabled, i.e. those triggers that where armed when the system was
|
||||||
|
# disabled will resume when the system is enabled again.
|
||||||
|
#
|
||||||
|
# The FailureMgr disables itself during a teleport.
|
||||||
|
|
||||||
|
enable: func { me.enabled = 1 },
|
||||||
|
disable: func { me.enabled = 0 },
|
||||||
|
|
||||||
##
|
##
|
||||||
# Creates an interface for the trigger in the property tree.
|
# Creates an interface for the trigger in the property tree.
|
||||||
# Every parameter in the params hash will be exposed, in addition to
|
# Every parameter in the params hash will be exposed, in addition to
|
||||||
# a path/reset property for resetting the trigger from the prop tree.
|
# a path/reset property for resetting the trigger from the prop tree.
|
||||||
|
|
||||||
bind: func(path) {
|
bind: func(path) {
|
||||||
me._path == nil or
|
assert(me._path == nil, "Trigger.bind: trigger already bound");
|
||||||
die("Trigger.bind(): attempt to bind an already bound trigger");
|
|
||||||
|
|
||||||
me._path = path;
|
me._path = path;
|
||||||
props.globals.getNode(path) != nil or props.globals.initNode(path);
|
props.globals.getNode(path) != nil or props.globals.initNode(path);
|
||||||
props.globals.getNode(path).setValues(me.params);
|
props.globals.getNode(path).setValues(me.params);
|
||||||
|
|
||||||
var reset_prop = path ~ "/reset";
|
var prop = path ~ "/armed";
|
||||||
props.globals.initNode(reset_prop, 0, "BOOL");
|
props.globals.initNode(prop, 0, "BOOL");
|
||||||
setlistener(reset_prop, func me.reset(), 0, 0);
|
setlistener(prop,
|
||||||
|
func(p) { p.getValue() ? me._arm() : me._disarm() }, 0, 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
##
|
##
|
||||||
# Removes this trigger's interface from the property tree.
|
# Removes this trigger's interface from the property tree.
|
||||||
|
|
||||||
unbind: func {
|
unbind: func {
|
||||||
props.globals.getNode(me._path ~ "/reset").remove();
|
props.globals.getNode(me._path ~ "/armed").remove();
|
||||||
foreach (var p; keys(me.params))
|
foreach (var p; keys(me.params))
|
||||||
props.globals.getNode(me._path ~ "/" ~ p).remove();
|
props.globals.getNode(me._path ~ "/" ~ p).remove();
|
||||||
|
|
||||||
|
|
108
Nasal/events.nas
Normal file
108
Nasal/events.nas
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
# events.nas - Generic objects for managing events
|
||||||
|
#
|
||||||
|
# Copyright (C) 2014 Anton Gomez Alvedro
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# The EventDispatcher is a simple helper object that keeps a list of listeners
|
||||||
|
# and allows a calling entity to send an "event" object to all subscribers.
|
||||||
|
#
|
||||||
|
# It is intended to be used internally by modules that want to offer a
|
||||||
|
# subscription interface so other modules can receive notifications.
|
||||||
|
# For an example of how to use it this way, check Nasal/FailureMgr
|
||||||
|
|
||||||
|
var EventDispatcher = (func {
|
||||||
|
|
||||||
|
var global_id = 0;
|
||||||
|
var getid = func { global_id += 1 };
|
||||||
|
|
||||||
|
return {
|
||||||
|
new: func {
|
||||||
|
var m = { parents: [EventDispatcher] };
|
||||||
|
m._subscribers = {};
|
||||||
|
return m;
|
||||||
|
},
|
||||||
|
|
||||||
|
notify: func(event) {
|
||||||
|
foreach(var id; keys(me._subscribers))
|
||||||
|
me._subscribers[id](event);
|
||||||
|
},
|
||||||
|
|
||||||
|
subscribe: func(callback) {
|
||||||
|
assert(typeof(callback) == "func");
|
||||||
|
|
||||||
|
var id = getid();
|
||||||
|
me._subscribers[id] = callback;
|
||||||
|
return id;
|
||||||
|
},
|
||||||
|
|
||||||
|
unsubscribe: func(id) {
|
||||||
|
delete(me._subscribers, id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# Stores messages in a circular buffer that then can be retrieved at any point.
|
||||||
|
# Messages are time stamped when pushed into the buffer, and the time stamp is
|
||||||
|
# kept by the message.
|
||||||
|
|
||||||
|
var LogBuffer = {
|
||||||
|
|
||||||
|
new: func (max_messages = 128, echo = 0) {
|
||||||
|
assert(max_messages > 1, "come on, lets be serious..");
|
||||||
|
|
||||||
|
var m = { parents: [LogBuffer] };
|
||||||
|
m.echo = echo;
|
||||||
|
m.max_messages = max_messages;
|
||||||
|
m.buffer = setsize([], max_messages);
|
||||||
|
m.full = m.wp = 0;
|
||||||
|
|
||||||
|
return m;
|
||||||
|
},
|
||||||
|
|
||||||
|
push: func(message) {
|
||||||
|
var stamp = getprop("/sim/time/gmt-string");
|
||||||
|
if (me.echo) print(stamp, " ", message);
|
||||||
|
|
||||||
|
me.buffer[me.wp] = { time: stamp, message: message };
|
||||||
|
me.wp += 1;
|
||||||
|
if (me.wp == me.max_messages) {
|
||||||
|
me.wp = 0;
|
||||||
|
me.full = 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
clear: func {
|
||||||
|
me.full = me.wp = 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
##
|
||||||
|
# Returns a vector with all messages, starting with the oldest one.
|
||||||
|
# Each vector entry is a hash with the format:
|
||||||
|
# { time: <timestamp_str>, message: <the message> }
|
||||||
|
|
||||||
|
get_buffer: func {
|
||||||
|
if (me.full)
|
||||||
|
!me.wp ? me.buffer : me.buffer[me.wp:-1] ~ me.buffer[0:me.wp-1];
|
||||||
|
elsif (me.wp == 0)
|
||||||
|
[];
|
||||||
|
else
|
||||||
|
me.buffer[0:me.wp-1];
|
||||||
|
}
|
||||||
|
};
|
Loading…
Reference in a new issue