Menu

Sqext Log in to Edit

Wizzard James Lomax

Sqext

This is just a simple extension to Sqrat I threw together today to make management of script defined classes simpler. There's a lot of documentation within the source on usage.

The premise of the library is fairly simple; Sqrat is an excellent tool which allows you to expose C++ classes to scripts, however it lacks functionality to expose squirrel classes to C++. This extension provides a little extra functionality required to do this. Primarily it simply wraps squirrel classes and allows you to bind handles to their members. You can then use it to instantiate a squirrel class from C++ and subsequently access it's members quickly and efficiently.

To use it in a project simply copy the entire contents of the code into a header file and put it somewhere - anywhere! I recommend saving it as sqext.h in the Sqrat include root.

Relevant thread of the coming about of this: http://forum.squirrel-lang.org/default.aspx?g=posts&m=7260&#post7260

An example to outline most of the features is below

/James Lomax

Squirrel script for the example

class Thing {
    num = 0;

    constructor(n) {
        num = n;
    }

    function printer() {
        ::print("Number = " + num + "\n");
    }

    function set(n) {
        num = n;
    }

    function get() {
        return num;
    }

    function offset(n) {
        return num + n;
    }
}

C++ code for the example

DefaultVM::Set(vm);

// ...
// Compile and run script
// ...

sqext::SQIClass thingClass("Thing");
thingClass.bind(0, "num");
thingClass.bind(1, "printer");
thingClass.bind(2, "set");
thingClass.bind(3, "get");
thingClass.bind(4, "offset");

sqext::SQIClassInstance thingy = thingClass.New(59);
thingy.call(1);
thingy.call(2, 69);
assert(thingy.callr<int>(3) == thingy.callr<int>("get"));
int offsetted = thingy.callr<int>(4, 42);

int num = thingy.get(0).Cast<int>();
num *= 1000;
thingy.set(0, num);

assert(thingy.cast<int>(0) == thingy.cast<int>("num"));

thingy.call("printer");

The actual code

/*
 * Copyright (c) 2013 James Lomax (javajames64 @ http://forum.squirrel-lang.org/)
 *
 * This software is provided 'as-is', without any express or implied
 * warranty. In no event will the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 * claim that you wrote the original software. If you use this software
 * in a product, an acknowledgment in the product documentation would be
 * appreciated but is not required.
 *
 * 2. Altered source versions must be plainly marked as such, and must not be
 * misrepresented as being the original software.
 *
 * 3. This notice may not be removed or altered from any source
 * distribution.
 */

#ifndef __SQUIRREL_EXTENSION_HEADER__
#define __SQUIRREL_EXTENSION_HEADER__

#include <vector>
#include <string.h>

#include "squirrel.h"
#include "sqrat.h"

