Version 3 (modified by Philip Taylor, 14 years ago) ( diff )

--

Defining interfaces in C++

Think of a name for the component. We'll use "Position" in this example; replace it with your chosen name in all the filenames and code samples below.

Create the file simulation2/components/ICmpPosition.h:

/* Copyright (C) 2010 Wildfire Games.
 * ...the usual copyright header...
 */

#ifndef INCLUDED_ICMPPOSITION
#define INCLUDED_ICMPPOSITION

#include "simulation2/Interface.h"

...any other forward declarations and includes you might need...

/**
 * Documentation to describe what this interface and its associated component types are
 * for, and roughly how they should be used.
 */
class ICmpPosition : public IComponent
{
public:
    /**
     * Documentation for each method.
     */
    virtual int DoWhatever(int x, int y) = 0;

    ...

    DECLARE_INTERFACE_TYPE(Position)
};

#endif // INCLUDED_ICMPPOSITION

This defines the interface that C++ code will use to access components.

Create the file simulation2/components/ICmpPosition.cpp:

/* Copyright (C) 2010 Wildfire Games.
 * ...the usual copyright header...
 */

#include "precompiled.h"

#include "ICmpPosition.h"

#include "simulation2/InterfaceScripted.h"

BEGIN_INTERFACE_WRAPPER(Position)
DEFINE_INTERFACE_METHOD_2("DoWhatever", int, ICmpPosition, DoWhatever, int, int)
// DEFINE_INTERFACE_METHOD for all the other methods too
END_INTERFACE_WRAPPER(Position)

This defines a JavaScript wrapper, so that scripts can access methods of components implementing that interface.

This wrapper should only contain methods that are safe to access from simulation scripts: they must not crash, they must return deterministic results, etc. Methods that are intended for use solely by C++ should not be listed here.

Every interface must define a script wrapper, though in some cases they might contain no methods.

Interface method script wrappers

Interface methods are defined with the macro:

DEFINE_INTERFACE_METHOD_NumberOfArguments("MethodName", ReturnType, ICmpPosition, MethodName, !ArgType0, !ArgType1, ...)

corresponding to the C++ method ReturnType ICmpPosition::MethodName(!ArgType0, !ArgType1, ...)

There's a small limit to the number of arguments that are currently supported - if you need more, first try to save yourself some pain by using fewer arguments, otherwise you'll need to add a new macro into simulation2/InterfaceScripted.h and increase MAX_ARGS in scriptinterface/NativeWrapperDecls.h. (Not sure if anything else needs changing.)

The two MethodNames don't have to be the same - in rare cases you might want to expose it as DoWhatever to scripts but link it to the ICmpPosition::DoWhatever_wrapper() method which does some extra conversions or checks or whatever.

For methods exposed to scripts like this, the arguments should be pass-by-value. E.g. use std::wstring arguments, not const std::wstring&.

To convert types between C++ and JS, ToJSVal<ReturnType> and FromJSVal<ArgTypeN> must be defined, as below.

Script type conversions

If you try to use a type without having defined conversions, you'll probably get mysterious linker errors that mention ToJSVal or FromJSVal. First, work out where the conversion should be defined. Basic data types (integers, STL containers, etc) go in scriptinterface/ScriptConversions.cpp. Non-basic data types from the game engine typically go in simulation2/scripting/EngineScriptConversions.cpp. (They could go in different files if that turns out to be cleaner - it doesn't matter where they're defined as long as the linker finds them).

To convert from a C++ type T to a JS value, define:

template<> jsval ScriptInterface::ToJSVal<T>(JSContext* cx, T const& val)
{
    ...
}

On error, you should return JSVAL_VOID (JS's undefined value) and probably report an error message somehow. Be careful about JS garbage collection (don't let it collect the objects you're constructing before you return them).

To convert from a JS value to a C++ type T, define:

template<> bool ScriptInterface::FromJSVal<T>(JSContext* cx, jsval v, T& out)
{
    ...
}

On error, return false (doesn't matter what you do with out). On success, return true and put the value in out. Still need to be careful about garbage collection (v is rooted, but it might have getters that execute arbitrary code and return unrooted values when you access properties, so don't let them be collected before you've finished using them).

Note: See TracWiki for help on using the wiki.