2003-12-01 14:36:22 +00:00
|
|
|
##
|
|
|
|
# Node class definition. The class methods simply wrap the
|
2007-05-14 15:15:00 +00:00
|
|
|
# low level extension functions which work on a "ghost" handle to a
|
2003-12-01 14:36:22 +00:00
|
|
|
# SGPropertyNode object stored in the _g field.
|
|
|
|
#
|
|
|
|
# Not all of the features of SGPropertyNode are supported. There is
|
|
|
|
# no support for ties, obviously, as that wouldn't make much sense
|
|
|
|
# from a Nasal context. The various get/set methods work only on the
|
|
|
|
# local node, there is no equivalent of the "relative path" variants
|
|
|
|
# available in C++; just use node.getNode(path).whatever() instead.
|
2008-05-15 15:17:56 +00:00
|
|
|
# The aliasing feature isn't exposed, except that you can get an
|
|
|
|
# "ALIAS" return from getType to detect them (to avoid cycles while
|
|
|
|
# walking the tree).
|
2003-12-01 14:36:22 +00:00
|
|
|
#
|
|
|
|
Node = {
|
|
|
|
getType : func { wrap(_getType(me._g, arg)) },
|
2007-01-12 18:06:50 +00:00
|
|
|
getAttribute : func { wrap(_getAttribute(me._g, arg)) },
|
|
|
|
setAttribute : func { wrap(_setAttribute(me._g, arg)) },
|
2003-12-01 14:36:22 +00:00
|
|
|
getName : func { wrap(_getName(me._g, arg)) },
|
|
|
|
getIndex : func { wrap(_getIndex(me._g, arg)) },
|
|
|
|
getValue : func { wrap(_getValue(me._g, arg)) },
|
|
|
|
setValue : func { wrap(_setValue(me._g, arg)) },
|
|
|
|
setIntValue : func { wrap(_setIntValue(me._g, arg)) },
|
|
|
|
setBoolValue : func { wrap(_setBoolValue(me._g, arg)) },
|
|
|
|
setDoubleValue : func { wrap(_setDoubleValue(me._g, arg)) },
|
|
|
|
getParent : func { wrap(_getParent(me._g, arg)) },
|
|
|
|
getChild : func { wrap(_getChild(me._g, arg)) },
|
|
|
|
getChildren : func { wrap(_getChildren(me._g, arg)) },
|
|
|
|
removeChild : func { wrap(_removeChild(me._g, arg)) },
|
2005-10-23 16:09:51 +00:00
|
|
|
removeChildren : func { wrap(_removeChildren(me._g, arg)) },
|
2003-12-01 14:36:22 +00:00
|
|
|
getNode : func { wrap(_getNode(me._g, arg)) },
|
|
|
|
|
2005-03-26 22:15:26 +00:00
|
|
|
getPath : func {
|
2007-08-07 11:30:30 +00:00
|
|
|
var name = me.getName();
|
2005-03-27 17:30:51 +00:00
|
|
|
if(me.getIndex() != 0) { name = name ~ "[" ~ me.getIndex() ~ "]"; }
|
|
|
|
if(me.getParent() != nil) { name = me.getParent().getPath() ~ "/" ~ name; }
|
2005-03-26 22:15:26 +00:00
|
|
|
return name;
|
|
|
|
},
|
|
|
|
|
2004-01-28 03:49:59 +00:00
|
|
|
getBoolValue : func {
|
2007-08-07 11:30:30 +00:00
|
|
|
var val = me.getValue();
|
2004-03-27 04:07:56 +00:00
|
|
|
if(me.getType() == "STRING" and val == "false") { 0 }
|
2007-02-07 17:31:41 +00:00
|
|
|
elsif (val == nil) { 0 }
|
|
|
|
else { val != 0 }
|
|
|
|
},
|
2007-08-07 11:30:30 +00:00
|
|
|
|
|
|
|
remove : func {
|
|
|
|
if((var p = me.getParent()) == nil) return nil;
|
|
|
|
p.removeChild(me.getName(), me.getIndex());
|
|
|
|
},
|
2003-12-01 14:36:22 +00:00
|
|
|
};
|
|
|
|
|
2003-12-08 02:09:19 +00:00
|
|
|
##
|
|
|
|
# Static constructor for a Node object. Accepts a Nasal hash
|
|
|
|
# expression to initialize the object a-la setValues().
|
|
|
|
#
|
2003-12-02 17:54:15 +00:00
|
|
|
Node.new = func {
|
2003-12-01 14:36:22 +00:00
|
|
|
result = wrapNode(_new());
|
2004-05-12 15:37:17 +00:00
|
|
|
if(size(arg) > 0 and typeof(arg[0]) == "hash") {
|
2003-12-08 02:09:19 +00:00
|
|
|
result.setValues(arg[0]);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
##
|
|
|
|
# Useful utility. Sets a whole property tree from a Nasal hash
|
|
|
|
# object, such that scalars become leafs in the property tree, hashes
|
|
|
|
# become named subnodes, and vectors become indexed subnodes. This
|
|
|
|
# works recursively, so you can define whole property trees with
|
|
|
|
# syntax like:
|
|
|
|
#
|
|
|
|
# dialog = {
|
|
|
|
# name : "exit", width : 180, height : 100, modal : 0,
|
|
|
|
# text : { x : 10, y : 70, label : "Hello World!" } };
|
|
|
|
#
|
2007-05-14 15:15:00 +00:00
|
|
|
Node.setValues = func(val) {
|
|
|
|
foreach(var k; keys(val)) { me._setChildren(k, val[k]); }
|
2003-12-08 02:09:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
##
|
|
|
|
# Private function to do the work of setValues().
|
|
|
|
# The first argument is a child name, the second a nasal scalar,
|
|
|
|
# vector, or hash.
|
|
|
|
#
|
2007-05-14 15:15:00 +00:00
|
|
|
Node._setChildren = func(name, val) {
|
|
|
|
var subnode = me.getNode(name, 1);
|
2003-12-08 02:09:19 +00:00
|
|
|
if(typeof(val) == "scalar") { subnode.setValue(val); }
|
|
|
|
elsif(typeof(val) == "hash") { subnode.setValues(val); }
|
|
|
|
elsif(typeof(val) == "vector") {
|
2007-05-14 15:15:00 +00:00
|
|
|
for(var i=0; i<size(val); i+=1) {
|
|
|
|
var iname = name ~ "[" ~ i ~ "]";
|
2003-12-08 02:09:19 +00:00
|
|
|
me._setChildren(iname, val[i]);
|
2003-12-01 14:36:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-05-14 15:15:00 +00:00
|
|
|
##
|
|
|
|
# Counter piece of setValues(). Returns a hash with all values
|
|
|
|
# in the subtree. Nodes with same name are returned as vector,
|
|
|
|
# where the original node indices are lost. The function should
|
|
|
|
# only be used if all or almost all values are needed, and never
|
|
|
|
# in performance-critical code paths. If it's called on a node
|
|
|
|
# without children, then the result is equivalent to getValue().
|
|
|
|
#
|
|
|
|
Node.getValues = func {
|
|
|
|
var children = me.getChildren();
|
|
|
|
if(!size(children)) return me.getValue();
|
|
|
|
var val = {};
|
|
|
|
var numchld = {};
|
|
|
|
foreach(var c; children) {
|
|
|
|
var name = c.getName();
|
|
|
|
if(contains(numchld, name)) { var nc = numchld[name]; }
|
|
|
|
else {
|
|
|
|
var nc = size(me.getChildren(name));
|
|
|
|
numchld[name] = nc;
|
|
|
|
if (nc > 1 and !contains(val, name)) val[name] = [];
|
|
|
|
}
|
|
|
|
if(nc > 1) append(val[name], c.getValues());
|
|
|
|
else val[name] = c.getValues();
|
|
|
|
}
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
2003-12-01 14:36:22 +00:00
|
|
|
##
|
|
|
|
# Useful debugging utility. Recursively dumps the full state of a
|
|
|
|
# Node object to the console. Try binding "props.dump(props.globals)"
|
|
|
|
# to a key for a fun hack.
|
|
|
|
#
|
2007-03-18 13:57:32 +00:00
|
|
|
var dump = func {
|
2003-12-01 14:36:22 +00:00
|
|
|
if(size(arg) == 1) { prefix = ""; node = arg[0]; }
|
|
|
|
else { prefix = arg[0]; node = arg[1]; }
|
|
|
|
|
|
|
|
index = node.getIndex();
|
|
|
|
type = node.getType();
|
|
|
|
name = node.getName();
|
|
|
|
val = node.getValue();
|
|
|
|
|
|
|
|
if(val == nil) { val = "nil"; }
|
|
|
|
name = prefix ~ name;
|
|
|
|
if(index > 0) { name = name ~ "[" ~ index ~ "]"; }
|
|
|
|
print(name, " {", type, "} = ", val);
|
|
|
|
|
|
|
|
# Don't recurse into aliases, lest we get stuck in a loop
|
|
|
|
if(type != "ALIAS") {
|
|
|
|
children = node.getChildren();
|
|
|
|
foreach(c; children) { dump(name ~ "/", c); }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-12-16 20:25:52 +00:00
|
|
|
##
|
|
|
|
# Recursively copy property branch from source Node to
|
2007-04-04 14:44:13 +00:00
|
|
|
# destination Node. Doesn't copy aliases. Copies attributes
|
|
|
|
# if optional third argument is set and non-zero.
|
2005-12-16 20:25:52 +00:00
|
|
|
#
|
2007-04-04 14:44:13 +00:00
|
|
|
var copy = func(src, dest, attr = 0) {
|
2007-05-14 15:15:00 +00:00
|
|
|
foreach(var c; src.getChildren()) {
|
|
|
|
var name = c.getName() ~ "[" ~ c.getIndex() ~ "]";
|
2007-04-04 14:44:13 +00:00
|
|
|
copy(src.getNode(name), dest.getNode(name, 1), attr);
|
2005-12-16 20:25:52 +00:00
|
|
|
}
|
|
|
|
var type = src.getType();
|
|
|
|
var val = src.getValue();
|
2008-05-15 15:17:56 +00:00
|
|
|
if(type == "ALIAS" or type == "NONE") return;
|
|
|
|
elsif(type == "BOOL") dest.setBoolValue(val);
|
|
|
|
elsif(type == "INT" or type == "LONG") dest.setIntValue(val);
|
|
|
|
elsif(type == "FLOAT" or type == "DOUBLE") dest.setDoubleValue(val);
|
|
|
|
else dest.setValue(val);
|
2007-04-04 14:44:13 +00:00
|
|
|
if(attr) dest.setAttribute(src.getAttribute());
|
2005-12-16 20:25:52 +00:00
|
|
|
}
|
|
|
|
|
2003-12-01 14:36:22 +00:00
|
|
|
##
|
|
|
|
# Utility. Turns any ghosts it finds (either solo, or in an
|
|
|
|
# array) into Node objects.
|
|
|
|
#
|
2007-03-18 13:57:32 +00:00
|
|
|
var wrap = func {
|
2003-12-01 14:36:22 +00:00
|
|
|
argtype = typeof(arg[0]);
|
|
|
|
if(argtype == "ghost") {
|
|
|
|
return wrapNode(arg[0]);
|
|
|
|
} elsif(argtype == "vector") {
|
|
|
|
v = arg[0];
|
|
|
|
n = size(v);
|
2007-03-18 13:57:32 +00:00
|
|
|
for(i=0; i<n; i+=1) { v[i] = wrapNode(v[i]); }
|
2003-12-01 14:36:22 +00:00
|
|
|
return v;
|
|
|
|
}
|
|
|
|
return arg[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
##
|
|
|
|
# Utility. Returns a new object with its superclass/parent set to the
|
|
|
|
# Node object and its _g (ghost) field set to the specified object.
|
|
|
|
# Nasal's literal syntax can be pleasingly terse. I like that. :)
|
|
|
|
#
|
2007-03-18 13:57:32 +00:00
|
|
|
var wrapNode = func { { parents : [Node], _g : arg[0] } }
|
2003-12-01 14:36:22 +00:00
|
|
|
|
|
|
|
##
|
|
|
|
# Global property tree. Set once at initialization. Is that OK?
|
|
|
|
# Does anything ever call globals.set_props() from C++? May need to
|
|
|
|
# turn this into a function if so.
|
|
|
|
#
|
2007-03-29 22:09:25 +00:00
|
|
|
var globals = wrapNode(_globals());
|
2003-12-01 14:36:22 +00:00
|
|
|
|
2003-12-22 20:05:18 +00:00
|
|
|
##
|
|
|
|
# Sets all indexed property children to a single value. arg[0]
|
|
|
|
# specifies a property name (e.g. /controls/engines/engine), arg[1] a
|
|
|
|
# path under each node of that name to set (e.g. "throttle"), arg[2]
|
|
|
|
# is the value.
|
|
|
|
#
|
2007-03-18 13:57:32 +00:00
|
|
|
var setAll = func {
|
2003-12-22 20:05:18 +00:00
|
|
|
node = props.globals.getNode(arg[0]);
|
|
|
|
if(node == nil) { return; }
|
|
|
|
name = node.getName();
|
|
|
|
node = node.getParent();
|
|
|
|
if(node == nil) { return; }
|
|
|
|
children = node.getChildren();
|
|
|
|
foreach(c; children) {
|
2003-12-23 17:31:08 +00:00
|
|
|
if(c.getName() == name) {
|
|
|
|
c.getNode(arg[1], 1).setValue(arg[2]); }}
|
2003-12-22 20:05:18 +00:00
|
|
|
}
|
2007-03-18 13:57:32 +00:00
|
|
|
|
2008-05-15 15:17:56 +00:00
|
|
|
##
|
|
|
|
# Turns about anything into a list of props.Nodes, including ghosts,
|
|
|
|
# path strings, vectors or hashes containing, as well as functions
|
|
|
|
# returning any of the former and in arbitrary nesting. This is meant
|
|
|
|
# to be used in functions whose main purpose is to handle collections
|
|
|
|
# of properties.
|
|
|
|
#
|
|
|
|
var nodeList = func {
|
|
|
|
var list = [];
|
|
|
|
foreach(var a; arg) {
|
|
|
|
var t = typeof(a);
|
|
|
|
if(isa(a, props.Node))
|
|
|
|
append(list, a);
|
|
|
|
elsif(t == "scalar")
|
|
|
|
append(list, props.globals.getNode(a, 1));
|
|
|
|
elsif(t == "vector")
|
|
|
|
foreach(var i; a)
|
|
|
|
list ~= nodeList(i);
|
|
|
|
elsif(t == "hash")
|
|
|
|
foreach(var i; keys(a))
|
|
|
|
list ~= nodeList(a[i]);
|
|
|
|
elsif(t == "func")
|
|
|
|
list ~= nodeList(a());
|
2008-05-19 15:44:23 +00:00
|
|
|
elsif(t == "ghost" and ghosttype(a) == ghosttype(_globals()))
|
|
|
|
append(list, wrapNode(a));
|
2008-05-15 15:17:56 +00:00
|
|
|
else
|
|
|
|
die("nodeList: invalid nil property");
|
|
|
|
}
|
|
|
|
return list;
|
|
|
|
}
|
|
|
|
|
2008-05-19 15:44:23 +00:00
|
|
|
##
|
|
|
|
# Initializes property if it's still undefined. First argument is a property
|
|
|
|
# path or a props.Node. Second argument is the default value. The third,
|
|
|
|
# optional argument is a property type (one of "STRING", "DOUBLE", "INT", or
|
|
|
|
# "BOOL"). If it is omitted, then "DOUBLE" is used for numbers, and STRING
|
|
|
|
# for everything else. Returns the property as props.Node.
|
|
|
|
#
|
|
|
|
var initNode = func(prop, value, type = nil) {
|
|
|
|
if(!isa(prop, props.Node)) prop = props.globals.getNode(prop, 1);
|
|
|
|
if(prop.getType() != "NONE") value = prop.getValue();
|
|
|
|
if(type == nil) prop.setValue(value);
|
|
|
|
elsif(type == "DOUBLE") prop.setDoubleValue(value);
|
|
|
|
elsif(type == "INT") prop.setIntValue(value);
|
|
|
|
elsif(type == "BOOL") prop.setBoolValue(value);
|
|
|
|
elsif(type == "STRING") prop.setValue("" ~ value);
|
|
|
|
else die("initNode(): unsupported type '" ~ type ~ "'");
|
|
|
|
return prop;
|
|
|
|
}
|
|
|
|
|
2007-03-18 13:57:32 +00:00
|
|
|
##
|
|
|
|
# Evaluates a <condition> property branch according to the rules
|
2007-03-20 16:23:23 +00:00
|
|
|
# set out in $FG_ROOT/Docs/README.conditions. Undefined conditions
|
|
|
|
# and a nil argument are "true". The function dumps the condition
|
|
|
|
# branch and returns nil on error.
|
2007-03-18 13:57:32 +00:00
|
|
|
#
|
|
|
|
var condition = func(p) {
|
2008-05-15 15:17:56 +00:00
|
|
|
if(p == nil) return 1;
|
|
|
|
if(!isa(p, props.Node)) p = props.globals.getNode(p);
|
2007-03-18 13:57:32 +00:00
|
|
|
return _cond_and(p)
|
|
|
|
}
|
|
|
|
|
|
|
|
var _cond_and = func(p) {
|
2008-05-15 15:17:56 +00:00
|
|
|
foreach(var c; p.getChildren())
|
|
|
|
if(!_cond(c)) return 0;
|
2007-03-18 13:57:32 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
var _cond_or = func(p) {
|
2008-05-15 15:17:56 +00:00
|
|
|
foreach(var c; p.getChildren())
|
|
|
|
if(_cond(c)) return 1;
|
2007-03-18 13:57:32 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
var _cond = func(p) {
|
|
|
|
var n = p.getName();
|
2008-05-15 15:17:56 +00:00
|
|
|
if(n == "or") return _cond_or(p);
|
|
|
|
if(n == "and") return _cond_and(p);
|
|
|
|
if(n == "not") return !_cond_and(p);
|
|
|
|
if(n == "equals") return _cond_cmp(p, 0);
|
|
|
|
if(n == "not-equals") return !_cond_cmp(p, 0);
|
|
|
|
if(n == "less-than") return _cond_cmp(p, -1);
|
|
|
|
if(n == "greater-than") return _cond_cmp(p, 1);
|
|
|
|
if(n == "less-than-equals") return !_cond_cmp(p, 1);
|
|
|
|
if(n == "greater-than-equals") return !_cond_cmp(p, -1);
|
|
|
|
if(n == "property") return !!getprop(p.getValue());
|
2007-03-18 13:57:32 +00:00
|
|
|
printlog("alert", "condition: invalid operator ", n);
|
|
|
|
dump(p);
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
var _cond_cmp = func(p, op) {
|
|
|
|
var left = p.getChild("property", 0, 0);
|
|
|
|
if(left != nil) { left = getprop(left.getValue()); }
|
|
|
|
else {
|
|
|
|
printlog("alert", "condition: no left value");
|
|
|
|
dump(p);
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
var right = p.getChild("property", 1, 0);
|
|
|
|
if(right != nil) { right = getprop(right.getValue()); }
|
|
|
|
else {
|
|
|
|
right = p.getChild("value", 0, 0);
|
|
|
|
if(right != nil) { right = right.getValue(); }
|
|
|
|
else {
|
|
|
|
printlog("alert", "condition: no right value");
|
|
|
|
dump(p);
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(left == nil or right == nil) {
|
|
|
|
printlog("alert", "condition: comparing with nil");
|
|
|
|
dump(p);
|
|
|
|
return nil;
|
|
|
|
}
|
2008-05-15 15:17:56 +00:00
|
|
|
if(op < 0) return left < right;
|
|
|
|
if(op > 0) return left > right;
|
2007-03-21 18:37:06 +00:00
|
|
|
return left == right;
|
2007-03-18 13:57:32 +00:00
|
|
|
}
|
|
|
|
|
2007-03-19 18:17:05 +00:00
|
|
|
|