02b01af19e
+ After discussion with Henning: - orig_setlistener() and orig_maketimer() were not intended to be public -> prefix the function names with an underscore; - clear the add-on's lists of tracked listeners and timers in remove() (load() does that too, but it's a bit late). + Little rewording in Docs/README.add-ons.
162 lines
5 KiB
Text
162 lines
5 KiB
Text
##
|
|
# Initialize addons configured with --addon=foobar command line switch:
|
|
# - get the list of registered add-ons
|
|
# - load the addon-main.nas file of each add-on into namespace
|
|
# __addon[ADDON_ID]__
|
|
# - call function main() from every such addon-main.nas with the add-on ghost
|
|
# as argument (an addons.Addon instance).
|
|
|
|
# Example:
|
|
#
|
|
# fgfs --addon=/foo/bar/baz
|
|
#
|
|
# - AddonManager.cxx parses /foo/bar/baz/addon-metadata.xml
|
|
# - AddonManager.cxx creates prop nodes under /addons containing add-on metadata
|
|
# - AddonManager.cxx loads /foo/bar/baz/addon-config.xml into the Property Tree
|
|
# - AddonManager.cxx adds /foo/bar/baz to the list of aircraft paths (to get
|
|
# permissions to read files from there)
|
|
# - this script loads /foo/bar/baz/addon-main.nas into namespace
|
|
# __addon[ADDON_ID]__
|
|
# - this script calls main(addonGhost) from /foo/bar/baz/addon-main.nas.
|
|
# - the add-on ghost can be used to retrieve most of the add-on metadata, for
|
|
# instance:
|
|
# addonGhost.id the add-on identifier
|
|
# addonGhost.name the add-on name
|
|
# addonGhost.version.str() the add-on version as a string
|
|
# addonGhost.basePath the add-on base path (realpath() of
|
|
# "/foo/bar/baz" here)
|
|
# etc.
|
|
#
|
|
# For more details, see $FG_ROOT/Docs/README.add-ons.
|
|
|
|
# hashes to store listeners and timers per addon ID
|
|
var _listeners = {};
|
|
var _timers = {};
|
|
var _orig_setlistener = nil;
|
|
var _orig_maketimer = nil;
|
|
|
|
var getNamespaceName = func(a) {
|
|
return "__addon[" ~ a.id ~ "]__";
|
|
}
|
|
|
|
var load = func(a) {
|
|
var main_nas = a.basePath ~ "/addon-main.nas";
|
|
var namespace = getNamespaceName(a);
|
|
var loaded = a.node.getNode("loaded", 1);
|
|
loaded.setBoolValue(0);
|
|
|
|
if (globals[namespace] == nil) {
|
|
globals[namespace] = {};
|
|
}
|
|
|
|
_listeners[a.id] = [];
|
|
_timers[a.id] = [];
|
|
|
|
# redirect setlistener() for addon
|
|
globals[namespace].setlistener = func(p, f, start=0, runtime=1) {
|
|
# listeners won't work on aliases so find real node
|
|
if (typeof(p) == "scalar") {
|
|
p = props.getNode(p, 1);
|
|
while (p.getAttribute("alias")) {
|
|
p = p.getAliasTarget();
|
|
}
|
|
}
|
|
append(_listeners[a.id], _orig_setlistener(p, f, start, runtime));
|
|
print("#listeners for " ~ a.id ~ " " ~ size(_listeners[a.id]));
|
|
}
|
|
|
|
# redirect maketimer for addon
|
|
globals[namespace].maketimer = func() {
|
|
if (size(arg) == 2) {
|
|
append(_timers[a.id], _orig_maketimer(arg[0], arg[1]));
|
|
} elsif (size(arg) == 3) {
|
|
append(_timers[a.id], _orig_maketimer(arg[0], arg[1], arg[2]));
|
|
} else {
|
|
print("Invalid number of arguments to maketimer()");
|
|
return;
|
|
}
|
|
print("#timers for " ~ a.id ~ " " ~ size(_timers[a.id]));
|
|
return _timers[a.id][-1];
|
|
}
|
|
|
|
logprint(5, "+ Loading " ~ main_nas ~ " into " ~ namespace);
|
|
|
|
if (io.load_nasal(main_nas, namespace)) {
|
|
logprint(5, " [OK] '" ~ a.name ~ "' (V. " ~ a.version.str() ~
|
|
") loaded.");
|
|
var addon_main = globals[namespace]["main"];
|
|
var addon_main_args = [a];
|
|
var errors = [];
|
|
call(addon_main, addon_main_args, errors);
|
|
if (size(errors)) {
|
|
debug.printerror(errors);
|
|
} else {
|
|
loaded.setBoolValue(1);
|
|
}
|
|
} else {
|
|
logprint(5, " [Failed] '" ~ a.name ~ "'");
|
|
}
|
|
}
|
|
|
|
|
|
var remove = func(a) {
|
|
if (!a.node.getChild("loaded").getValue()) {
|
|
print("! ", a.id, " was not fully loaded.");
|
|
}
|
|
|
|
print("- Removing ", a.id);
|
|
var namespace = getNamespaceName(a);
|
|
foreach (var id; _listeners[a.id]) {
|
|
print(" Removing listener " ~ id);
|
|
removelistener(id);
|
|
}
|
|
_listeners[a.id] = [];
|
|
|
|
print(" Stopping timers ");
|
|
foreach (var t; _timers[a.id]) {
|
|
if (typeof(t.stop) == "func") {
|
|
t.stop();
|
|
print(" .");
|
|
}
|
|
}
|
|
_timers[a.id] = [];
|
|
|
|
# call clean up method if available
|
|
# addon shall release resources not handled by addon framework
|
|
if (globals[namespace]["unload"] != nil
|
|
and typeof(globals[namespace]["unload"]) == "func") {
|
|
globals[namespace].unload(a);
|
|
}
|
|
globals[namespace] = {};
|
|
}
|
|
|
|
var _reloadFlags = {};
|
|
|
|
var reload = func(a) {
|
|
addons.remove(a);
|
|
addons.load(a);
|
|
}
|
|
|
|
var init = func {
|
|
foreach (var addon; addons.registeredAddons()) {
|
|
addons._reloadFlags[addon.id] = addon.node.getNode("reload", 1);
|
|
addons._reloadFlags[addon.id].setBoolValue(0);
|
|
var makeListener = func(a) {
|
|
return func(n) {
|
|
if (n.getValue()) {
|
|
n.setValue(0);
|
|
addons.reload(a);
|
|
}
|
|
};
|
|
}
|
|
setlistener(addons._reloadFlags[addon.id], makeListener(addon));
|
|
addons.load(addon);
|
|
}
|
|
}
|
|
|
|
var id = _setlistener("/sim/signals/fdm-initialized", func {
|
|
removelistener(id);
|
|
_orig_setlistener = setlistener;
|
|
_orig_maketimer = maketimer;
|
|
addons.init();
|
|
})
|