Code share: ArgumentOrganizer for NativeFunctionArgs

I want to share some code that I’ve written for working with NativeFunctionArgs. If you work with JavascriptEngine, I think you’ll find this code to be very useful.

If you use JavascriptEngine for scripting, then you’ll know that a DynamicObject function receives all of its arguments in the form of an Array<var>. I wanted to make something that would organize this into a more manageable form, so that you can quickly specify which type of arguments you are expecting a function to receive, and easily access them.

Let’s look at what ArgumentOrganizer does.

struct ScriptObject : public DynamicObject
{
    static var someFunction(const var::NativeFunctionArgs& a) // Here is the function that we can run from within Javascript
    {
        ArgumentOrganizer<String, int> o(a);      // We construct an ArgumentOrganizer from the NativeFunctionArgs, specifying which types or argument you are expecting.

        auto firstArgument  = o.getArgument<0>(); // this is a String
        auto secondArgument = o.getArgument<1>(); // this is an int

        DBG(firstArgument + " " + String(secondArgument)); // prints "hello world 6"

        return var::undefined();
    }

    ScriptObject()
    { setMethod("f", someFunction); }
};


int main()
{
    JavascriptEngine js;

    js.registerNativeObject("a", new ScriptObject());

                                                   // Here is the function we're executing in JS.
                                                   // It has one String argument, followed by an int.
    js.execute("a.f(\"hello world\", 6)");
    return 0;
}

This is really useful! It saves you a lot of looping and type checking every time you want to extract arguments.

Now let’s look at the code.

#include "../JuceLibraryCode/JuceHeader.h"
#include <utility>
#include <tuple>
#include <type_traits>

template<typename... T>
class ArgumentOrganizer
{
public:
    ArgumentOrganizer(const var::NativeFunctionArgs& a) :
        thisObject(a.thisObject.getDynamicObject()),
        tpl(populate_tuple<T...>(a, std::make_index_sequence<sizeof...(T)>{})) {}

    template <int n>
    auto getArgument() const
    { return std::get<n>(tpl); }

private:
    DynamicObject::Ptr thisObject;
    const std::tuple<T...> tpl;

    template<typename... Args, int... i>
    auto populate_tuple(const var::NativeFunctionArgs& a, const std::index_sequence<i...>&)
    { return std::make_tuple((VariantConverter<Args>::fromVar(i < a.numArguments ? a.arguments[i] : var()))...); }
};

I was really pleased with how concise this I got this. As you can see, it loads the arguments into a tuple, meaning that a lot of the heavy lifting is done at compile time, and that the compiler won’t let you do stupid things like trying to access arguments terms don’t exist.

A second version, ComplexArgumentOrganizer, organizes things even further. Instead of matching the arguments types by position, it scans through all of the arguments and categorizes them, allowing for “lazy evaluation”.

struct ScriptObject : public DynamicObject
{
    static var someFunction(const var::NativeFunctionArgs& a)
    {
        ComplexArgumentOrganizer<String, int64> o(a);

        DBG(o.getArgument<0>(0));      // Access the 0th argument of type 0 (String)
        DBG(o.getArgument<0>(1));      // Access the 1st argument, etc...
        DBG(o.getArgument<0>(2));
        DBG(o.getArgument<1>(0));      // Access the 0th argument of type 1 (int)
        DBG(o.getArgument<1>(1));      // Access the 1st argument, etc...
        DBG(o.getArgument<1>(2));

        return var::undefined();
    }

    ScriptObject()
    { setMethod("f", someFunction); }
};


int main()
{
    JavascriptEngine js;

    js.registerNativeObject("a", new ScriptObject());

    js.execute("a.f(1, \"a\", 2, \"b\", 3, \"c\")"); // Prints "a b c 1 2 3"

    return 0;
}

This is more convenient, but a bit less efficient: every extra type you specify constructs an Array and loops through the argument list.

You can get the whole code base here. There are a few more features there that I haven’t mentioned, mostly to help you work with DynamicObject and numeric arguments (so that you don’t have to distinguish between ints and doubles).

I hope it’s useful to you! I’ve been testing it for a couple of days, but there might still be bugs. Comments and improvements are welcome.

1 Like