Board index FlightGear Development

Relative paths for Nasal props.Node methods

FlightGear is opensource, so you can be the developer. In the need for help on anything? We are here to help you.
Forum rules
Core development is discussed on the official FlightGear-Devel development mailing list.

Bugs can be reported in the bug tracker.

Relative paths for Nasal props.Node methods

Postby Philosopher » Tue Apr 09, 2013 12:19 am

Could some benevolent soul test this file for me? And maybe commit it :)? It's $FG_SRC/Scripting/nasal-props.cxx and it provides relative path versions for node methods and documents things, but does not change any functionality other than that.

Yeah, I'm not subscribed to the mailing list...

Code: Select all

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include <cstring>

#include <simgear/nasal/nasal.h>
#include <simgear/props/props.hxx>
#include <simgear/props/vectorPropTemplates.hxx>

#include <Main/globals.hxx>

#include "NasalSys.hxx"

using namespace std;

// Implementation of a Nasal wrapper for the SGPropertyNode class,
// using the Nasal "ghost" (er... Garbage collection Handle for
// OutSide Thingy) facility.
//
// Note that these functions appear in Nasal with prepended
// underscores.  They work on the low-level "ghost" objects and aren't
// intended for use from user code, but from Nasal code you will find
// in props.nas.  That is where the Nasal props.Node class is defined,
// which provides a saner interface along the lines of SGPropertyNode.

static void propNodeGhostDestroy(void* ghost)
{
    SGPropertyNode_ptr* prop = (SGPropertyNode_ptr*)ghost;
    delete prop;
}

naGhostType PropNodeGhostType = { propNodeGhostDestroy, "prop" };

naRef propNodeGhostCreate(naContext c, SGPropertyNode* n)
{
    if(!n) return naNil();
    SGPropertyNode_ptr* ghost = new SGPropertyNode_ptr(n);
    return naNewGhost(c, &PropNodeGhostType, ghost);
}

naRef FGNasalSys::propNodeGhost(SGPropertyNode* handle)
{
    return propNodeGhostCreate(_context, handle);
}

SGPropertyNode* ghostToPropNode(naRef ref)
{
  if (!naIsGhost(ref) || (naGhost_type(ref) != &PropNodeGhostType))
    return NULL;
 
  SGPropertyNode_ptr* pp = (SGPropertyNode_ptr*) naGhost_ptr(ref);
  return pp->ptr();
}

#define NASTR(s) s ? naStr_fromdata(naNewString(c),(char*)(s),strlen(s)) : naNil()

//
// Standard header for the extension functions.  It turns the "ghost"
// found in arg[0] into a SGPropertyNode_ptr*, and then "unwraps" the
// vector found in the second argument into a normal-looking args
// array.  This allows the Nasal handlers to do things like:
//   Node.getChild = func { _getChild(me.ghost, arg) }
//
#define NODENOARG()                                                       \
    if(argc < 2 || !naIsGhost(args[0]) ||                               \
        naGhost_type(args[0]) != &PropNodeGhostType)                      \
        naRuntimeError(c, "bad argument to props function");            \
    SGPropertyNode_ptr* node = (SGPropertyNode_ptr*)naGhost_ptr(args[0]);

#define NODEARG()                                                         \
    NODENOARG();                                                        \
    naRef argv = args[1]

//
// Pops the first argument as a relative path if the first condition
// is true (e.g. argc > 1 for getAttribute) and if it is a string.
// If the second confition is true, then another is popped to specify
// if the node should be created (i.e. like the second argument to
// getNode())
//
// Note that this makes the function return nil if the node doesn't
// exist, so all functions with a relative_path parameter will
// return nil if the specified node does not exist.
//
#define MOVETARGET(cond1, cond2)                                           \
    if(cond1) {                                                          \
        naRef name = naVec_get(argv, 0);                                   \
        if(naIsString(name)) {                                           \
            if (cond2) {                                                   \
                naVec_removefirst(vec);                                  \
                bool create = naTrue(naVec_get(argv, 0)) ? true : false;   \
            } else bool create = false;                                  \
            try {                                                          \
                n = (*node)->getNode(naStr_data(name), create);          \
            } catch(const string& err) {                                   \
                naRuntimeError(c, (char *)err.c_str());                  \
                return naNil();                                            \
            }                                                            \
            if(!n) return naNil();                                         \
            (*node) = n;                                                 \
            naVec_removefirst(argv); /* pop only if we were successful */  \
        }                                                                \
    }


// Get the type of a property (returns a string).
// Forms:
//    props.Node.getType(string relative_path,
//                       bool create=false);
//    props.Node.getType();
static naRef f_getType(naContext c, naRef me, int argc, naRef* args)
{
    using namespace simgear;
    NODENOARG();
    MOVETARGET(naVec_size(argv) > 0, naVec_size(argv) > 1);
    const char* t = "unknown";
    switch((*node)->getType()) {
    case props::NONE:   t = "NONE";   break;
    case props::ALIAS:  t = "ALIAS";  break;
    case props::BOOL:   t = "BOOL";   break;
    case props::INT:    t = "INT";    break;
    case props::LONG:   t = "LONG";   break;
    case props::FLOAT:  t = "FLOAT";  break;
    case props::DOUBLE: t = "DOUBLE"; break;
    case props::STRING: t = "STRING"; break;
    case props::UNSPECIFIED: t = "UNSPECIFIED"; break;
    case props::VEC3D:  t = "VEC3D";  break;
    case props::VEC4D:  t = "VEC4D";  break;
    case props::EXTENDED: t = "EXTENDED";  break; // shouldn't happen
    }
    return NASTR(t);
}


// Get an attribute of a property by name (returns true/false).
// Forms:
//    props.Node.getType(string relative_path,
//                       bool create=false,
//                       string attribute_name);
//    props.Node.getType(string attribute_name);
static naRef f_getAttribute(naContext c, naRef me, int argc, naRef* args)
{
    NODEARG();
    if(naVec_size(argv) == 0) return naNum(unsigned((*node)->getAttributes()));
    MOVETARGET(naVec_size(argv) > 1, naVec_size(argv) > 2);
    naRef val = naVec_get(argv, 0);
    const char *a = naStr_data(val);
    SGPropertyNode::Attribute attr;
    if(!a) a = "";
    if(!strcmp(a, "last")) return naNum(SGPropertyNode::LAST_USED_ATTRIBUTE);
    else if(!strcmp(a, "children"))    return naNum((*node)->nChildren());
    else if(!strcmp(a, "listeners"))   return naNum((*node)->nListeners());
    else if(!strcmp(a, "references"))  return naNum(node->getNumRefs());
    else if(!strcmp(a, "tied"))        return naNum((*node)->isTied());
    else if(!strcmp(a, "alias"))       return naNum((*node)->isAlias());
    else if(!strcmp(a, "readable"))    attr = SGPropertyNode::READ;
    else if(!strcmp(a, "writable"))    attr = SGPropertyNode::WRITE;
    else if(!strcmp(a, "archive"))     attr = SGPropertyNode::ARCHIVE;
    else if(!strcmp(a, "trace-read"))  attr = SGPropertyNode::TRACE_READ;
    else if(!strcmp(a, "trace-write")) attr = SGPropertyNode::TRACE_WRITE;
    else if(!strcmp(a, "userarchive")) attr = SGPropertyNode::USERARCHIVE;
    else if(!strcmp(a, "preserve"))    attr = SGPropertyNode::PRESERVE;
    else {
        naRuntimeError(c, "props.getAttribute() with invalid attribute");
        return naNil();
    }
    return naNum((*node)->getAttribute(attr));
}


// Set an attribute by name and boolean value.
// Forms:
//    props.Node.setAttribute(string relative_path,
//                            bool create=false,
//                            string attribute_name,
//                            bool value);
//    props.Node.setAttribute(string attribute_name,
//                            bool value);
//    props.Node.setArtribute(int attribute_number);
static naRef f_setAttribute(naContext c, naRef me, int argc, naRef* args)
{
    NODEARG();
    MOVETARGET(naVec_size(argv) > 2, naVec_size(argv) > 3);
    naRef val = naVec_get(argv, 0);
    if(naVec_size(argv) == 1 && naIsNum(val)) {
        naRef ret = naNum((*node)->getAttributes());
        (*node)->setAttributes((int)val.num);
        return ret;
    }
    SGPropertyNode::Attribute attr;
    const char *a = naStr_data(val);
    if(!a) a = "";
    if(!strcmp(a, "readable"))         attr = SGPropertyNode::READ;
    else if(!strcmp(a, "writable"))    attr = SGPropertyNode::WRITE;
    else if(!strcmp(a, "archive"))     attr = SGPropertyNode::ARCHIVE;
    else if(!strcmp(a, "trace-read"))  attr = SGPropertyNode::TRACE_READ;
    else if(!strcmp(a, "trace-write")) attr = SGPropertyNode::TRACE_WRITE;
    else if(!strcmp(a, "userarchive")) attr = SGPropertyNode::USERARCHIVE;
    else if(!strcmp(a, "preserve"))    attr = SGPropertyNode::PRESERVE;
    else {
        naRuntimeError(c, "props.setAttribute() with invalid attribute");
        return naNil();
    }
    naRef ret = naNum((*node)->getAttribute(attr));
    (*node)->setAttribute(attr, naTrue(naVec_get(argv, 1)) ? true : false);
    return ret;
}