/**
 * @brief Sqrat extensions.
 *
 * <pre>
 *  - Squirrel class management
 *  - Variadic templates (sqrats endless copy&paste overloading of functions for
 * parameters is a little messy IMO.)
 *
 * Usage at a glance:
 *  - <VM> is the squirrel VM argument. Use Sqrat::DefaultVM::Set(<VM>) and this
 *  argument can be ignored
 *  - Create a class type from types defined in squirrel:
 *      SQIClass myClass(<classname in root table>, <VM>);
 *      SQIClass myClass(<sqrat object of this class>, <VM>);
 *
 *      EG:
 *          SQIClass myClass("Entity");
 *          SQIClass myClass(RootTable().GetSlot("Entity"));
 *
 *  - Bind a member to a handle lookup within the class:
 *      myClass.bind(<handle index>, <member name>);
 *      <handle index> = SQIClass::bind(<member name>);
 *
 *      EG:
 *          myClass.bind(0, "move");
 *          myClass.bind("move2") == 1;
 *
 *      Specifying the index manually will expand the handle lookup with nulls to cover it.
 *
 *  - Instantiate a new class in C++ from the class:
 *      <SQIClassInstance> = SQIClass::New(<constructor parameters>);
 *
 *      EG:
 *          SQIClassIndex object = myClass.New(5, 6, 7);
 *
 *  - Create a class instance from an existing Sqrat::Object:
 *      <SQIClassInstance> = SQIClass::cast(<Sqrat::Object>);
 *
 *      EG:
 *          SQIClassInstance object = myClass.cast(RootTable().GetSlot("anEntity"));
 *
 *  - Get the Sqrat::Object from a class instance:
 *      <Sqrat::Object> = SQIClassInstance::get();
 *
 *      EG:
 *          Sqrat::Object theEntity = object.get();
 *
 *  - Get the Sqrat::Object from a given handle or key:
 *      <Sqrat::Object> = SQIClassInstance::get(<handle index>);
 *      <Sqrat::Object> = SQIClassInstance::get(<key>);
 *
 *  - Get and cast the object from a given handle or key:
 *      <T> = SQIClassInstance::cast<T>(<handle index>);
 *      <T> = SQIClassInstance::cast<T>(<key>);
 *
 *      EG:
 *          int x = object.cast<int>(4);
 *          int x = object.cast<int>("xpos");
 *
 *  - Set a value or object to a given handle or key:
 *      SQIClassInstance::set(<handle index>, <value>);
 *      SQIClassInstance::set(<key>, <value>);
 *
 *  - Note that key lookup is slower than handle lookup, so as much as possible use pre-bound handles.
 *
 *  - Call the function at a given index:
 *      SQIClassInstance::call(<handle index>);
 *      SQIClassInstance::call(<handle index>, arguments...);
 *      <T> = SQIClassInstance::callr<T>(<handle index>);
 *      <T> = SQIClassInstance::callr<T>(<handle index>, arguments...);
 *
 *      EG:
 *          object->call(0);
 *          object->call(0, blah, blah, blah); //Any squirrel types accepted
 *          int i = object.call<int>(0);
 *          int i = object.call<int>(0, blah, blah, blah);
 *  - Objects and functions by string name:
 *      - Objects and functions by string value should be retrieved from
 *      the Sqrat::Object.
 *      - The SQIClassInstance::get() function will retrieve the Sqrat::Object
 *      cast of the sqext::SQIClassInstance:
 *          Sqrat::Object asObject = object->get();
 *          //From this the Sqrat functionality can be used
 *          asObject.GetSlot("member");
 *      - There are a few convenience functions provided for ease of reading:
 *          SQIClassInstance::get(<key>) -> SQIClassInstance::get().GetSlot(<key>);
 *          SQIClassInstance::cast<T>(<key>) -> SQIClassInstance::get().GetSlot(<key>).Cast<T>();
 *          SQIClassInstance::call(<key>, <args...>) -> Sqrat::Function(SQIClassInstance::get(), <key>)(<args...>);
 * </pre>
 */
namespace sqext {

/**
 * @brief Variadic argument processor.
 * @param vm    Squirrel VM
 * @param arg   First argument for processing
 * @tparam Arg  Type of the first argument
 * @return Argument number
 */
template <class Arg>
inline int pushArgs(HSQUIRRELVM vm, Arg arg) {
    Sqrat::PushVar(vm, arg);
    return 1;
}

/**
 * @brief Variadic argument processor. This is overloading to exploit tail
 * recursion and variadics to achieve unlimited arguments.
 * @param vm    Squirrel VM
 * @param arg   First argument for processing
 * @param args  Rest of the arguments
 * @tparam Arg  Type of the first argument
 * @tparam Args Type of the rest of the arguments
 * @return Argument number
 */
template <class Arg, class... Args>
inline int pushArgs(HSQUIRRELVM vm, Arg arg, Args... args) {
    return pushArgs(vm, arg) + pushArgs(vm, args...);
}

/**
 * @brief Constructor allocator for variadic constructor arguments with Sqrat::Class.
 * When constructing a class, pass this as the allocator template parameter with the
 * parameters specifying the parameters of the visible constructor you wish to expose
 * to the script. For example, if you have a class Point, and wish to expose the
 *  Point(int x, int y);
 * constructor, you can do so on creating the sqrat class:
 *  Sqrat::Class<Point, sqext::ConstAlloc<Point, int, int> > pointClass;
 * @tparam C    Class to allocate for
 * @tparam Args Argument types.
 */
template <class C, class... Args>
class ConstAlloc : public Sqrat::DefaultAllocator<C> {
public:
    /**
     * @brief Internal function for creating a new instance of the class.
     */
    static SQInteger New(HSQUIRRELVM vm) {
        int index = 2;
        C *instance = new C(Sqrat::Var<Args>(vm, index++).value...);
        sq_setinstanceup(vm, 1, instance);
        sq_setreleasehook(vm, 1, &Sqrat::DefaultAllocator<C>::Delete);
        return 0;
    }
};

class SQIClass;

/**
 * @brief Squirrel class instance. This handles fast access to handled members.
 * The handles of a SQI class instance are immutable.
 */
class SQIClassInstance {
public:
    /**
     * @brief SQIClassInstance constructor
     * @param v Squirrel VM
     * @param o Object
     * @param handles   Handles of this object
     */
    SQIClassInstance(HSQUIRRELVM v, HSQOBJECT o, SQIClass &ct) : vm(v), object(o), classt(ct) {
        sq_addref(vm, &object);
    }

