1
0
Fork 0

modules.nas: change reload trigger from property to fgcommand, fix a logprint call, add module name to debug output

This commit is contained in:
Henning Stahlke 2020-04-07 22:46:04 +02:00 committed by James Turner
parent 1035ed4269
commit 251a376f68

View file

@ -4,16 +4,16 @@
# created: 12/2019 # created: 12/2019
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
# modules.nas allowes to load and unload Nasal modules at runtime (e.g. without # modules.nas allowes to load and unload Nasal modules at runtime (e.g. without
# restarting Flightgear as a whole). It implements resource tracking for # restarting Flightgear as a whole). It implements resource tracking for
# setlistener and maketimer to make unloading easier # setlistener and maketimer to make unloading easier.
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
# Example - generic module load: # Example - generic module load:
# #
# if (modules.isAvailable("foo_bar")) { # if (modules.isAvailable("foo_bar")) {
# modules.load("foo_bar"); # modules.load("foo_bar");
# } # }
# #
# Example - create an aircraft nasal system as module # Example - create an aircraft nasal system as module
# (e.g. for rapid reload while development) # (e.g. for rapid reload while development)
# #
# var my_foo_sys = modules.Module.new("my_aircraft_foo"); # var my_foo_sys = modules.Module.new("my_aircraft_foo");
@ -22,14 +22,14 @@
# my_foo_sys.setMainFile("foo.nas"); # my_foo_sys.setMainFile("foo.nas");
# my_foo_sys.load(); # my_foo_sys.load();
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
var MODULES_DIR = getprop("/sim/fg-root")~"/Nasal/modules/"; var MODULES_DIR = getprop("/sim/fg-root")~"/Nasal/modules/";
var MODULES_NODE = nil; var MODULES_NODE = nil;
var MODULES_DEFAULT_FILENAME = "main.nas";
var _modules_available = {}; var _modules_available = {};
# Hash storing Module objects; keep this outside Module to avoid stack overflow # Hash storing Module objects; keep this outside Module to avoid stack overflow
# when using debug.dump # when using debug.dump
var _instances = {}; var _instances = {};
# Class Module # Class Module
# to handle a re-loadable Nasal module at runtime # to handle a re-loadable Nasal module at runtime
@ -37,19 +37,19 @@ var Module = {
_orig_setlistener: setlistener, _orig_setlistener: setlistener,
_orig_maketimer: maketimer, _orig_maketimer: maketimer,
_orig_settimer: settimer, _orig_settimer: settimer,
# id: must be a string without special characters or spaces # id: must be a string without special characters or spaces
# ns: optional namespace name # ns: optional namespace name
# node: optional property node for module management # node: optional property node for module management
new: func(id, ns = "", node = nil) { new: func(id, ns = "", node = nil) {
if (!id or typeof(id) != "scalar") { if (!id or typeof(id) != "scalar") {
logprint(5, "Module.new(): id: must be a string without special characters or spaces"); logprint(LOG_ALERT, "Module.new(): id: must be a string without special characters or spaces");
return; return;
} }
if (_instances[id] != nil) { if (_instances[id] != nil) {
return _instances[id]; return _instances[id];
} }
var obj = { var obj = {
parents: [me], parents: [me],
_listeners: [], _listeners: [],
_timers: [], _timers: [],
@ -58,47 +58,38 @@ var Module = {
id: id, id: id,
version: 1, version: 1,
file_path: MODULES_DIR, file_path: MODULES_DIR,
main_file: "main.nas", main_file: MODULES_DEFAULT_FILENAME,
namespace: ns ? ns : id, namespace: ns ? ns : id,
node: nil, node: nil,
}; };
if (isa(node, props.Node)) { if (isa(node, props.Node)) {
obj.node = node obj.node = node
} else { } else {
obj.node = MODULES_NODE.getNode(id, 1); obj.node = MODULES_NODE.getNode(id, 1);
} }
obj.reloadN = obj.node.initNode("reload", 0, "BOOL");
obj.loadedN = obj.node.initNode("loaded", 0, "BOOL"); obj.loadedN = obj.node.initNode("loaded", 0, "BOOL");
obj.lcountN = obj.node.initNode("listeners", 0, "INT"); obj.lcountN = obj.node.initNode("listeners", 0, "INT");
obj.tcountN = obj.node.initNode("timers", 0, "INT"); obj.tcountN = obj.node.initNode("timers", 0, "INT");
obj.lhitN = obj.node.initNode("listener-hits", 0, "INT"); obj.lhitN = obj.node.initNode("listener-hits", 0, "INT");
obj.reloadL = setlistener(obj.reloadN, func(n) {
if (n.getValue()) {
n.setValue(0);
logprint(3, "Reload listener for ", obj.id, " triggered (",
obj.reloadL, ")");
obj.reload();
}
});
_instances[id] = obj; _instances[id] = obj;
return obj; return obj;
}, },
getNode: func { return me.node; }, getNode: func { return me.node; },
getReloadNode: func { return me.reloadN; },
getNamespaceName: func { return me.namespace; }, getNamespaceName: func { return me.namespace; },
getNamespace: func { return globals[me.namespace]; }, getNamespace: func { return globals[me.namespace]; },
getFilePath: func { return me.file_path; }, getFilePath: func { return me.file_path; },
#return variable from module namespace #return variable from module namespace
get: func(var_name) { get: func(var_name) {
return globals[me.namespace][var_name]; return globals[me.namespace][var_name];
}, },
setDebug: func (debug = 1) { setDebug: func (debug = 1) {
me._debug = debug; me._debug = debug;
logprint(4, "Module "~me.id~" debug = "~debug); logprint(DEV_WARN, "Module "~me.id~" debug = "~debug);
}, },
setFilePath: func(path) { setFilePath: func(path) {
@ -110,40 +101,40 @@ var Module = {
} }
return 0; return 0;
}, },
setMainFile: func(filename) { setMainFile: func(filename) {
if (typeof(filename) == "scalar") { if (typeof(filename) == "scalar") {
me.main_file = filename; me.main_file = filename;
} }
else { else {
logprint("4", "setMainFile() needs a string parameter"); logprint(LOG_WARN, "setMainFile() needs a string parameter");
} }
}, },
setNamespace: func(ns) { setNamespace: func(ns) {
if (typeof(ns) == "scalar") { if (typeof(ns) == "scalar") {
me.namespace = ns; me.namespace = ns;
} }
else { else {
logprint("4", "setNamespace() needs a string parameter"); logprint(LOG_WARN, "setNamespace() needs a string parameter");
} }
}, },
# load module # load module
# if no arguments are given, the Module object will be passed to main() # if no arguments are given, the Module object will be passed to main()
load: func(myargs...) { load: func(myargs...) {
me.loadedN.setBoolValue(0); me.loadedN.setBoolValue(0);
if (globals[me.namespace] == nil) { if (globals[me.namespace] == nil) {
globals[me.namespace] = {}; globals[me.namespace] = {};
} }
logprint(5, "Module.load() ", me.id); logprint(LOG_INFO, "Module.load() ", me.id);
me.lcountN.setIntValue(0); me.lcountN.setIntValue(0);
me.tcountN.setIntValue(0); me.tcountN.setIntValue(0);
me.lhitN.setIntValue(0); me.lhitN.setIntValue(0);
me._redirect_setlistener(); me._redirect_setlistener();
me._redirect_maketimer(); me._redirect_maketimer();
me._redirect_settimer(); me._redirect_settimer();
var filename = me.file_path~"/"~me.main_file; var filename = me.file_path~"/"~me.main_file;
if (io.load_nasal(filename, me.namespace)) { if (io.load_nasal(filename, me.namespace)) {
var main = globals[me.namespace]["main"]; var main = globals[me.namespace]["main"];
@ -162,37 +153,39 @@ var Module = {
me.loadedN.setBoolValue(1); me.loadedN.setBoolValue(1);
} }
return me; return me;
} }
else { return nil; } else { # loading failed
return nil;
}
}, },
# unload a module and remove its tracked resources # unload a module and remove its tracked resources
unload: func() { unload: func() {
if (!me.loadedN.getValue()) { if (!me.loadedN.getValue()) {
logprint(5, "! ", me.id, " was not fully loaded."); logprint(DEV_ALERT, "! ", me.id, " was not fully loaded.");
} }
if (globals[me.namespace] != nil if (globals[me.namespace] != nil
and typeof(globals[me.namespace]) == "hash") and typeof(globals[me.namespace]) == "hash")
{ {
logprint(5, "- Removing module ", me.id); logprint(LOG_INFO, "- Removing module ", me.id);
if (globals[me.namespace]["setlistener"] != nil) if (globals[me.namespace]["setlistener"] != nil)
globals[me.namespace]["setlistener"] = func {}; globals[me.namespace]["setlistener"] = func {};
foreach (var id; me._listeners) { foreach (var id; me._listeners) {
logprint(3, "Removing listener "~id); logprint(DEV_WARN, "Removing listener "~id);
if (removelistener(id)) { if (removelistener(id)) {
me.lcountN.setValue(me.lcountN.getValue() - 1); me.lcountN.setValue(me.lcountN.getValue() - 1);
} }
} }
me._listeners = []; me._listeners = [];
logprint(3, "Stopping timers "); logprint(LOG_INFO, "Stopping timers ");
if (globals[me.namespace]["maketimer"] != nil) if (globals[me.namespace]["maketimer"] != nil)
globals[me.namespace]["maketimer"] = func {}; globals[me.namespace]["maketimer"] = func {};
foreach (var t; me._timers) { foreach (var t; me._timers) {
if (typeof(t.stop) == "func") { if (typeof(t.stop) == "func") {
t.stop(); t.stop();
me.tcountN.setValue(me.tcountN.getValue() - 1); me.tcountN.setValue(me.tcountN.getValue() - 1);
logprint(2, " ."); logprint(DEV_WARN, " .");
} }
} }
me._timers = []; me._timers = [];
@ -208,32 +201,32 @@ var Module = {
} }
} }
me.loadedN.setBoolValue(0); me.loadedN.setBoolValue(0);
#kill namespace (replace with empty hash and hope GC cleans up behind us) #kill namespace (and hope GC will clean up behind us)
globals[me.namespace] = nil; globals[me.namespace] = nil;
} }
}, },
reload: func() { reload: func() {
me.unload(); me.unload();
me.load(); me.load();
}, },
printTrackedResources: func(loglevel = 3) { printTrackedResources: func(loglevel = LOG_INFO) {
logprint(loglevel, "Tracked resources after running the main() function of " ~ logprint(loglevel, "Tracked resources after running the main() function of " ~
me.id~":"); me.id~":");
logprint(loglevel, "#listeners: "~size(me._listeners)); logprint(loglevel, "#listeners: "~size(me._listeners));
logprint(loglevel, "#timers: "~size(me._timers)); logprint(loglevel, "#timers: "~size(me._timers));
logprint(loglevel, "Use log level DEBUG to see all calls to the " ~ logprint(loglevel, "Use log level DEBUG to see all calls to the " ~
"setlistener() and maketimer() wrappers."); "setlistener() and maketimer() wrappers.");
}, },
# redirect setlistener() for module # redirect setlistener() for module
_redirect_setlistener: func() { _redirect_setlistener: func() {
globals[me.namespace].setlistener = func(p, f, start=0, runtime=0) { globals[me.namespace].setlistener = func(p, f, start=0, runtime=0) {
if (!isa(p, props.Node)) { if (!isa(p, props.Node)) {
p = props.getNode(p, 1).resolveAlias(); p = props.getNode(p, 1).resolveAlias();
} }
if (me._debug) { if (me._debug) {
var f_debug = func { var f_debug = func {
me.lhitN.setValue(me.lhitN.getValue() + 1); me.lhitN.setValue(me.lhitN.getValue() + 1);
@ -245,35 +238,35 @@ var Module = {
append(me._listeners, Module._orig_setlistener(p, f_debug, start, runtime)); append(me._listeners, Module._orig_setlistener(p, f_debug, start, runtime));
var c = caller(1); var c = caller(1);
if (c != nil) { if (c != nil) {
print(sprintf("setlistener for %s called from %s:%s", print(sprintf("[%s] setlistener for %s called from %s:%s",
p.getPath(), io.basename(c[2]), c[3])); me.namespace, p.getPath(), io.basename(c[2]), c[3]));
}; };
} else { } else {
append(me._listeners, Module._orig_setlistener(p, append(me._listeners, Module._orig_setlistener(p,
f, start, runtime)); f, start, runtime));
} }
me.lcountN.setValue(me.lcountN.getValue() + 1); me.lcountN.setValue(me.lcountN.getValue() + 1);
} }
me.setlistener = globals[me.namespace].setlistener; me.setlistener = globals[me.namespace].setlistener;
}, },
# redirect maketimer for module # redirect maketimer for module
_redirect_maketimer: func() { _redirect_maketimer: func() {
globals[me.namespace].maketimer = func() { globals[me.namespace].maketimer = func() {
if (size(arg) == 2) { if (size(arg) == 2) {
append(me._timers, Module._orig_maketimer(arg[0], arg[1])); append(me._timers, Module._orig_maketimer(arg[0], arg[1]));
} elsif (size(arg) == 3) { } elsif (size(arg) == 3) {
append(me._timers, append(me._timers,
Module._orig_maketimer(arg[0], arg[1], arg[2])); Module._orig_maketimer(arg[0], arg[1], arg[2]));
} else { } else {
logprint(5, "Invalid number of arguments to maketimer()"); logprint(DEV_ALERT, "Invalid number of arguments to maketimer()");
return; return;
} }
if (me._debug) { if (me._debug) {
var c = caller(1); var c = caller(1);
if (c != nil) { if (c != nil) {
print(sprintf("maketimer called from %s:%s", print(sprintf("[%s] maketimer called from %s:%s",
io.basename(c[2]), c[3])); me.namespace, io.basename(c[2]), c[3]));
}; };
} }
me.tcountN.setValue(me.tcountN.getValue() + 1); me.tcountN.setValue(me.tcountN.getValue() + 1);
@ -281,11 +274,11 @@ var Module = {
} }
me.maketimer = globals[me.namespace].maketimer; me.maketimer = globals[me.namespace].maketimer;
}, },
_redirect_settimer: func() { _redirect_settimer: func() {
globals[me.namespace].settimer = func() { globals[me.namespace].settimer = func() {
var c = caller(1); var c = caller(1);
logprint(5, sprintf("\n\Unsupported settimer() call from %s:%s. "~ logprint(DEV_ALERT, sprintf("\n\Unsupported settimer() call from %s:%s. "~
"Use maketimer() instead.", "Use maketimer() instead.",
io.basename(c[2]), c[3])); io.basename(c[2]), c[3]));
} }
@ -317,15 +310,17 @@ var load = func(name, ns="") {
if (ns) { m.setNamespace(ns); } if (ns) { m.setNamespace(ns); }
return m.load(); return m.load();
} }
else return 0; else return 0;
} }
# scan MODULES_DIR for subdirectories; it is assumed, that only well-formed # scan MODULES_DIR for subdirectories; it is assumed, that only well-formed
# modules are stored in that directories, so no further checks right here # modules are stored in that directories, so no further checks right here
var _findModules = func() { var _findModules = func() {
var module_dirs = io.subdirectories(MODULES_DIR); var module_dirs = io.subdirectories(MODULES_DIR);
_modules_available = {}; _modules_available = {};
foreach (var name; module_dirs) { foreach (var name; module_dirs) {
if (!io.is_regular_file(MODULES_DIR~"/"~name~"/"~MODULES_DEFAULT_FILENAME))
break;
_modules_available[name] = 1; _modules_available[name] = 1;
MODULES_NODE.getNode(name~"/available",1).setBoolValue(1); MODULES_NODE.getNode(name~"/available",1).setBoolValue(1);
} }
@ -342,7 +337,7 @@ var commandModuleReload = func(node)
var module = node.getChild("module").getValue(); var module = node.getChild("module").getValue();
var m = _getInstance(module); var m = _getInstance(module);
if (m == nil) { if (m == nil) {
logprint(5, "Unknown module to reload: %s", module); logprint(LOG_WARN, "Unknown module to reload: "~module);
return; return;
} }