// Get the simple name of this node.
// Forms:
//    props.Node.getName();
static naRef f_getName(naContext c, naRef me, int argc, naRef* args)
{
    NODENOARG();
    return NASTR((*node)->getName());
}


// Get the index of this node.
// Forms:
//    props.Node.getIndex();
static naRef f_getIndex(naContext c, naRef me, int argc, naRef* args)
{
    NODENOARG();
    return naNum((*node)->getIndex());
}

template<typename T>
naRef makeVectorFromVec(naContext c, const T& vec)
{
    const int num_components
        = sizeof(vec.data()) / sizeof(typename T::value_type);
    naRef vector = naNewVector(c);
    naVec_setsize(c, vector, num_components);
    for (int i = 0; i < num_components; ++i)
        naVec_set(vector, i, naNum(vec[i]));
    return vector;
}


// Get the value of a node, with or without a relative path.
// Forms:
//    props.Node.getValue(string relative_path,
//                        bool create=false);
//    props.Node.getValue();
static naRef f_getValue(naContext c, naRef me, int argc, naRef* args)
{
    using namespace simgear;
    NODENOARG();
    MOVETARGET(naVec_size(argv) > 0, naVec_size(argv) > 1);
    switch((*node)->getType()) {
    case props::BOOL:   case props::INT:
    case props::LONG:   case props::FLOAT:
    case props::DOUBLE:
    {
        double dv = (*node)->getDoubleValue();
        if (SGMisc<double>::isNaN(dv)) {
          SG_LOG(SG_NASAL, SG_ALERT, "Nasal getValue: property " << (*node)->getPath() << " is NaN");
          return naNil();
        }

        return naNum(dv);
    }

    case props::STRING:
    case props::UNSPECIFIED:
        return NASTR((*node)->getStringValue());
    case props::VEC3D:
        return makeVectorFromVec(c, (*node)->getValue<SGVec3d>());
    case props::VEC4D:
        return makeVectorFromVec(c, (*node)->getValue<SGVec4d>());
    default:
        return naNil();
    }
}

template<typename T>
T makeVecFromVector(naRef vector)
{
    T vec;
    const int num_components
        = sizeof(vec.data()) / sizeof(typename T::value_type);
    int size = naVec_size(vector);

    for (int i = 0; i < num_components && i < size; ++i) {
        naRef element = naVec_get(vector, i);
        naRef n = naNumValue(element);
        if (!naIsNil(n))
            vec[i] = n.num;
    }
    return vec;
}


// Set the value of a node; returns true if it succeeded or
// false if it failed. <val> can be a string, number, or a
// vector or numbers (for SGVec3D/4D types).
// Forms:
//    props.Node.setValue(string relative_path,
//                        bool create=false,
//                        val);
//    props.Node.setValue(val);
static naRef f_setValue(naContext c, naRef me, int argc, naRef* args)
{
    NODEARG();
    MOVETARGET(naVec_size(argv) > 1, naVec_size(argv) > 2);
    naRef val = naVec_get(argv, 0);
    bool result = false;
    if(naIsString(val)) {
         result = (*node)->setStringValue(naStr_data(val));
    } else if(naIsVector(val)) {
        if(naVec_size(val) == 3)
            result = (*node)->setValue(makeVecFromVector<SGVec3d>(val));
        else if(naVec_size(val) == 4)
            result = (*node)->setValue(makeVecFromVector<SGVec4d>(val));
        else
            naRuntimeError(c, "props.setValue() vector value has wrong size");
    } else {
        naRef n = naNumValue(val);
        if(naIsNil(n))
            naRuntimeError(c, "props.setValue() with non-number");

        double d = naNumValue(val).num;
        if (SGMisc<double>::isNaN(d)) {
          naRuntimeError(c, "props.setValue() passed a NaN");
        }

        result = (*node)->setDoubleValue(d);
    }
    return naNum(result);
}

static naRef f_setIntValue(naContext c, naRef me, int argc, naRef* args)
{
    NODEARG();
    MOVETARGET(naVec_size(argv) > 1, naVec_size(argv) > 2);
    // Original code:
    //   int iv = (int)naNumValue(naVec_get(argv, 0)).num;

    // Junk to pacify the gcc-2.95.3 optimizer:
    naRef tmp0 = naVec_get(argv, 0);
    naRef tmp1 = naNumValue(tmp0);
    if(naIsNil(tmp1))
        naRuntimeError(c, "props.setIntValue() with non-number");
    double tmp2 = tmp1.num;
    int iv = (int)tmp2;

    return naNum((*node)->setIntValue(iv));
}

static naRef f_setBoolValue(naContext c, naRef me, int argc, naRef* args)
{
    NODEARG();
    MOVETARGET(naVec_size(argv) > 1, naVec_size(argv) > 2);
    naRef val = naVec_get(argv, 0);
    return naNum((*node)->setBoolValue(naTrue(val) ? true : false));
}

static naRef f_setDoubleValue(naContext c, naRef me, int argc, naRef* args)
{
    NODEARG();
    MOVETARGET(naVec_size(argv) > 1, naVec_size(argv) > 2);
    naRef r = naNumValue(naVec_get(argv, 0));
    if (naIsNil(r))
        naRuntimeError(c, "props.setDoubleValue() with non-number");

    if (SGMisc<double>::isNaN(r.num)) {
      naRuntimeError(c, "props.setDoubleValue() passed a NaN");
    }

    return naNum((*node)->setDoubleValue(r.num));
}


// Get the parent of this node as a ghost.
// Forms:
//    props.Node.getParent();
static naRef f_getParent(naContext c, naRef me, int argc, naRef* args)
{
    NODENOARG();
    SGPropertyNode* n = (*node)->getParent();
    if(!n) return naNil();
    return propNodeGhostCreate(c, n);
}


// Get a child by name and optional index=0, creating if specified (by default it
// does not create it). If the node does not exist and create is false, then it
// returns nil, else it returns a (possibly new) property ghost.
// Forms:
//    props.Node.getChild(string relative_path,
//                        int index=0,
//                        bool create=false);
static naRef f_getChild(naContext c, naRef me, int argc, naRef* args)
{
    NODEARG();
    naRef child = naVec_get(argv, 0);
    if(!naIsString(child)) return naNil();
    naRef idx = naNumValue(naVec_get(argv, 1));
    bool create = naTrue(naVec_get(argv, 2)) != 0;
    SGPropertyNode* n;
    try {
        if(naIsNil(idx)) {
            n = (*node)->getChild(naStr_data(child), create);
        } else {
            n = (*node)->getChild(naStr_data(child), (int)idx.num, create);
        }
    } catch (const string& err) {
        naRuntimeError(c, (char *)err.c_str());
        return naNil();
    }
    if(!n) return naNil();
    return propNodeGhostCreate(c, n);
}


// Get all children with a specified name as a vector of ghosts.
// Forms:
//    props.Node.getChildren(string relative_path);
//    props.Node.getChildren(); #get all children
static naRef f_getChildren(naContext c, naRef me, int argc, naRef* args)
{
    NODEARG();
    naRef result = naNewVector(c);
    if(naIsNil(argv) || naVec_size(argv) == 0) {
        // Get all children
        for(int i=0; i<(*node)->nChildren(); i++)
            naVec_append(result, propNodeGhostCreate(c, (*node)->getChild(i)));
    } else {
        // Get all children of a specified name
        naRef name = naVec_get(argv, 0);
        if(!naIsString(name)) return naNil();
        try {
            vector<SGPropertyNode_ptr> children
                = (*node)->getChildren(naStr_data(name));
            for(unsigned int i=0; i<children.size(); i++)
                naVec_append(result, propNodeGhostCreate(c, children[i]));
        } catch (const string& err) {
            naRuntimeError(c, (char *)err.c_str());
            return naNil();
        }
    }
    return result;
}