    /**
     * @brief SQIClassInstance copy constructor
     */
    SQIClassInstance(const SQIClassInstance &other) : vm(other.vm), object(other.object), classt(other.classt) {
        sq_addref(vm, &object);
    }

    ~SQIClassInstance() {
        sq_release(vm, &object);
        sq_resetobject(&object);
    }

    /**
     * @brief Get the object as a sqrat object
     * @return The sqrat object
     */
    Sqrat::Object get() {
        return Sqrat::Object(object, vm);
    }

    /**
     * @brief Get the object by its handle
     * @param index Index of the handle
     * @return The sqrat object associated with this handle
     */
    inline Sqrat::Object get(int index);

    /**
     * @brief Get the object by its handle
     * @param index Index of the handle
     * @tparam T    Type to cast to
     * @return The sqrat object associated with this handle
     */
    template <class T>
    inline T cast(int index) {
        return get(index).Cast<T>();
    }

    /**
     * @brief Get an member object by it's key. This is a convenience function.
     * @param key   Member name
     * @return The Sqrat::Object under this key
     */
    inline Sqrat::Object get(const SQChar *key) {
        return get().GetSlot(key);
    }

    /**
     * @brief Get an member object by it's key. This is a convenience function.
     * @param key   Member name
     * @tparam T    Type to cast to
     * @return The object under this key cast to type T
     */
    template <class T>
    inline T cast(const SQChar *key) {
        return get().GetSlot(key).Cast<T>();
    }

    /**
     * @brief Set the object at a given index
     * @param index Index of the object handle
     * @param o Object
     * @tparam T    Object type
     */
    template <class T>
    inline void set(int index, T o);

    /**
     * @brief Set the object at a given index
     * @param key   Object name
     * @param o Object
     * @tparam T    Object type
     */
    template <class T>
    inline void set(const SQChar *key, T o) {
        sq_pushobject(vm, object);
        sq_pushstring(vm, key, -1);
        pushArgs(vm, o);

        if (SQ_FAILED(sq_set(vm, -3)))
            throw Sqrat::Exception(Sqrat::LastErrorString(vm));

        sq_pop(vm, 1);
    }

    /**
     * @brief Call a function by its handle
     * @param index Index of the handle
     */
    void call(int index) {
        sq_pushobject(vm, get(index).GetObject());
        sq_pushobject(vm, object);

        if (SQ_FAILED(sq_call(vm, 1, false, true)))
            throw Sqrat::Exception(Sqrat::LastErrorString(vm));

        sq_pop(vm, 1);
    }

    /**
     * @brief Call a function by its handle
     * @param index Index of the handle
     * @param args  Arguments to pass to the function
     * @tparam Args Argument type
     */
    template <class... Args>
    void call(int index, Args... args) {
        sq_pushobject(vm, get(index).GetObject());
        sq_pushobject(vm, object);

        int argc = pushArgs(vm, args...);

        if (SQ_FAILED(sq_call(vm, 1 + argc, false, true)))
            throw Sqrat::Exception(Sqrat::LastErrorString(vm));

        sq_pop(vm, 1);
    }

