-///@file Expose C++ objects to Nasal as ghosts
-//
+///@file
+/// Expose C++ objects to Nasal as ghosts
+///
// Copyright (C) 2012 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
#include <simgear/debug/logstream.hxx>
#include <boost/bind.hpp>
+#include <boost/call_traits.hpp>
#include <boost/function.hpp>
#include <boost/lambda/lambda.hpp>
#include <boost/utility/enable_if.hpp>
+#include <map>
+
+/**
+ * Bindings between C++ and the Nasal scripting language
+ */
namespace nasal
{
template<class T>
struct SharedPointerPolicy
{
- typedef typename GhostTypeTraits<T>::raw_type raw_type;
+ typedef typename GhostTypeTraits<T>::raw_type raw_type;
+ typedef T pointer;
+ typedef boost::false_type returns_dynamic_type;
/**
* Create a shared pointer on the heap to handle the reference counting for
return new T(ptr);
}
+ static pointer getPtr(void* ptr)
+ {
+ if( ptr )
+ return *static_cast<T*>(ptr);
+ else
+ return pointer();
+ }
+
static raw_type* getRawPtr(void* ptr)
{
- return static_cast<T*>(ptr)->get();
+ if( ptr )
+ return static_cast<T*>(ptr)->get();
+ else
+ return 0;
+ }
+
+ static raw_type* getRawPtr(const T& ptr)
+ {
+ return ptr.get();
}
};
template<class T>
struct RawPointerPolicy
{
- typedef typename GhostTypeTraits<T>::raw_type raw_type;
+ typedef typename GhostTypeTraits<T>::raw_type raw_type;
+ typedef raw_type* pointer;
+ typedef boost::true_type returns_dynamic_type;
/**
* Create a new object instance on the heap
return new T();
}
+ static pointer getPtr(void* ptr)
+ {
+ BOOST_STATIC_ASSERT((boost::is_same<pointer, T*>::value));
+ return static_cast<T*>(ptr);
+ }
+
static raw_type* getRawPtr(void* ptr)
{
BOOST_STATIC_ASSERT((boost::is_same<raw_type, T>::value));
_parents.push_back(parent);
}
+ bool isBaseOf(naGhostType* ghost_type) const
+ {
+ if( ghost_type == &_ghost_type )
+ return true;
+
+ for( DerivedList::const_iterator derived = _derived_classes.begin();
+ derived != _derived_classes.end();
+ ++derived )
+ {
+ if( (*derived)->isBaseOf(ghost_type) )
+ return true;
+ }
+
+ return false;
+ }
+
protected:
- const std::string _name;
- naGhostType _ghost_type;
- // std::vector<GhostMetadata*> _base_classes;
- std::vector<naRef> _parents;
+
+ typedef std::vector<const GhostMetadata*> DerivedList;
+
+ const std::string _name;
+ naGhostType _ghost_type;
+ DerivedList _derived_classes;
+ std::vector<naRef> _parents;
explicit GhostMetadata(const std::string& name):
_name(name)
}
- // void addBaseClass(GhostMetadata* base)
- // {
- // assert(base);
- // _base_classes.push_back(base);
- // }
+ void addDerived(const GhostMetadata* derived)
+ {
+ assert(derived);
+ _derived_classes.push_back(derived);
+
+ SG_LOG
+ (
+ SG_NASAL,
+ SG_INFO,
+ "Ghost::addDerived: " <<_ghost_type.name << " -> " << derived->_name
+ );
+ }
naRef getParents(naContext c)
{
};
}
+ /**
+ * Context passed to a function/method being called from Nasal
+ */
+ struct CallContext
+ {
+ CallContext(naContext c, size_t argc, naRef* args):
+ c(c),
+ argc(argc),
+ args(args)
+ {}
+
+ /**
+ * Get the argument with given index if it exists. Otherwise returns the
+ * passed default value.
+ *
+ * @tparam T Type of argument (converted using ::from_nasal)
+ * @param index Index of requested argument
+ * @param def Default value returned if too few arguments available
+ */
+ template<class T>
+ T getArg(size_t index, const T& def = T()) const
+ {
+ if( index >= argc )
+ return def;
+
+ return from_nasal<T>(c, args[index]);
+ }
+
+ /**
+ * Get the argument with given index. Raises a Nasal runtime error if there
+ * are to few arguments available.
+ */
+ template<class T>
+ T requireArg(size_t index) const
+ {
+ if( index >= argc )
+ naRuntimeError(c, "Missing required arg #%d", index);
+
+ return from_nasal<T>(c, args[index]);
+ }
+
+ naContext c;
+ size_t argc;
+ naRef *args;
+ };
+
/**
* Class for exposing C++ objects to Nasal
*
* void setX(int x);
* int getX() const;
*
- * naRef myMember(int argc, naRef* args);
+ * naRef myMember(naContext c, int argc, naRef* args);
* }
*
* void exposeClasses()
RawPointerPolicy<T> >::type
{
public:
- typedef typename GhostTypeTraits<T>::raw_type raw_type;
- typedef naRef (T::*member_func_t)(int, naRef*);
- typedef boost::function<naRef(naContext c, raw_type*)> getter_t;
- typedef boost::function<void(naContext c, raw_type*, naRef)> setter_t;
+ typedef T value_type;
+ typedef typename GhostTypeTraits<T>::raw_type raw_type;
+ typedef typename Ghost::pointer pointer;
+ typedef naRef (raw_type::*member_func_t)(const CallContext&);
+ typedef naRef (*free_func_t)(raw_type&, const CallContext&);
+ typedef boost::function<naRef(naContext, raw_type&)> getter_t;
+ typedef boost::function<void(naContext, raw_type&, naRef)> setter_t;
/**
* A ghost member. Can consist either of getter and/or setter functions
/**
* Register a new ghost type.
*
+ * @note Only intialize each ghost type once!
+ *
* @param name Descriptive name of the ghost type.
*/
static Ghost& init(const std::string& name)
{
+ assert( !getSingletonPtr() );
+
getSingletonHolder().reset( new Ghost(name) );
return *getSingletonPtr();
}
bases()
{
BaseGhost* base = BaseGhost::getSingletonPtr();
- //addBaseClass( base );
+ base->addDerived
+ (
+ this,
+ // Both ways of retrieving the address of a static member function
+ // should be legal but not all compilers know this.
+ // g++-4.4.7+ has been tested to work with both versions
+#if defined(GCC_VERSION) && GCC_VERSION < 40407
+ // The old version of g++ used on Jenkins (16.11.2012) only compiles
+ // this version.
+ &getTypeFor<BaseGhost>
+#else
+ // VS (2008, 2010, ... ?) only allow this version.
+ &Ghost::getTypeFor<BaseGhost>
+#endif
+ );
// Replace any getter that is not available in the current class.
// TODO check if this is the correct behavior of function overriding
template<class Var>
Ghost& member( const std::string& field,
Var (raw_type::*getter)() const,
- void (raw_type::*setter)(Var) = 0 )
+ void (raw_type::*setter)(typename boost::call_traits<Var>::param_type) = 0 )
{
member_t m;
if( getter )
{
- naRef (*to_nasal_)(naContext, Var) = &nasal::to_nasal;
+ typedef typename boost::call_traits<Var>::param_type param_type;
+ naRef (*to_nasal_)(naContext, param_type) = &nasal::to_nasal;
- // Getter signature: naRef(naContext, raw_type*)
+ // Getter signature: naRef(naContext, raw_type&)
m.getter = boost::bind(to_nasal_, _1, boost::bind(getter, _2));
}
{
Var (*from_nasal_)(naContext, naRef) = &nasal::from_nasal;
- // Setter signature: void(naContext, raw_type*, naRef)
+ // Setter signature: void(naContext, raw_type&, naRef)
m.setter = boost::bind(setter, _2, boost::bind(from_nasal_, _1, _3));
}
* class MyClass
* {
* public:
- * naRef myMethod(int argc, naRef* args);
+ * naRef myMethod(naContext c, int argc, naRef* args);
* }
*
* Ghost<MyClass>::init("Test")
return *this;
}
+ /**
+ * Register a free function as member function. The object instance is
+ * passed as additional first argument.
+ *
+ * @tparam func Pointer to free function being registered.
+ *
+ * @note Due to a severe bug in Visual Studio it is not possible to create
+ * a specialization of #method for free function pointers and
+ * member function pointers at the same time. Do overcome this
+ * limitation we had to use a different name for this function.
+ *
+ * @code{cpp}
+ * class MyClass;
+ * naRef myMethod(MyClass& obj, naContext c, int argc, naRef* args);
+ *
+ * Ghost<MyClass>::init("Test")
+ * .method_func<&myMethod>("myMethod");
+ * @endcode
+ */
+ template<free_func_t func>
+ Ghost& method_func(const std::string& name)
+ {
+ _members[name].func = &FreeFunctionWrapper<func>::call;
+ return *this;
+ }
+
// TODO use variadic template when supporting C++11
/**
* Create a Nasal instance of this ghost.
return create(c);
}
+ static bool isBaseOf(naGhostType* ghost_type)
+ {
+ if( !ghost_type )
+ return false;
+
+ return getSingletonPtr()->GhostMetadata::isBaseOf(ghost_type);
+ }
+
+ static bool isBaseOf(naRef obj)
+ {
+ return isBaseOf( naGhost_type(obj) );
+ }
+
+ /**
+ * Convert Nasal object to C++ object. To get a valid object the passed
+ * Nasal objects has to be derived class of the target class (Either
+ * derived in C++ or in Nasal using a 'parents' vector)
+ */
+ static pointer fromNasal(naContext c, naRef me)
+ {
+ // Check if it's a ghost and if it can be converted
+ if( isBaseOf( naGhost_type(me) ) )
+ return Ghost::getPtr( naGhost_ptr(me) );
+
+ // Now if it is derived from a ghost (hash with ghost in parent vector)
+ else if( naIsHash(me) )
+ {
+ naRef na_parents = naHash_cget(me, const_cast<char*>("parents"));
+ if( !naIsVector(na_parents) )
+ {
+ SG_LOG(SG_NASAL, SG_DEBUG, "Missing 'parents' vector for ghost");
+ return pointer();
+ }
+
+ typedef std::vector<naRef> naRefs;
+ naRefs parents = from_nasal<naRefs>(c, na_parents);
+ for( naRefs::const_iterator parent = parents.begin();
+ parent != parents.end();
+ ++parent )
+ {
+ pointer ptr = fromNasal(c, *parent);
+ if( ptr )
+ return ptr;
+ }
+ }
+
+ return pointer();
+ }
+
private:
template<class>
friend class Ghost;
+ typedef naGhostType* (*type_checker_t)(const raw_type*);
+ typedef std::vector<type_checker_t> DerivedList;
+ DerivedList _derived_types;
+
+ void addDerived( const internal::GhostMetadata* derived_meta,
+ const type_checker_t& derived_info )
+ {
+ GhostMetadata::addDerived(derived_meta);
+ _derived_types.push_back(derived_info);
+ }
+
+ template<class BaseGhost>
+ static
+ typename boost::enable_if
+ < boost::is_polymorphic<typename BaseGhost::raw_type>,
+ naGhostType*
+ >::type
+ getTypeFor(const typename BaseGhost::raw_type* base)
+ {
+ // Check first if passed pointer can by converted to instance of class
+ // this ghost wraps.
+ if( !boost::is_same
+ < typename BaseGhost::raw_type,
+ typename Ghost::raw_type
+ >::value
+ && dynamic_cast<const typename Ghost::raw_type*>(base) != base )
+ return 0;
+
+ // Now check if we can further downcast to one of our derived classes.
+ for( typename DerivedList::reverse_iterator
+ derived = getSingletonPtr()->_derived_types.rbegin();
+ derived != getSingletonPtr()->_derived_types.rend();
+ ++derived )
+ {
+ naGhostType* ghost_type =
+ (*derived)( static_cast<const typename Ghost::raw_type*>(base) );
+ if( ghost_type )
+ return ghost_type;
+ }
+
+ // If base is not an instance of any derived class, this class has to
+ // be the dynamic type.
+ return &getSingletonPtr()->_ghost_type;
+ }
+
+ template<class BaseGhost>
+ static
+ typename boost::disable_if
+ < boost::is_polymorphic<typename BaseGhost::raw_type>,
+ naGhostType*
+ >::type
+ getTypeFor(const typename BaseGhost::raw_type* base)
+ {
+ // For non polymorphic classes there is no possibility to get the actual
+ // dynamic type, therefore we can only use its static type.
+ return &BaseGhost::getSingletonPtr()->_ghost_type;
+ }
+
static Ghost* getSingletonPtr()
{
return getSingletonHolder().get();
}
+ static raw_type& requireObject(naContext c, naRef me)
+ {
+ raw_type* obj = Ghost::getRawPtr( fromNasal(c, me) );
+ naGhostType* ghost_type = naGhost_type(me);
+
+ if( !obj )
+ naRuntimeError
+ (
+ c,
+ "method called on object of wrong type: is '%s' expected '%s'",
+ ghost_type ? ghost_type->name : "unknown",
+ getSingletonPtr()->_ghost_type.name
+ );
+
+ return *obj;
+ }
+
/**
* Wrapper class to enable registering pointers to member functions as
* Nasal function callbacks. We need to use the function pointer as
*/
static naRef call(naContext c, naRef me, int argc, naRef* args)
{
- if( naGhost_type(me) != &getSingletonPtr()->_ghost_type )
- naRuntimeError
- (
- c,
- "method called on object of wrong type: '%s' expected",
- getSingletonPtr()->_ghost_type.name
- );
-
- raw_type* obj = Ghost::getRawPtr( static_cast<T*>(naGhost_ptr(me)) );
- assert(obj);
+ return (requireObject(c, me).*func)(CallContext(c, argc, args));
+ }
+ };
- return (obj->*func)(argc, args);
+ /**
+ * Wrapper class to enable registering pointers to free functions (only
+ * external linkage). We need to use the function pointer as template
+ * parameter to ensure every registered function gets a static function
+ * which can be passed to Nasal. Even though we just wrap another simple
+ * function pointer this intermediate step is need to be able to retrieve
+ * the object the function call belongs to and pass it along as argument.
+ */
+ template<free_func_t func>
+ struct FreeFunctionWrapper
+ {
+ /**
+ * Called from Nasal upon invocation of the according registered
+ * function. Forwards the call to the passed function pointer and passes
+ * the required parameters.
+ */
+ static naRef call(naContext c, naRef me, int argc, naRef* args)
+ {
+ return func(requireObject(c, me), CallContext(c, argc, args));
}
};
static naRef makeGhost(naContext c, void *ptr)
{
- return naNewGhost2(c, &getSingletonPtr()->_ghost_type, ptr);
+ if( !Ghost::getRawPtr(ptr) )
+ return naNil();
+
+ naGhostType* ghost_type = 0;
+ if( Ghost::returns_dynamic_type::value )
+ // For pointer policies already returning instances of an object with
+ // the dynamic type of this Ghost's raw_type the type is always the
+ // same.
+ ghost_type = &getSingletonPtr()->_ghost_type;
+ else
+ // If wrapping eg. shared pointers the users passes an already
+ // existing instance of an object which will then be hold be a new
+ // shared pointer. We therefore have to check for the dynamic type
+ // of the object as it might differ from the passed static type.
+ ghost_type = getTypeFor<Ghost>( Ghost::getRawPtr(ptr) );
+
+ if( !ghost_type )
+ return naNil();
+
+ return naNewGhost2(c, ghost_type, ptr);
}
static void destroyGhost(void *ptr)
if( member->second.func )
*out = nasal::to_nasal(c, member->second.func);
else if( !member->second.getter.empty() )
- *out = member->second.getter(c, Ghost::getRawPtr(g));
+ *out = member->second.getter(c, *Ghost::getRawPtr(g));
else
return "Read-protected member";
if( member->second.setter.empty() )
naRuntimeError(c, "ghost: Write protected member: %s", key.c_str());
- member->second.setter(c, Ghost::getRawPtr(g), val);
+ member->second.setter(c, *Ghost::getRawPtr(g), val);
}
};