// Append a named child at the first unused index...
// Forms:
//    props.Node.addChild(string name,
//                        int min_index=0,
//                        bool append=true);
static naRef f_addChild(naContext c, naRef me, int argc, naRef* args)
{
    NODEARG();
    naRef child = naVec_get(argv, 0);
    if(!naIsString(child)) return naNil();
    naRef ref_min_index = naNumValue(naVec_get(argv, 1));
    naRef ref_append = naVec_get(argv, 2);
    SGPropertyNode* n;
    try
    {
      int min_index = 0;
      if(!naIsNil(ref_min_index))
        min_index = ref_min_index.num;

      bool append = true;
      if(!naIsNil(ref_append))
        append = naTrue(ref_append) != 0;

      n = (*node)->addChild(naStr_data(child), min_index, append);
    }
    catch (const string& err)
    {
      naRuntimeError(c, (char *)err.c_str());
      return naNil();
    }

    return propNodeGhostCreate(c, n);
}

static naRef f_addChildren(naContext c, naRef me, int argc, naRef* args)
{
    NODEARG();
    naRef child = naVec_get(argv, 0);
    if(!naIsString(child)) return naNil();
    naRef ref_count = naNumValue(naVec_get(argv, 1));
    naRef ref_min_index = naNumValue(naVec_get(argv, 2));
    naRef ref_append = naVec_get(argv, 3);
    try
    {
      size_t count = 0;
      if( !naIsNum(ref_count) )
        throw string("props.addChildren() missing number of children");
      count = ref_count.num;

      int min_index = 0;
      if(!naIsNil(ref_min_index))
        min_index = ref_min_index.num;

      bool append = true;
      if(!naIsNil(ref_append))
        append = naTrue(ref_append) != 0;

      const simgear::PropertyList& nodes =
        (*node)->addChildren(naStr_data(child), count, min_index, append);

      naRef result = naNewVector(c);
      for( size_t i = 0; i < nodes.size(); ++i )
        naVec_append(result, propNodeGhostCreate(c, nodes[i]));
      return result;
    }
    catch (const string& err)
    {
      naRuntimeError(c, (char *)err.c_str());
    }

    return naNil();
}


// Remove a child by name and index. Returns it as a ghost.
// Forms:
//    props.Node.removeChild(string relative_path,
//                           int index);
static naRef f_removeChild(naContext c, naRef me, int argc, naRef* args)
{
    NODEARG();
    naRef child = naVec_get(argv, 0);
    naRef index = naVec_get(argv, 1);
    if(!naIsString(child) || !naIsNum(index)) return naNil();
    SGPropertyNode_ptr n = 0;
    try {
        n = (*node)->removeChild(naStr_data(child), (int)index.num, false);
    } catch (const string& err) {
        naRuntimeError(c, (char *)err.c_str());
    }
    return propNodeGhostCreate(c, n);
}


// Remove all children with specified name. Returns a vector of all the nodes
// removed as ghosts.
// Forms:
//    props.Node.removeChildren(string relative_path);
//    props.Node.removeChildren(); #remove all children
static naRef f_removeChildren(naContext c, naRef me, int argc, naRef* args)
{
    NODEARG();
    naRef result = naNewVector(c);
    if(naIsNil(argv) || naVec_size(argv) == 0) {
        // Remove all children
        for(int i = (*node)->nChildren() - 1; i >=0; i--)
            naVec_append(result, propNodeGhostCreate(c, (*node)->removeChild(i, false)));
    } else {
        // Remove all children of a specified name
        naRef name = naVec_get(argv, 0);
        if(!naIsString(name)) return naNil();
        try {
            vector<SGPropertyNode_ptr> children
                = (*node)->removeChildren(naStr_data(name), false);
            for(unsigned int i=0; i<children.size(); i++)
                naVec_append(result, propNodeGhostCreate(c, children[i]));
        } catch (const string& err) {
            naRuntimeError(c, (char *)err.c_str());
            return naNil();
        }
    }
    return result;
}


// Alias this property to another one; returns 1 on success or 0 on failure
// (only applicable to tied properties).
// Forms:
//    props.Node.alias(string global_path);
//    props.Node.alias(prop_ghost node);
//    props.Node.alias(props.Node node); #added by props.nas
static naRef f_alias(naContext c, naRef me, int argc, naRef* args)
{
    NODEARG();
    SGPropertyNode* al;
    naRef prop = naVec_get(argv, 0);
    try {
        if(naIsString(prop)) al = globals->get_props()->getNode(naStr_data(prop), true);
        else if(naIsGhost(prop)) al = *(SGPropertyNode_ptr*)naGhost_ptr(prop);
        else throw string("props.alias() with bad argument");
    } catch (const string& err) {
        naRuntimeError(c, (char *)err.c_str());
        return naNil();
    }
    return naNum((*node)->alias(al));
}


// Un-alias this property. Returns 1 on success or 0 on failure (only
// applicable to tied properties).
// Forms:
//    props.Node.unalias();
static naRef f_unalias(naContext c, naRef me, int argc, naRef* args)
{
    NODENOARG();
    return naNum((*node)->unalias());
}


// Get the alias of this node as a ghost.
// Forms:
//    props.Node.getAliasTarget();
static naRef f_getAliasTarget(naContext c, naRef me, int argc, naRef* args)
{
    NODENOARG();
    return propNodeGhostCreate(c, (*node)->getAliasTarget());
}


// Get a relative node. Returns nil if it does not exist and create is false,
// or a ghost object otherwise (wrapped into a props.Node object by props.nas).
// Forms:
//    props.Node.getNode(string relative_path,
//                       bool create=false);
static naRef f_getNode(naContext c, naRef me, int argc, naRef* args)
{
    NODEARG();
    naRef path = naVec_get(argv, 0);
    bool create = naTrue(naVec_get(argv, 1)) != 0;
    if(!naIsString(path)) return naNil();
    SGPropertyNode* n;
    try {
        n = (*node)->getNode(naStr_data(path), create);
    } catch (const string& err) {
        naRuntimeError(c, (char *)err.c_str());
        return naNil();
    }
    return propNodeGhostCreate(c, n);
}


// Create a new property node.
// Forms:
//    props.Node.new();
static naRef f_new(naContext c, naRef me, int argc, naRef* args)
{
    return propNodeGhostCreate(c, new SGPropertyNode());
}


// Get the global root node (cached by props.nas so that it does
// not require a function call).
// Forms:
//    props._globals()
//    props.globals
static naRef f_globals(naContext c, naRef me, int argc, naRef* args)
{
    return propNodeGhostCreate(c, globals->get_props());
}

static struct {
    naCFunction func;
    const char* name;
} propfuncs[] = {
    { f_getType,        "_getType"        },
    { f_getAttribute,   "_getAttribute"   },
    { f_setAttribute,   "_setAttribute"   },
    { f_getName,        "_getName"        },
    { f_getIndex,       "_getIndex"       },
    { f_getValue,       "_getValue"       },
    { f_setValue,       "_setValue"       },
    { f_setIntValue,    "_setIntValue"    },
    { f_setBoolValue,   "_setBoolValue"   },
    { f_setDoubleValue, "_setDoubleValue" },
    { f_getParent,      "_getParent"      },
    { f_getChild,       "_getChild"       },
    { f_getChildren,    "_getChildren"    },
    { f_addChild,       "_addChild"       },
    { f_addChildren,    "_addChildren"    },
    { f_removeChild,    "_removeChild"    },
    { f_removeChildren, "_removeChildren" },
    { f_alias,          "_alias"          },
    { f_unalias,        "_unalias"        },
    { f_getAliasTarget, "_getAliasTarget" },
    { f_getNode,        "_getNode"        },
    { f_new,            "_new"            },
    { f_globals,        "_globals"        },
    { 0, 0 }
};

naRef FGNasalSys::genPropsModule()
{
    naRef namespc = naNewHash(_context);
    for(int i=0; propfuncs[i].name; i++)
        hashset(namespc, propfuncs[i].name,
                naNewFunc(_context, naNewCCode(_context, propfuncs[i].func)));
    return namespc;
}


Oh, and here's the removefirst function!

Code: Select all