    /**
     * @brief Call a function by its handle. Calls with return type parameter
     * which must be specified (e.g. obj.callr<int>(0);)
     * @param index Index of the handle
     * @tparam RT   Return type (must be specified)
     * @return The return of the function
     */
    template <class RT>
    RT callr(int index) {
        sq_pushobject(vm, get(index).GetObject());
        sq_pushobject(vm, object);

        if (SQ_FAILED(sq_call(vm, 1, true, true)))
            throw Sqrat::Exception(Sqrat::LastErrorString(vm));

        HSQOBJECT retObj;
        sq_getstackobj(vm, -1, &retObj);
        Sqrat::Object obj(retObj, vm);
        sq_pop(vm, 2);

        return obj.Cast<RT>();
    }

    /**
     * @brief Call a function by its handle. Calls with return type parameter
     * which must be specified (e.g. obj.callr<int>(0);)
     * @param index Index of the handle
     * @param args  Arguments to pass to the function
     * @tparam RT   Return type (must be specified)
     * @tparam Args Argument type
     * @return The return of the function
     */
    template <class RT, class... Args>
    RT callr(int index, Args... args) {
        sq_pushobject(vm, get(index).GetObject());
        sq_pushobject(vm, object);

        int argc = pushArgs(vm, args...);

        if (SQ_FAILED(sq_call(vm, 1 + argc, true, true)))
            throw Sqrat::Exception(Sqrat::LastErrorString(vm));

        HSQOBJECT retObj;
        sq_getstackobj(vm, -1, &retObj);
        Sqrat::Object obj(retObj, vm);
        sq_pop(vm, 2);

        return obj.Cast<RT>();
    }

    /**
     * @brief Call a function by name
     * @param key   Key value of the member function
     */
    inline void call(const SQChar *key) {
        Sqrat::Function(get(), key)();
    }

    /**
     * @brief Call a function by name
     * @param key   Key value of the member function
     * @param args  Arguments to pass to the function
     * @tparam Args Argument type
     */
    template <class... Args>
    inline void call(const SQChar *key, Args... args) {
        Sqrat::Function(get(), key)(args...);
    }

    /**
     * @brief Call a function by name. Calls with return type parameter
     * which must be specified (e.g. obj.callr<int>("func");)
     * @param key   Key value of the member function
     * @tparam RT   Return type (must be specified)
     * @return The return of the function
     */
    template <class RT>
    inline RT callr(const SQChar *key) {
        return Sqrat::Function(get(), key).Evaluate<RT>();
    }

    /**
     * @brief Call a function by name. Calls with return type parameter
     * which must be specified (e.g. obj.callr<int>("func");)
     * @param key   Key value of the member function
     * @param args  Arguments to pass to the function
     * @tparam RT   Return type (must be specified)
     * @tparam Args Argument type
     * @return The return of the function
     */
    template <class RT, class... Args>
    inline RT callr(const SQChar *key, Args... args) {
        return Sqrat::Function(get(), key).Evaluate<RT>(args...);
    }

private:
    //Squirrel virtual machine
    const HSQUIRRELVM vm;

    HSQOBJECT object;

    //SQIClass reference
    SQIClass &classt;

};

/**
 * @brief Squirrel internal class. This class handles classes defined in scripts
 */
class SQIClass {
public:
    /**
     * @brief SQIClass constructor
     * @param cobj  Class object
     * @param v Squirrel VM
     */
    SQIClass(Sqrat::Object cobj, HSQUIRRELVM v = Sqrat::DefaultVM::Get()) : classobj(cobj), vm(v) { }

    /**
     * @brief SQIClass constructor
     * @param cobj  Class object
     * @param v Squirrel VM
     */
    SQIClass(const SQChar *key, HSQUIRRELVM v = Sqrat::DefaultVM::Get()) : vm(v) {
        classobj = Sqrat::RootTable(vm).GetSlot(key);
    }

