Phi: nicer scroll animation for METAR widget
[fg:fgdata.git] / Nasal / globals.nas
1 ##
2 # Constants.
3 #
4 var D2R = math.pi / 180;               # degree to radian
5 var R2D = 180 / math.pi;               # radian to degree
6
7 var FT2M = 0.3048;                     # feet to meter
8 var M2FT = 1 / FT2M;
9 var IN2M = FT2M / 12;
10 var M2IN = 1 / IN2M;
11 var NM2M = 1852;                       # nautical miles to meter
12 var M2NM = 1 / NM2M;
13
14 var KT2MPS = 0.5144444444;             # knots to m/s
15 var MPS2KT = 1 / KT2MPS;
16
17 var FPS2KT = 0.5924838012958964;        # fps to knots
18 var KT2FPS = 1 / FPS2KT;
19
20 var LB2KG = 0.45359237;                # pounds to kg
21 var KG2LB = 1 / LB2KG;
22
23 var GAL2L = 3.785411784;               # US gallons to liter
24 var L2GAL = 1 / GAL2L;
25
26
27 # container for local variables, so as not to clutter the global namespace
28 var __ = {};
29
30 ##
31 # Aborts execution if <condition> evaluates to false.
32 # Prints an optional message if present, or just "assertion failed!"
33 #
34 var assert = func (condition, message=nil) {
35         message != nil or (message = "assertion failed!");
36         condition or die(message);
37 }
38
39 ##
40 # Returns true if the first object is an instance of the second
41 # (class) object.  Example: isa(someObject, props.Node)
42 #
43 var isa = func(obj, class) {
44     if(typeof(obj) == "hash" and obj["parents"] != nil)
45         foreach(var c; obj.parents)
46             if(c == class or isa(c, class))
47                 return 1;
48     return 0;
49 }
50
51 ##
52 # Invokes a FlightGear command specified by the first argument.  The
53 # second argument specifies the property tree to be passed to the
54 # command as its argument.  It may be either a props.Node object or a
55 # string, in which case it specifies a path in the global property
56 # tree.
57 #
58 var fgcommand = func(cmd, node=nil) {
59     if(isa(node, props.Node)) node = node._g;
60     elsif(typeof(node) == 'hash')
61         node = props.Node.new(node)._g;
62     _fgcommand(cmd, node);
63 }
64
65 ##
66 # Returns the SGPropertyNode argument to the currently executing
67 # function. Wrapper for the internal _cmdarg function that retrieves
68 # the ghost handle to the argument and wraps it in a
69 # props.Node object.
70 #
71 var cmdarg = func { props.wrapNode(_cmdarg()) }
72
73 ##
74 # Utility.  Does what you think it does.
75 #
76 var abs = func(v) { return v < 0 ? -v : v }
77
78 ##
79 # Convenience wrapper for the _interpolate function.  Takes a
80 # single string or props.Node object in arg[0] indicating a target
81 # property, and a variable-length list of time/value pairs.  Example:
82 #
83 #  interpolate("/animations/radar/angle",
84 #              180, 1, 360, 1, 0, 0,
85 #              180, 1, 360, 1, 0, 0,
86 #              180, 1, 360, 1, 0, 0,
87 #              180, 1, 360, 1, 0, 0,
88 #              180, 1, 360, 1, 0, 0,
89 #              180, 1, 360, 1, 0, 0,
90 #              180, 1, 360, 1, 0, 0,
91 #              180, 1, 360, 1, 0, 0);
92 #
93 # This will swing the "radar dish" smoothly through 8 revolutions over
94 # 16 seconds.  Note the use of zero-time interpolation between 360 and
95 # 0 to wrap the interpolated value properly.
96 #
97 var interpolate = func(node, val...) {
98     if(isa(node, props.Node)) node = node._g;
99     elsif(typeof(node) != "scalar" and typeof(node) != "ghost")
100         die("bad argument to interpolate()");
101     _interpolate(node, val);
102 }
103
104
105 ##
106 # Wrapper for the _setlistener function. Takes a property path string
107 # or props.Node object in arg[0] indicating the listened to property,
108 # a function in arg[1], an optional bool in arg[2], which triggers the
109 # function initially if true, and an optional integer in arg[3], which
110 # sets the listener's runtime behavior to "only trigger on change" (0),
111 # "always trigger on write" (1), and "trigger even when children are
112 # written to" (2).
113 #
114 var setlistener = func(node, fn, init = 0, runtime = 1) {
115     if(isa(node, props.Node)) node = node._g;
116     elsif(typeof(node) != "scalar" and typeof(node) != "ghost")
117         die("bad argument to setlistener()");
118     var id = _setlistener(node, func(chg, lst, mode, is_child) {
119         fn(props.wrapNode(chg), props.wrapNode(lst), mode, is_child);
120     }, init, runtime);
121     if(__.log_level <= 2) {
122         var c = caller(1);
123         printf("setting listener #%d in %s, line %s", id, c[2], c[3]);
124     }
125     return id;
126 }
127
128
129 ##
130 # Returns true if the symbol name is defined in the caller, or the
131 # caller's lexical namespace.  (i.e. defined("varname") tells you if
132 # you can use varname in an expression without a undefined symbol
133 # error.
134 #
135 var defined = func(sym) {
136     if (contains(caller(1)[0], sym)) return 1;
137     var fn = caller(1)[1];
138     for (var l=0; (var frame = closure(fn, l)) != nil; l+=1)
139         if (contains(frame, sym)) return 1;
140     return 0;
141 }
142
143
144 ##
145 # Returns reference to calling function. This allows a function to
146 # reliably call itself from a closure, rather than the global function
147 # with the same name.
148 #
149 var thisfunc = func caller(1)[1];
150
151
152 ##
153 # Just what it says it is.
154 #
155 var printf = func print(call(sprintf, arg));
156
157
158 ##
159 # Returns vector of hash values.
160 #
161 var values = func(hash) {
162     var vec = [];
163     foreach(var key; keys(hash)) append(vec, hash[key]);
164     return vec;
165 }
166
167
168 ##
169 # Print log messages in appropriate --log-level.
170 # Usage: printlog("warn", "...");
171 # The underscore hash prevents helper functions/variables from
172 # needlessly polluting the global namespace.
173 #
174 __.dbg_types = { none:0, bulk:1, debug:2, info:3, warn:4, alert:5 };
175 __.log_level = __.dbg_types[getprop("/sim/logging/priority")];
176 var printlog = func(level) {
177     if(__.dbg_types[level] >= __.log_level) call(print, arg);
178 }
179
180
181 ##
182 # Load and execute ~/.fgfs/Nasal/*.nas files in alphabetic order
183 # after all $FG_ROOT/Nasal/*.nas files were loaded.
184 #
185 settimer(func {
186     var path = getprop("/sim/fg-home") ~ "/Nasal";
187     if((var dir = directory(path)) == nil) return;
188     foreach(var file; sort(dir, cmp))
189         if(size(file) > 4 and substr(file, -4) == ".nas")
190             io.load_nasal(path ~ "/" ~ file, substr(file, 0, size(file) - 4));
191 }, 0);