naRef naVec_removefirst(naRef vec)
{
    naRef o;
    int i;
    if(IS_VEC(vec)) {
        struct VecRec* v = PTR(vec).vec->rec;
        if(!v || v->size == 0) return naNil();
        o = v->array[0];
        for (i=1; i<v->size; i++)
            v->array[i-1] = v->array[i];
        v->size--;
        if(v->size < (v->alloced >> 1))
            resize(PTR(vec).vec);
        return o;
    }
    return naNil();
}
Last edited by Philosopher on Tue Apr 09, 2013 9:12 pm, edited 1 time in total.
Philosopher
 
Posts: 1593
Joined: Sun Aug 12, 2012 7:29 pm

Re: Relative paths for Nasal props.Node methods

Postby Philosopher » Tue Apr 09, 2013 7:38 pm

Some improvements to props.nas. Firstly, a better setAll function that should be 100% backwards-compatible:
Code: Select all
##
# Sets all indexed property children to a single value.  base
# specifies a property (e.g. /controls/engines/engine), child a
# path under each node of that name to set (e.g. "throttle"),
# and value is the value to set each node to (as per setValue
# conventions).
#
var setAll = func(base, child, value) {
    if (isa(base, Node)) var node = base;
    elsif (typeof(base) == 'ghost' and ghosttype(base) == 'prop')
        var node = wrapNode(base);
    else var node = props.globals.getNode(base);
    if(node == nil) return;
    foreach(var c; node.getParent().getChildren(node.getName()))
        c.getNode(child, 1).setValue(value);
}


I added getAttributes and setAttributes (plural) to the Node hash:
Code: Select all
    ##
    # Get several attributes, either on this node or on another node
    # specified by a relative path. If no specific attributes are
    # specified, then all available attributes are fetched. Returns
    # a hash of attributes and values.
    # Forms:
    #    props.Node.getAttributes(string relative_path,
    #                             vector attributes);
    #    props.Node.getAttributes(string relative_path);
    #    props.Node.getAttributes(vector attributes);
    #
    getAttributes : func {
        if (size(arg) == 2)
            var (relative_path, attrs) = arg;
        elsif (size(arg) == 1)
            if (typeof(arg[0]) == 'scalar')
              { var relative_path = arg[0]; var attrs = attributes.get; }
            elsif (typeof(arg[0] == 'vector')
              { var relative_path = ""; var attrs = arg[0]; }
            else die("bad argument to getAttributes: needs to be a string (the relative "
                     "path) or vector (list of attributes to get)");
        else die("wrong number of arguments to getAttributes (needs to be of size 1 or 2)");
        var ret = {};
        foreach (var attr; attrs)
            ret[attr] = me.getAttribute(relative_path, attr);
        return ret;
    },

    ##
    # Set several attributes at once, either on this node or on
    # another node specified by a relative path. Attributes are
    # passed in a hash as attribute:value pairs. Returns the
    # previous value of each attribute set (i.e. returns what a
    # getAttributes call would have returned).
    # Forms:
    #    props.Node.setAttributes(string relative_path,
    #                             hash attributes&values);
    #    props.Node.getAttributes(hash attributes&values);
    #
    setAttributes : func {
        if (size(arg) == 2)
            var (relative_path, attrs) = arg;
        elsif (size(arg) == 1) { var relative_path = ""; attrs = arg[0] }
        else die("wrong number of arguments to setAttributes");
        var ret = {};
        foreach (var attr; attributes.set)
            if (contains(attrs, attr))
                ret[attr] = me.setAttribute(relative_path, attr, attrs[attr]);
        return ret;
    },


And that requires this list of valid attributes (place after the end of the Node hash):
Code: Select all
##
# A list of all attributes, split into ones for setAttribute and
# getAttribute.
#
var attributes = {
    set: ["readable", "writable", "archive", "trace-read",
          "trace-write", "userarchive", "preserve"],
    get: attributes.set~
         ["last", "children", "listeners",
          "references", "tied", "alias"],
};
Philosopher
 
Posts: 1593
Joined: Sun Aug 12, 2012 7:29 pm

Re: Relative paths for Nasal props.Node methods

Postby Philosopher » Tue Apr 09, 2013 9:00 pm

Here's a simple sanity test, feel free to suggest things to add -- this is not something I am particularly good at (I'm only doing it since Hooray suggested that it might help with code review ;)):

Code: Select all
var error = func call(print, ["Error in props_test: "]~arg);
var hasNode = func(node, path) return node.getNode(path) != nil;

var aNode = props.Node.new({
   one: 1, two: 2, folder: {
      item: [0, 1]
   },
});

# Only put leaf nodes here:
var paths = [
   "one", "two",
   "folder/item[0]",
   "folder/item[1]"
];

foreach (var path; paths)
   if (aNode.getNode(path) == nil)
      error("setValues: creation of node "~path~" failed!");
   elsif (aNode.getNode(path).getValue() != aNode.getValue(path))
      error("relative path version of getValue does not match on node "~path);
   elsif (!aNode.getNode(path).getAttribute("readable") and
          !aNode.getNode(path).getAttribute("writable"))
      error("warning: implicit behavior of read/write access to a new node changed: "~path);
   elsif (!aNode.getNode(path).getAttribute("readable"))
      error("warning: implicit behavior of read access to a new node changed: "~path);
   elsif (!aNode.getNode(path).getAttribute("writable"))
      error("warning: implicit behavior of write access to a new node changed: "~path);
   elsif (aNode.getNode(path).getAttribute("archive") or
          aNode.getNode(path).getAttribute("trace-read") or
          aNode.getNode(path).getAttribute("trace-write") or
          aNode.getNode(path).getAttribute("userarchive") or
          aNode.getNode(path).getAttribute("preserve") or
          aNode.getNode(path).getAttribute("tied") or
          aNode.getNode(path),getAttribute("alias"))
      error("node "~path~" should not have any other attributes!");

if (aNode.getValue("folder") != nil)
   error("parent nodes should have a value of nil!");
if (aNode.getAttribute("folder", "children") != 2)
   error("wrong number of children in folder");
elsif (aNode.getAttribute("folder", "children") !=
       size(aNode.getNode("folder").getChildren()))
   error("getAttribute('children') and size(getChildren) do not match!");

if (aNode.getNode("one").getParent() != aNode)
   error("getParent returns wrong node");
if (aNode.getNode("folder/item[0]").getParent().getParent() != aNode)
   error("getParent.getParent returns wrong node");

aNode.setValue("new", 1, 3);
if (!hasNode(aNode, "new"))
   error("relative path failed to create child");

var values = aNode.getValues();

if (values != { one: 1, two: 2,
                folder: {
                  item: [0, 1]
                }, new: 3,
              })
   error("either getValues does not work or the node is not correct");

aNode.setAttribute("one", "writable", 0);
if (aNode.setValue("one", 0) == 1)
   error("setValue succeeded on a read-only node");
Philosopher
 
Posts: 1593
Joined: Sun Aug 12, 2012 7:29 pm

Re: Relative paths for Nasal props.Node methods

Postby TheTom » Wed Apr 10, 2013 2:38 pm

Looking good to me (although I'd prefer to see cppbind used for Nasal/C++ bindings, but probably it'll be better to port the whole file at once). One thing I don't like is the signature of getValue and similar methods. I'd keep it similar to the core SGPropertyNode and don't allow creating new nodes with eg. getValue but instead return a default value (which can be passed as default argument). Otherwise, having relative paths is definitely a useful addition.
TheTom
 
Posts: 322
Joined: Sun Oct 09, 2011 11:20 am

Re: Relative paths for Nasal props.Node methods

Postby Philosopher » Wed Apr 10, 2013 3:33 pm

Okay, so to get it clear, you don't like the create argument? That's fair, I actually see that it isn't such a great idea now :P — for most cases, at least. I actually added it after my initial proposal, so I can take it back out, but I was originally going to do MOVETARGET(naVec_size > 0, false) for getters & setAttribute, but then MOVETARGET(size > 1, size > 2) for setValue/setBoolValue/etc. (then I forgot to do so). What should be discussed is whether the setValues are OK to have a create argument. I think it would be fine to have and default it to true (because it makes more sense to have a setValue create the node, but you still want both behaviors, so leave room for an argument specifying to not create it):

Code: Select all
Node.setValue(3); #set value of current node
Node.setValue("current-view", 3); #create it if needed
Node.setValue("current-view", 0, 3); #do not create it


Also, the "default value" to be returned when the node doesn't exist would be nil regardless of the function (see the MOVETARGET macro). They already return nil for wrong arguments most of the time ;).

Thanks for taking a look at it!
Philosopher
 
Posts: 1593
Joined: Sun Aug 12, 2012 7:29 pm

Re: Relative paths for Nasal props.Node methods

