Random newspaper headline: "FlightGear's Nasal lifts itself up by its bootstraps!" Some instructions and notes:All files need this header at the top of the file:
if (globals["GLOBALS_LOADED"] != -1) return;
Any _setlistener("/sim/signals/nasal-dir-initialized", func{}) replaced with the contents of the func{}.
There are a few "forward-declarations" of items (mostly props.Node objects) that are assignments to nil that can be removed, since the listener is now immediate.
I did not need to modify any sub-modules any....
gui.nas and string.nas need to keep their listeners (the former may be fixable by moving it around, though).
Any error in globals.nas will be potentially deadly -- as in modules will re-load when the C++ code calls it (again). Two things:
a) Do *not* load modules from C++ anymore (^.^), except globals of course.
b) Make sure that globals returns without an error. Display a message if it did -- this could be "detrimental" to the user experience, as I've seen
.
Items that I've learned the hard way (I've probably missed some, but most modules just use the listener for the props.Node objects and related helpers):
• view.nas: require("geo")
• dynamic_vew.nas: require("view.nas")
• geo.nas: require("math")
Installation:Add this to the top of globals.nas:
- Code: Select all
##
# Module loading
#
var GLOBALS_LOADED = 0; #0: in progress, -1: loading modules, -2: loading home modules, 1: done, nil: not yet started, other: failed
# container for local variables, so as not to clutter the global namespace
var __ = {};
__.loaded_modules = {};
##
# Require directive, somewhat duplicated from io.load_nasal().
# Essentially asks for a module to be loaded if it is not already.
# The force_load argument has four values:
# * 1: re-load the module even if it has already been loaded
# * 2: load the (sub-)module even if it is not enabled (this
# is for directories, aka sub-modules, where the property
# /sim/module_name/enabled controls loading of it).
# * 3: combination of the above (aka force everything)
# * 0: none of the above
# This function will return nil if the module is not loaded.
#
# Module are defined as:
# * All files in a directory in $FG_ROOT/Nasal/
# * A file in $FG_ROOT/Nasal/
# If both are found then both are loaded.
#
var require = func(name, force_load=0) {
if (find("/", name) != -1) die("bad module name"); #this is unsafe
if (name == "globals") return nil; #no way are we loading ourself!
elsif (name == "" or name == "." or name == "..") return nil;
if ((force_load == 0 or force_load == 2) and contains(__.loaded_modules, name))
return nil; #we already loaded it...
if (!contains(globals, name))
globals[name] = {};
var err = [];
var have_directory = 1;
var base_dir = FG_ROOT~"/Nasal/";
var files = directory(base_dir~name);
if (files == nil) {
var have_directory = 0;
var files = [base_dir~name~".nas"];
if (io.stat(files[0]) == nil) die("cannot find module "~name);
} else {
if (!getprop("/sim/"~name~"/enabled")) {
if (force_load == 2 or force_load == 3)
printlog("warn", "warning: loading a diabled module using require()");
else return nil;
}
forindex (var i; files) {
files[i] = name~"/"~files[i]; #correct their path from base_dir
}
if (io.stat(base_dir~name~".nas"))
append(files, name~".nas");
}
__.loaded_modules[name] = 0; #partial load
foreach (var file; files) {
var code = call(func {
if ((var st = io.stat(file)) == nil) #should not happen
print("Nasal runtime error: Cannot stat file: "~base_dir~file~", this is a bug!");
var sz = st[7];
var buf = bits.buf(sz);
io.read(io.open(file), buf, sz);
return buf;
}, nil, err);
if (!size(err))
var code = call(func compile(code, file), nil, err);
if (!size(err) and typeof(code) == 'func')
call(bind(code, globals), nil, nil, globals[name], err);
if (size(err)) {
if (err != 1 and err != 0)
__.loaded_modules[name] = err; #unsuccesful load
print("Error on file "~file);
debug.printerror(err);
}
}
__.loaded_modules[name] = 1; #sucessful load
}
Add this below the constant definitions, or replace all my uses of them with getprops (the former is muchly preferred
):
- Code: Select all
# IMO some useful global variables
var FG_ROOT = getprop("/sim/fg-root");
var FG_HOME = getprop("/sim/fg-home");
var MODEL_PATH = getprop("/sim/model/path");
And then add this below the printlog definition, replacing the settimer:
- Code: Select all
var GLOBALS_LOADED = -1;
##
# Put modules here that require special attention; ones
# that are used often enough not to warrant a require()
# at the top of every file or contain security code
# (like io.nas). Other modules that are needed by a file
# should be require'd at the top of that file.
#
require("string");
require("debug");
require("props");
require("io"); #uses all 3 of the above modules
##
# Then we require() others in as necessary.
#
__.dir = directory(FG_ROOT~"/Nasal/");
if (__.dir == nil) print("globals.nas: No Nasal directory!");
else {
foreach (__.file; sort(__.dir, cmp)) {
if (substr(__.file, -4) == ".nas") {
#print("loading module "~substr(__.file, 0, size(__.file)-4));
require(substr(__.file, 0, size(__.file)-4));
} elsif (directory(FG_ROOT~"/Nasal/"~__.file) != nil and __.file != "globals" and __.file != "io"
and __.file != "props" and __.file != "." and __.file != "..") { #very important that we skip . and ..!
#print("loading sub-module "~__.file);
require(__.file);
}
}
}
var GLOBALS_LOADED = -2;
##
# Load and execute ~/.fgfs/Nasal/*.nas files in alphabetic order
# after all $FG_ROOT/Nasal/*.nas files were loaded. (We set
# GLOBALS_LOADED before this since it essentially is loaded
# and its loading status does not depend upon these modules).
#
__.path = FG_HOME ~ "/Nasal";
if((__.dir = directory(__.path)) == nil) return;
foreach(__.file; sort(__.dir, cmp))
if(size(__.file) > 4 and substr(__.file, -4) == ".nas")
io.load_nasal(__.path ~ "/" ~ __.file, substr(__.file, 0, size(__.file) - 4));
# Keep this at the very end of the file!
var GLOBALS_LOADED = 1;
I personally haven't seen any regressions, but I only use a very small feature set of FG so I might have introduced some, but on the whole I believe it to be cleaner and easier, not to mention much less hack-ish (well, besides the fact that I am working around C++ code).