## # Returns true if the first object is an instance of the second # (class) object. Example: isa(someObject, props.Node) # isa = func { obj = arg[0]; class = arg[1]; if(obj == nil or !contains(obj, "parents")) { return 0; } foreach(c; obj.parents) { if(c == class) { return 1; } elsif(isa(obj, c)) { return 1; } } return 0; } ## # Invokes a FlightGear command specified by the first argument. The # second argument specifies the property tree to be passed to the # command as its argument. It may be either a props.Node object or a # string, in which case it specifies a path in the global property # tree. # fgcommand = func(cmd, node=nil) { if(isa(node, props.Node)) node = node._g; _fgcommand(cmd, node); } ## # Returns the SGPropertyNode argument to the currently executing # function. Wrapper for the internal _cmdarg function that retrieves # the ghost handle to the argument and wraps it in a # props.Node object. # cmdarg = func { props.wrapNode(_cmdarg()) } ## # Utility. Does what it you think it does. # abs = func { if(arg[0] < 0) { -arg[0] } else { arg[0] } } ## # Convenience wrapper for the _interpolate function. Takes a # single string or props.Node object in arg[0] indicating a target # property, and a variable-length list of time/value pairs. Example: # # interpolate("/animations/radar/angle", # 180, 1, 360, 1, 0, 0, # 180, 1, 360, 1, 0, 0, # 180, 1, 360, 1, 0, 0, # 180, 1, 360, 1, 0, 0, # 180, 1, 360, 1, 0, 0, # 180, 1, 360, 1, 0, 0, # 180, 1, 360, 1, 0, 0, # 180, 1, 360, 1, 0, 0); # # This will swing the "radar dish" smoothly through 8 revolutions over # 16 seconds. Note the use of zero-time interpolation between 360 and # 0 to wrap the interpolated value properly. # interpolate = func { if(isa(arg[0], props.Node)) { arg[0] = arg[0]._g; } elsif(typeof(arg[0]) != "scalar") { return; } _interpolate(arg[0], size(arg) == 1 ? [] : subvec(arg, 1)); } ## # Convenience wrapper for the _setlistener function. Takes a # single string or props.Node object in arg[0] indicating the # listened to property, a function in arg[1], and an optional # bool in arg[2], which triggers the function initially if true. # setlistener = func { if(isa(arg[0], props.Node)) { arg[0] = arg[0]._g; } var id = _setlistener(arg[0], arg[1], size(arg) > 2 ? arg[2] : 0); if(__.log_level <= 2) { var c = caller(1); print(sprintf("setting listener #%d in %s, line %s", id, c[2], c[3])) } return id; } ## # Returns true if the symbol name is defined in the caller, or the # caller's lexical namespace. (i.e. defined("varname") tells you if # you can use varname in an expression without a undefined symbol # error. # defined = func(sym) { var fn = 1; while((var frame = caller(fn)) != nil) { if(contains(frame[0], sym)) { return 1; } fn += 1; } return 0; } ## # Print log messages in appropriate --log-level. # Usage: printlog("warn", "..."); # The underscore hash prevents helper functions/variables from # needlessly polluting the global namespace. # __ = {}; __.dbg_types = { none:0, bulk:1, debug:2, info:3, warn:4, alert:5 }; __.log_level = __.dbg_types[getprop("/sim/logging/priority")]; printlog = func(level, args...) { if(__.dbg_types[level] >= __.log_level) { call(print, args); } } ## # Load and execute ~/.fgfs/Nasal/*.nas files in alphabetic order # after all $FG_ROOT/Nasal/*.nas files were loaded. # _setlistener("/sim/signals/nasal-dir-initialized", func { var path = getprop("/sim/fg-home") ~ "/Nasal"; if ((var dir = directory(path)) == nil) return; foreach (var file; sort(dir, cmp)) { if (substr(file, -4) != ".nas") continue; var module = substr(file, 0, size(file) - 4); file = path ~ "/" ~ file; printlog("info", ">>> executing local Nasal file ", file); if (!contains(globals, module)) var locals = globals[module] = {}; elsif (typeof(globals[module]) == "hash") var locals = globals[module]; else var locals = {}; var err = []; var code = call(func { compile(io.readfile(file), file) }, nil, err); if (size(err)) { print(file ~ ": " ~ err[0]); continue; } call(bind(code, globals), nil, nil, locals, err); debug.printerror(err); } });