Postby TheTom » Wed Apr 10, 2013 10:33 pm

Philosopher wrote in Wed Apr 10, 2013 3:33 pm:Okay, so to get it clear, you don't like the create argument?

No, I just don't like it for get/setValue/Attribute. Two things I'd like to keep in mind. First, the C++ and Nasal API should be usable the same way. Ideally all methods/functions have the same signature and also do the same thing. And second a function/method should only do what it's name indicates. Eg. if I see a method called setValue I'd expect it to always set a value, even if the node does not exist.
TheTom
 
Posts: 322
Joined: Sun Oct 09, 2011 11:20 am

Re: Relative paths for Nasal props.Node methods

Postby Philosopher » Wed Apr 10, 2013 11:47 pm

Okay, that was an oversight on my part. Here's the revised file:

Code: Select all
#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include <cstring>

#include <simgear/nasal/nasal.h>
#include <simgear/props/props.hxx>
#include <simgear/props/vectorPropTemplates.hxx>

#include <Main/globals.hxx>

#include "NasalSys.hxx"

using namespace std;

// Implementation of a Nasal wrapper for the SGPropertyNode class,
// using the Nasal "ghost" (er... Garbage collection Handle for
// OutSide Thingy) facility.
//
// Note that these functions appear in Nasal with prepended
// underscores.  They work on the low-level "ghost" objects and aren't
// intended for use from user code, but from Nasal code you will find
// in props.nas.  That is where the Nasal props.Node class is defined,
// which provides a saner interface along the lines of SGPropertyNode.

static void propNodeGhostDestroy(void* ghost)
{
    SGPropertyNode_ptr* prop = (SGPropertyNode_ptr*)ghost;
    delete prop;
}

naGhostType PropNodeGhostType = { propNodeGhostDestroy, "prop" };

naRef propNodeGhostCreate(naContext c, SGPropertyNode* n)
{
    if(!n) return naNil();
    SGPropertyNode_ptr* ghost = new SGPropertyNode_ptr(n);
    return naNewGhost(c, &PropNodeGhostType, ghost);
}

naRef FGNasalSys::propNodeGhost(SGPropertyNode* handle)
{
    return propNodeGhostCreate(_context, handle);
}

SGPropertyNode* ghostToPropNode(naRef ref)
{
  if (!naIsGhost(ref) || (naGhost_type(ref) != &PropNodeGhostType))
    return NULL;
  
  SGPropertyNode_ptr* pp = (SGPropertyNode_ptr*) naGhost_ptr(ref);
  return pp->ptr();
}

#define NASTR(s) s ? naStr_fromdata(naNewString(c),(char*)(s),strlen(s)) : naNil()

//
// Standard header for the extension functions.  It turns the "ghost"
// found in arg[0] into a SGPropertyNode_ptr*, and then "unwraps" the
// vector found in the second argument into a normal-looking args
// array.  This allows the Nasal handlers to do things like:
//   Node.getChild = func { _getChild(me.ghost, arg) }
//
#define NODENOARG()                                                       \
    if(argc < 2 || !naIsGhost(args[0]) ||                               \
        naGhost_type(args[0]) != &PropNodeGhostType)                      \
        naRuntimeError(c, "bad argument to props function");            \
    SGPropertyNode_ptr* node = (SGPropertyNode_ptr*)naGhost_ptr(args[0]);

#define NODEARG()                                                         \
    NODENOARG();                                                        \
    naRef argv = args[1]

//
// Pops the first argument as a relative path if the first condition
// is true (e.g. argc > 1 for getAttribute) and if it is a string.
// If the second confition is true, then another is popped to specify
// if the node should be created (i.e. like the second argument to
// getNode())
//
// Note that this makes the function return nil if the node doesn't
// exist, so all functions with a relative_path parameter will
// return nil if the specified node does not exist.
//
#define MOVETARGET(cond1, create)                                          \
    if(cond1) {                                                          \
        naRef name = naVec_get(argv, 0);                                   \
        if(naIsString(name)) {                                           \
            try {                                                          \
                n = (*node)->getNode(naStr_data(name), create);          \
            } catch(const string& err) {                                   \
                naRuntimeError(c, (char *)err.c_str());                  \
                return naNil();                                            \
            }                                                            \
            if(!n) return naNil();                                         \
            (*node) = n;                                                 \
            naVec_removefirst(argv); /* pop only if we were successful */  \
        }                                                                \
    }


// Get the type of a property (returns a string).
// Forms:
//    props.Node.getType(string relative_path);
//    props.Node.getType();
static naRef f_getType(naContext c, naRef me, int argc, naRef* args)
{
    using namespace simgear;
    NODENOARG();
    MOVETARGET(naVec_size(argv) > 0, false);
    const char* t = "unknown";
    switch((*node)->getType()) {
    case props::NONE:   t = "NONE";   break;
    case props::ALIAS:  t = "ALIAS";  break;
    case props::BOOL:   t = "BOOL";   break;
    case props::INT:    t = "INT";    break;
    case props::LONG:   t = "LONG";   break;
    case props::FLOAT:  t = "FLOAT";  break;
    case props::DOUBLE: t = "DOUBLE"; break;
    case props::STRING: t = "STRING"; break;
    case props::UNSPECIFIED: t = "UNSPECIFIED"; break;
    case props::VEC3D:  t = "VEC3D";  break;
    case props::VEC4D:  t = "VEC4D";  break;
    case props::EXTENDED: t = "EXTENDED";  break; // shouldn't happen
    }
    return NASTR(t);
}


// Get an attribute of a property by name (returns true/false).
// Forms:
//    props.Node.getType(string relative_path,
//                       string attribute_name);
//    props.Node.getType(string attribute_name);
static naRef f_getAttribute(naContext c, naRef me, int argc, naRef* args)
{
    NODEARG();
    if(naVec_size(argv) == 0) return naNum(unsigned((*node)->getAttributes()));
    MOVETARGET(naVec_size(argv) > 1, false);
    naRef val = naVec_get(argv, 0);
    const char *a = naStr_data(val);
    SGPropertyNode::Attribute attr;
    if(!a) a = "";
    if(!strcmp(a, "last")) return naNum(SGPropertyNode::LAST_USED_ATTRIBUTE);
    else if(!strcmp(a, "children"))    return naNum((*node)->nChildren());
    else if(!strcmp(a, "listeners"))   return naNum((*node)->nListeners());
    else if(!strcmp(a, "references"))  return naNum(node->getNumRefs());
    else if(!strcmp(a, "tied"))        return naNum((*node)->isTied());
    else if(!strcmp(a, "alias"))       return naNum((*node)->isAlias());
    else if(!strcmp(a, "readable"))    attr = SGPropertyNode::READ;
    else if(!strcmp(a, "writable"))    attr = SGPropertyNode::WRITE;
    else if(!strcmp(a, "archive"))     attr = SGPropertyNode::ARCHIVE;
    else if(!strcmp(a, "trace-read"))  attr = SGPropertyNode::TRACE_READ;
    else if(!strcmp(a, "trace-write")) attr = SGPropertyNode::TRACE_WRITE;
    else if(!strcmp(a, "userarchive")) attr = SGPropertyNode::USERARCHIVE;
    else if(!strcmp(a, "preserve"))    attr = SGPropertyNode::PRESERVE;
    else {
        naRuntimeError(c, "props.getAttribute() with invalid attribute");
        return naNil();
    }
    return naNum((*node)->getAttribute(attr));
}


// Set an attribute by name and boolean value or raw (bitmasked) number.
// Forms:
//    props.Node.setAttribute(string relative_path,
//                            string attribute_name,
//                            bool value);
//    props.Node.setAttribute(string attribute_name,
//                            bool value);
//    props.Node.setArtribute(int attributes);
static naRef f_setAttribute(naContext c, naRef me, int argc, naRef* args)
{
    NODEARG();
    MOVETARGET(naVec_size(argv) > 2, false);
    naRef val = naVec_get(argv, 0);
    if(naVec_size(argv) == 1 && naIsNum(val)) {
        naRef ret = naNum((*node)->getAttributes());
        (*node)->setAttributes((int)val.num);
        return ret;
    }
    SGPropertyNode::Attribute attr;
    const char *a = naStr_data(val);
    if(!a) a = "";
    if(!strcmp(a, "readable"))         attr = SGPropertyNode::READ;
    else if(!strcmp(a, "writable"))    attr = SGPropertyNode::WRITE;
    else if(!strcmp(a, "archive"))     attr = SGPropertyNode::ARCHIVE;
    else if(!strcmp(a, "trace-read"))  attr = SGPropertyNode::TRACE_READ;
    else if(!strcmp(a, "trace-write")) attr = SGPropertyNode::TRACE_WRITE;
    else if(!strcmp(a, "userarchive")) attr = SGPropertyNode::USERARCHIVE;
    else if(!strcmp(a, "preserve"))    attr = SGPropertyNode::PRESERVE;
    else {
        naRuntimeError(c, "props.setAttribute() with invalid attribute");
        return naNil();
    }
    naRef ret = naNum((*node)->getAttribute(attr));
    (*node)->setAttribute(attr, naTrue(naVec_get(argv, 1)) ? true : false);
    return ret;
}