    /**
     * @brief Instantiate a new object of this class with all the handles of this type.
     * @return A class instance of this type
     */
    SQIClassInstance New() {
        HSQOBJECT pclass = classobj.GetObject();
        HSQOBJECT newObject;

        //Call the constructor
        sq_pushobject(vm, pclass);
        sq_pushnull(vm); //this === null (thanks fagiano :D)

        if (SQ_FAILED(sq_call(vm, 1, true, true)))
            throw Sqrat::Exception(Sqrat::LastErrorString(vm));

        sq_getstackobj(vm, -1, &newObject);

        SQIClassInstance object(vm, newObject, *this);
        sq_pop(vm, 2);

        return object;
    }

    /**
     * @brief Instantiate a new object of this class with all the handles of this type.
     * @param args  Arguments to instantiate with
     * @tparam Args Argument parameter types
     * @return A class instance of this type
     */
    template <class... Args>
    SQIClassInstance New(Args... args) {
        HSQOBJECT pclass = classobj.GetObject();
        HSQOBJECT newObject;

        //Call the constructor
        sq_pushobject(vm, pclass);
        sq_pushnull(vm); //this === null

        int argc = pushArgs(vm, args...);

        if (SQ_FAILED(sq_call(vm, 1 + argc, true, true)))
            throw Sqrat::Exception(Sqrat::LastErrorString(vm));

        sq_getstackobj(vm, -1, &newObject);

        SQIClassInstance object(vm, newObject, *this);
        sq_pop(vm, 2);

        return object;
    }

    /**
     * @brief Bind a member handle of member "key" to an index. This will bind
     * to a specific index and expand the handles vector to account for this.
     * This therefore should only be used on instantiation. Otherwise use
     * the single parameter version which allocates a single new member handle.
     * @param index Lookup index of the member handle
     * @param key   Key value of the member handle within the squirrel VM
     */
    void bind(int index, const SQChar *key) {
        if (index >= handles.size())
            handles.resize(index + 1);

        HSQMEMBERHANDLE handle;

        sq_pushobject(vm, classobj.GetObject());
        sq_pushstring(vm, key, -1);

        if (SQ_FAILED(sq_getmemberhandle(vm, -2, &handle)))
            throw Sqrat::Exception(Sqrat::LastErrorString(vm));

        sq_pop(vm, 1);

        handles[index] = handle;
    }

    /**
     * @brief Bind a member handle of member "key" to an index. This will bind
     * a new member handle to the end of the handles vector and get it's index.
     * @param key   Key value of the member handle within the squirrel VM
     * @return Index of the member handle
     */
    int bind(const SQChar *key) {
        int index = handles.size();
        bind(index, key);
        return index;
    }

    /**
     * @brief Cast a regular sqrat object to an SQIClassInstance
     * @param obj   Sqrat object
     * @return The class instance
     */
    SQIClassInstance cast(Sqrat::Object obj) {
        return SQIClassInstance(vm, obj.GetObject(), *this);
    }

    /**
     * @brief Get a handle at a given index
     * @param index Index of the handle
     * @return The squirrel member handle object
     */
    HSQMEMBERHANDLE getHandle(int index) const {
        return handles[index];
    }

private:
    //Sqrat class object type
    Sqrat::Object classobj;

    //Squirrel VM
    const HSQUIRRELVM vm;

    //Member handles lookup
    std::vector<HSQMEMBERHANDLE> handles;

};

Sqrat::Object SQIClassInstance::get(int index) {
    HSQOBJECT obj;
    sq_pushobject(vm, object);

    HSQMEMBERHANDLE handle = classt.getHandle(index);
    if (SQ_FAILED(sq_getbyhandle(vm, -1, &handle)))
        throw Sqrat::Exception(Sqrat::LastErrorString(vm));

    sq_getstackobj(vm, -1, &obj);
    Sqrat::Object sobj(obj, vm);

    sq_pop(vm, 2);

    return sobj;
}

template <class T>
inline void SQIClassInstance::set(int index, T o) {
    sq_pushobject(vm, object);

    pushArgs(vm, o);

    HSQMEMBERHANDLE handle = classt.getHandle(index);
    if (SQ_FAILED(sq_setbyhandle(vm, -2, &handle)))
        throw Sqrat::Exception(Sqrat::LastErrorString(vm));

    sq_pop(vm, 1);
}

}

#endif // __SQUIRREL_EXTENSION_HEADER__

Related

Wiki: Source Code