// Get the simple name of this node.
// Forms:
//    props.Node.getName();
static naRef f_getName(naContext c, naRef me, int argc, naRef* args)
{
    NODENOARG();
    return NASTR((*node)->getName());
}


// Get the index of this node.
// Forms:
//    props.Node.getIndex();
static naRef f_getIndex(naContext c, naRef me, int argc, naRef* args)
{
    NODENOARG();
    return naNum((*node)->getIndex());
}

template<typename T>
naRef makeVectorFromVec(naContext c, const T& vec)
{
    const int num_components
        = sizeof(vec.data()) / sizeof(typename T::value_type);
    naRef vector = naNewVector(c);
    naVec_setsize(c, vector, num_components);
    for (int i = 0; i < num_components; ++i)
        naVec_set(vector, i, naNum(vec[i]));
    return vector;
}


// Get the value of a node, with or without a relative path.
// Forms:
//    props.Node.getValue(string relative_path);
//    props.Node.getValue();
static naRef f_getValue(naContext c, naRef me, int argc, naRef* args)
{
    using namespace simgear;
    NODENOARG();
    MOVETARGET(naVec_size(argv) > 0, false);
    switch((*node)->getType()) {
    case props::BOOL:   case props::INT:
    case props::LONG:   case props::FLOAT:
    case props::DOUBLE:
    {
        double dv = (*node)->getDoubleValue();
        if (SGMisc<double>::isNaN(dv)) {
          SG_LOG(SG_NASAL, SG_ALERT, "Nasal getValue: property " << (*node)->getPath() << " is NaN");
          return naNil();
        }

        return naNum(dv);
    }

    case props::STRING:
    case props::UNSPECIFIED:
        return NASTR((*node)->getStringValue());
    case props::VEC3D:
        return makeVectorFromVec(c, (*node)->getValue<SGVec3d>());
    case props::VEC4D:
        return makeVectorFromVec(c, (*node)->getValue<SGVec4d>());
    default:
        return naNil();
    }
}

template<typename T>
T makeVecFromVector(naRef vector)
{
    T vec;
    const int num_components
        = sizeof(vec.data()) / sizeof(typename T::value_type);
    int size = naVec_size(vector);

    for (int i = 0; i < num_components && i < size; ++i) {
        naRef element = naVec_get(vector, i);
        naRef n = naNumValue(element);
        if (!naIsNil(n))
            vec[i] = n.num;
    }
    return vec;
}


// Set the value of a node; returns true if it succeeded or
// false if it failed. <val> can be a string, number, or a
// vector or numbers (for SGVec3D/4D types).
// Forms:
//    props.Node.setValue(string relative_path,
//                        val);
//    props.Node.setValue(val);
static naRef f_setValue(naContext c, naRef me, int argc, naRef* args)
{
    NODEARG();
    MOVETARGET(naVec_size(argv) > 1, true);
    naRef val = naVec_get(argv, 0);
    bool result = false;
    if(naIsString(val)) {
         result = (*node)->setStringValue(naStr_data(val));
    } else if(naIsVector(val)) {
        if(naVec_size(val) == 3)
            result = (*node)->setValue(makeVecFromVector<SGVec3d>(val));
        else if(naVec_size(val) == 4)
            result = (*node)->setValue(makeVecFromVector<SGVec4d>(val));
        else
            naRuntimeError(c, "props.setValue() vector value has wrong size");
    } else {
        if(!naIsNum(val))
            naRuntimeError(c, "props.setValue() with non-number");

        double d = naNumValue(val).num;
        if (SGMisc<double>::isNaN(d)) {
          naRuntimeError(c, "props.setValue() passed a NaN");
        }

        result = (*node)->setDoubleValue(d);
    }
    return naNum(result);
}

static naRef f_setIntValue(naContext c, naRef me, int argc, naRef* args)
{
    NODEARG();
    MOVETARGET(naVec_size(argv) > 1, true);
    // Original code:
    //   int iv = (int)naNumValue(naVec_get(argv, 0)).num;

    // Junk to pacify the gcc-2.95.3 optimizer:
    naRef tmp0 = naVec_get(argv, 0);
    naRef tmp1 = naNumValue(tmp0);
    if(naIsNil(tmp1))
        naRuntimeError(c, "props.setIntValue() with non-number");
    double tmp2 = tmp1.num;
    int iv = (int)tmp2;

    return naNum((*node)->setIntValue(iv));
}

static naRef f_setBoolValue(naContext c, naRef me, int argc, naRef* args)
{
    NODEARG();
    MOVETARGET(naVec_size(argv) > 1, true);
    naRef val = naVec_get(argv, 0);
    return naNum((*node)->setBoolValue(naTrue(val) ? true : false));
}

static naRef f_setDoubleValue(naContext c, naRef me, int argc, naRef* args)
{
    NODEARG();
    MOVETARGET(naVec_size(argv) > 1, true);
    naRef r = naNumValue(naVec_get(argv, 0));
    if (naIsNil(r))
        naRuntimeError(c, "props.setDoubleValue() with non-number");

    if (SGMisc<double>::isNaN(r.num)) {
      naRuntimeError(c, "props.setDoubleValue() passed a NaN");
    }

    return naNum((*node)->setDoubleValue(r.num));
}


// Get the parent of this node as a ghost.
// Forms:
//    props.Node.getParent();
static naRef f_getParent(naContext c, naRef me, int argc, naRef* args)
{
    NODENOARG();
    SGPropertyNode* n = (*node)->getParent();
    if(!n) return naNil();
    return propNodeGhostCreate(c, n);
}


// Get a child by name and optional index=0, creating if specified (by default it
// does not create it). If the node does not exist and create is false, then it
// returns nil, else it returns a (possibly new) property ghost.
// Forms:
//    props.Node.getChild(string relative_path,
//                        int index=0,
//                        bool create=false);
static naRef f_getChild(naContext c, naRef me, int argc, naRef* args)
{
    NODEARG();
    naRef child = naVec_get(argv, 0);
    if(!naIsString(child)) return naNil();
    naRef idx = naNumValue(naVec_get(argv, 1));
    bool create = naTrue(naVec_get(argv, 2)) != 0;
    SGPropertyNode* n;
    try {
        if(naIsNil(idx)) {
            n = (*node)->getChild(naStr_data(child), create);
        } else {
            n = (*node)->getChild(naStr_data(child), (int)idx.num, create);
        }
    } catch (const string& err) {
        naRuntimeError(c, (char *)err.c_str());
        return naNil();
    }
    if(!n) return naNil();
    return propNodeGhostCreate(c, n);
}


// Get all children with a specified name as a vector of ghosts.
// Forms:
//    props.Node.getChildren(string relative_path);
//    props.Node.getChildren(); #get all children
static naRef f_getChildren(naContext c, naRef me, int argc, naRef* args)
{
    NODEARG();
    naRef result = naNewVector(c);
    if(naIsNil(argv) || naVec_size(argv) == 0) {
        // Get all children
        for(int i=0; i<(*node)->nChildren(); i++)
            naVec_append(result, propNodeGhostCreate(c, (*node)->getChild(i)));
    } else {
        // Get all children of a specified name
        naRef name = naVec_get(argv, 0);
        if(!naIsString(name)) return naNil();
        try {
            vector<SGPropertyNode_ptr> children
                = (*node)->getChildren(naStr_data(name));
            for(unsigned int i=0; i<children.size(); i++)
                naVec_append(result, propNodeGhostCreate(c, children[i]));
        } catch (const string& err) {
            naRuntimeError(c, (char *)err.c_str());
            return naNil();
        }
    }
    return result;
}


// Append a named child at the first unused index...
// Forms:
//    props.Node.addChild(string name,
//                        int min_index=0,
//                        bool append=true);
static naRef f_addChild(naContext c, naRef me, int argc, naRef* args)
{
    NODEARG();
    naRef child = naVec_get(argv, 0);
    if(!naIsString(child)) return naNil();
    naRef ref_min_index = naNumValue(naVec_get(argv, 1));
    naRef ref_append = naVec_get(argv, 2);
    SGPropertyNode* n;
    try
    {
      int min_index = 0;
      if(!naIsNil(ref_min_index))
        min_index = ref_min_index.num;

      bool append = true;
      if(!naIsNil(ref_append))
        append = naTrue(ref_append) != 0;

      n = (*node)->addChild(naStr_data(child), min_index, append);
    }
    catch (const string& err)
    {
      naRuntimeError(c, (char *)err.c_str());
      return naNil();
    }

    return propNodeGhostCreate(c, n);
}

static naRef f_addChildren(naContext c, naRef me, int argc, naRef* args)
{
    NODEARG();
    naRef child = naVec_get(argv, 0);
    if(!naIsString(child)) return naNil();
    naRef ref_count = naNumValue(naVec_get(argv, 1));
    naRef ref_min_index = naNumValue(naVec_get(argv, 2));
    naRef ref_append = naVec_get(argv, 3);
    try
    {
      size_t count = 0;
      if( !naIsNum(ref_count) )
        throw string("props.addChildren() missing number of children");
      count = ref_count.num;

      int min_index = 0;
      if(!naIsNil(ref_min_index))
        min_index = ref_min_index.num;

      bool append = true;
      if(!naIsNil(ref_append))
        append = naTrue(ref_append) != 0;

      const simgear::PropertyList& nodes =
        (*node)->addChildren(naStr_data(child), count, min_index, append);

      naRef result = naNewVector(c);
      for( size_t i = 0; i < nodes.size(); ++i )
        naVec_append(result, propNodeGhostCreate(c, nodes[i]));
      return result;
    }
    catch (const string& err)
    {
      naRuntimeError(c, (char *)err.c_str());
    }

    return naNil();
}


// Remove a child by name and index. Returns it as a ghost.
// Forms:
//    props.Node.removeChild(string relative_path,
//                           int index);
static naRef f_removeChild(naContext c, naRef me, int argc, naRef* args)
{
    NODEARG();
    naRef child = naVec_get(argv, 0);
    naRef index = naVec_get(argv, 1);
    if(!naIsString(child) || !naIsNum(index)) return naNil();
    SGPropertyNode_ptr n = 0;
    try {
        n = (*node)->removeChild(naStr_data(child), (int)index.num, false);
    } catch (const string& err) {
        naRuntimeError(c, (char *)err.c_str());
    }
    return propNodeGhostCreate(c, n);
}


// Remove all children with specified name. Returns a vector of all the nodes
// removed as ghosts.
// Forms:
//    props.Node.removeChildren(string relative_path);
//    props.Node.removeChildren(); #remove all children
static naRef f_removeChildren(naContext c, naRef me, int argc, naRef* args)
{
    NODEARG();
    naRef result = naNewVector(c);
    if(naIsNil(argv) || naVec_size(argv) == 0) {
        // Remove all children
        for(int i = (*node)->nChildren() - 1; i >=0; i--)
            naVec_append(result, propNodeGhostCreate(c, (*node)->removeChild(i, false)));
    } else {
        // Remove all children of a specified name
        naRef name = naVec_get(argv, 0);
        if(!naIsString(name)) return naNil();
        try {
            vector<SGPropertyNode_ptr> children
                = (*node)->removeChildren(naStr_data(name), false);
            for(unsigned int i=0; i<children.size(); i++)
                naVec_append(result, propNodeGhostCreate(c, children[i]));
        } catch (const string& err) {
            naRuntimeError(c, (char *)err.c_str());
            return naNil();
        }
    }
    return result;
}


// Alias this property to another one; returns 1 on success or 0 on failure
// (only applicable to tied properties).
// Forms:
//    props.Node.alias(string global_path);
//    props.Node.alias(prop_ghost node);
//    props.Node.alias(props.Node node); #added by props.nas
static naRef f_alias(naContext c, naRef me, int argc, naRef* args)
{
    NODEARG();
    SGPropertyNode* al;
    naRef prop = naVec_get(argv, 0);
    try {
        if(naIsString(prop)) al = globals->get_props()->getNode(naStr_data(prop), true);
        else if(naIsGhost(prop)) al = *(SGPropertyNode_ptr*)naGhost_ptr(prop);
        else throw string("props.alias() with bad argument");
    } catch (const string& err) {
        naRuntimeError(c, (char *)err.c_str());
        return naNil();
    }
    return naNum((*node)->alias(al));
}


// Un-alias this property. Returns 1 on success or 0 on failure (only
// applicable to tied properties).
// Forms:
//    props.Node.unalias();
static naRef f_unalias(naContext c, naRef me, int argc, naRef* args)
{
    NODENOARG();
    return naNum((*node)->unalias());
}


// Get the alias of this node as a ghost.
// Forms:
//    props.Node.getAliasTarget();
static naRef f_getAliasTarget(naContext c, naRef me, int argc, naRef* args)
{
    NODENOARG();
    return propNodeGhostCreate(c, (*node)->getAliasTarget());
}


// Get a relative node. Returns nil if it does not exist and create is false,
// or a ghost object otherwise (wrapped into a props.Node object by props.nas).
// Forms:
//    props.Node.getNode(string relative_path,
//                       bool create=false);
static naRef f_getNode(naContext c, naRef me, int argc, naRef* args)
{
    NODEARG();
    naRef path = naVec_get(argv, 0);
    bool create = naTrue(naVec_get(argv, 1)) != 0;
    if(!naIsString(path)) return naNil();
    SGPropertyNode* n;
    try {
        n = (*node)->getNode(naStr_data(path), create);
    } catch (const string& err) {
        naRuntimeError(c, (char *)err.c_str());
        return naNil();
    }
    return propNodeGhostCreate(c, n);
}


// Create a new property node.
// Forms:
//    props.Node.new();
static naRef f_new(naContext c, naRef me, int argc, naRef* args)
{
    return propNodeGhostCreate(c, new SGPropertyNode());
}


// Get the global root node (cached by props.nas so that it does
// not require a function call).
// Forms:
//    props._globals()
//    props.globals
static naRef f_globals(naContext c, naRef me, int argc, naRef* args)
{
    return propNodeGhostCreate(c, globals->get_props());
}

static struct {
    naCFunction func;
    const char* name;
} propfuncs[] = {
    { f_getType,        "_getType"        },
    { f_getAttribute,   "_getAttribute"   },
    { f_setAttribute,   "_setAttribute"   },
    { f_getName,        "_getName"        },
    { f_getIndex,       "_getIndex"       },
    { f_getValue,       "_getValue"       },
    { f_setValue,       "_setValue"       },
    { f_setIntValue,    "_setIntValue"    },
    { f_setBoolValue,   "_setBoolValue"   },
    { f_setDoubleValue, "_setDoubleValue" },
    { f_getParent,      "_getParent"      },
    { f_getChild,       "_getChild"       },
    { f_getChildren,    "_getChildren"    },
    { f_addChild,       "_addChild"       },
    { f_addChildren,    "_addChildren"    },
    { f_removeChild,    "_removeChild"    },
    { f_removeChildren, "_removeChildren" },
    { f_alias,          "_alias"          },
    { f_unalias,        "_unalias"        },
    { f_getAliasTarget, "_getAliasTarget" },
    { f_getNode,        "_getNode"        },
    { f_new,            "_new"            },
    { f_globals,        "_globals"        },
    { 0, 0 }
};

naRef FGNasalSys::genPropsModule()
{
    naRef namespc = naNewHash(_context);
    for(int i=0; propfuncs[i].name; i++)
        hashset(namespc, propfuncs[i].name,
                naNewFunc(_context, naNewCCode(_context, propfuncs[i].func)));
    return namespc;
}
Philosopher
 
Posts: 1593
Joined: Sun Aug 12, 2012 7:29 pm

Re: Relative paths for Nasal props.Node methods

Postby Philosopher » Sun Apr 14, 2013 11:50 pm

Thomas Geymayer wrote:
James Turner wrote:> Extend Nasal property methods to support relative paths.

> All methods exposed to the Nasal props module now accept an
> optional first parameter refering to another node by a relative
> path.

I'm a bit torn on this change; making APIs more flexible is usually a
good thing.

However, the cost of property path parsing has previously shown up in
profilers as a source of dynamic allocations and overhead.


I haven't looked at it in detail, but why are dynamic allocations needed
for parsing a property path? If this is the case we should to some
profiling and rework it if needed.

James Turner wrote:Historically, on the C++ side; I can believe that in Nasal the extra
parsing overhead is lost in general Nasal overhead, but still, the
idea of the existing API was to force you to design & work 'the right
way', i.e cache leaf leaf nodes as objects, instead of repeated
referring to leaves by a relative path.

If you just want a quick and dirty way to read one property, we have
getprop / setprop - for any other use, you should be doing what the
API already supported; getting a props.Node object for the leaf, and
then manipulating it with no further string/path handling. At least,
I would prefer that getValue and setValue did not support this, since
it will surely encourage convenient-but-inefficient styles of use.

(I would also be happy to extend props.nas with helpers to make the
above syntactically more compact, but I think such sugar can all be
done on the Nasal side)


One thing I thing I want to achieve with this changes is to make the
Nasal props API more similar to its C++ counterpart as this makes it
easier to use if you are using both the C++ and the Nasal API.

Also someday I want to refactor nasal-props.cpp to use cppbind, where I
want to export as much methods as possible with exactly the same
signature than in C++.

Especially if using properties seldom (eg. only for initialization) the
relative versions are probably even faster, as the Nasal overhead is
lower. Eg. consider the following Nasal code used to initialize some module:

var cfg = props.globals.getNode("/my/config/root", 1);
var x = cfg.getDoubleValue("x");
var do_it = cfg.getBoolValue("do_it");

Using getprop on the one hand does not allow getting a property
converted to a given type and on the other hand is tedious to use for
more than one property, as one has to assemble the according property
paths (which is definitely less efficient than using a relative method).

Tom


Hi guys, (sorry to split this across mediums)

This is a valid concern, James, and this is definitely something to be mentioned where we can in the documentation: props.nas and wiki. I can believe that profiling verifies your result :). @Thomas: I think the dynamic allocation is from the fact that (boost?) iterators are used to split the path into parts. There is a call to make_iterator_range which I do not understand but it seems to parse it into whatever form, and then a lot of subsequent dealings with that form (under typedef <typename Range> usually). See props.cxx find_node and getNode. I don't know enough C++ to say if parsing it on our own would improve it any, but I would say that it should be simple enough and remove any dynamic allocations.

Yes I do agree that it can encourage bad habits, but I also believe that almost all use cases will involve looking up a single node and caching the immediate parent -- i.e. me.rootN.getValue("whatever") versus me.aboveRootN.getNode("F-18/arms/missle/whatever") -- so perhaps profiling a path containing no slashes could shed some more light on the issue? If you have a proposal that addresses efficiency or syntactic ease, I would definitely love to hear about it!
Philosopher
 
Posts: 1593
Joined: Sun Aug 12, 2012 7:29 pm

Re: Relative paths for Nasal props.Node methods

Postby Hooray » Mon Apr 15, 2013 3:49 am

Devel list thread: http://www.mail-archive.com/flightgear- ... 39838.html

And just to provide all the feedback in the place where the patch/discussion originally started, here's the message that I sent earlier to TheTom & Zakalawe:
Hooray wrote:
If you just want a quick and dirty way to read one property, we have getprop /
setprop - for any other use, you should be doing what the API already
supported; getting a props.Node object for the leaf, and then manipulating it
with no further string/path handling. At least, I would prefer that getValue
and setValue did not support this, since it will surely encourage
convenient-but-inefficient styles of use.


FWIW, getprop/setprop are known for not just being "quick & dirty", but also for outperforming the props.nas magic performance-wise, including the Nasal ghosts, which is plausible - because of the API and stack machine overhead. Andy himself repeatedly mentioned that several times in the past, e.g. see: http://www.mail-archive.com/flightgear- ... 12222.html

Also, Thorsten has previously run benchmarks to compare props.nas vs. setprop/getprop.
Like Tom mentioned previously, it would probably make sense to eventually port the whole props.nas stuff to use cppbind and then proceed from there, that should leave little Nasal-space overhead, and once everything is in C++ space, it will be straightforward to see how to optimize things there using the google perftools fgcommands.

Performance-wise, many fgcommands and Nasal extension functions show up - even without necessarily invoking the GC. And the constant string building is a known issue, but there are certainly workarounds or special-case optimizations possible, even if it's just a simple space/time tradeoff in the form of a cached property tree.

Performance-wise, the poco/boost property tree implementation is likely to outperform ours, and Mathias repeatedly mentioned that he has plans to extend the original property tree code anyhow.

Also, it should not be too complicated to directly wrap the PropertyObject<> stuff to be supported by Nasal - which removes another layer of indirection and could be optimized separately.
Please don't send support requests by PM, instead post your questions on the forum so that all users can contribute and benefit
Thanks & all the best,
Hooray
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Hooray
 
Posts: 12707
Joined: Tue Mar 25, 2008 9:40 am
Pronouns: THOU

Re: Relative paths for Nasal props.Node methods

Postby Philosopher » Wed Apr 17, 2013 8:06 pm

So you are saying that we should not provide the wrappers in props.nas (which are naCodes) and instead directly port the C++ extensions (naCCodes) to live directly in the object? That could be done using the naRef me and a hash lookup for "_g" (which should be interned and/or cached just like arg and parents are). I'm not sure I understand your last paragraph though...

Here's an alternative to string concatenation (for FGNasalSys.cxx of course):

Code: Select all
// The get/setprop functions accept a *list* of string and numbers and
// walks through the property tree with them to find the appropriate node.
// This allows a Nasal object to hold onto a property path and use it
// like a node object, e.g. setprop(ObjRoot, "size-parsecs", 2.02).  This
// is the utility function that walks the property tree.
static SGPropertyNode* findnode(naContext c, naRef* vec, int len, bool create)
{
    SGPropertyNode* p = globals->get_props();
    try {
        for(int i=0; i<len; i++) {
            naRef a = vec[i];
            if(naIsString(a)) {
                if(i < len-1 && naIsNum(vec[i+1])
                    p = p->getNode(naStr_data(a), (int)vec[++i].num, create);
                else
                    p = p->getNode(naStr_data(a), create);
                if(p == 0) return 0;
            } else { return 0; }
        }
    } catch (const string& err) {
        naRuntimeError(c, (char *)err.c_str());
        return 0;
    }
    return p;
}

// getprop() extension function.  Concatenates its string arguments as
// property names and returns the value of the specified property or
// nil if it doesn't exist.
static naRef f_getprop(naContext c, naRef me, int argc, naRef* args)
{
    using namespace simgear;
    const SGPropertyNode* p = findnode(c, args, argc, false);
    if(!p) return naNil();

    switch(p->getType()) {
    case props::BOOL:   case props::INT:
    case props::LONG:   case props::FLOAT:
    case props::DOUBLE:
        {
        double dv = p->getDoubleValue();
        if (SGMisc<double>::isNaN(dv)) {
          SG_LOG(SG_NASAL, SG_ALERT, "Nasal getprop: property " << p->getPath() << " is NaN");
          return naNil();
        }
        
        return naNum(dv);
        }
        
    case props::STRING:
    case props::UNSPECIFIED:
        {
            naRef nastr = naNewString(c);
            const char* val = p->getStringValue();
            naStr_fromdata(nastr, (char*)val, strlen(val));
            return nastr;
        }
    case props::ALIAS: // <--- FIXME, recurse?
    default:
        return naNil();
    }
}

// setprop() extension function.  Concatenates its string arguments as
// property names and sets the value of the specified property to the
// final argument.
static naRef f_setprop(naContext c, naRef me, int argc, naRef* args)
{
    const SGPropertyNode* p = findnode(c, args, argc-1, true);
    naRef val = args[argc-1];
    bool result = false;
    try {
        if(naIsString(val)) result = p->setStringValue(naStr_data(val));
        else {
            if(!naIsNum(n))
                naRuntimeError(c, "setprop() value is not string or number");
                
            if (SGMisc<double>::isNaN(n.num)) {
                naRuntimeError(c, "setprop() passed a NaN");
            }
            
            result = p->setDoubleValue(n.num);
        }
    } catch (const string& err) {
        naRuntimeError(c, (char *)err.c_str());
    }
    return naNum(result);
}


Could anyone perf it to see if it speeds up setprop/getprop? I was thinking of a simple case like «"/engines/engine",1,"mixture"» versus «"/engines/engine["~1~"]/mixture"», since that would be IMO the most common usage (a single index thrown in a path).
Philosopher
 
Posts: 1593
Joined: Sun Aug 12, 2012 7:29 pm


Return to Development

Who is online

Users browsing this forum: No registered users and 6 guests