Communicating with JavaScript
Gameface interprets and executes JavaScript code that can be used for scripting the UI. The execution itself is performed by a JavaScript virtual machine. Gameface uses different VMs on different platforms to provide the best available option.
- Gameface uses V8 on all platforms except for x86 UWP and 32-bit Arm UWP where ChakraCore is used.
All JavaScript virtual machines are planned to be replaced with V8 at some point in the future.
All currently implemented JavaScript functions and objects can be viewed in the JavaScript DOM API section in the documentation.
All communication between JavaScript and the game goes through the engine
module.
For ways to update the UI without writing JavaScript, take a look at the data binding feature.
There are two ways for invoking native code from JavaScript and vice-versa.
- Events
- Calls
Events
Events allow to call multiple handlers in both directions, but they cannot return any value. To register a JavaScript handler for an event use the engine.on
method.
C++ triggering JavaScript
First, the JavaScript code has to attach a handler for the event using engine.on
:
engine.on('PlayerScoreChanged', function (new_score) {
var scoreDisplay = document.getElementById('scoreDisplay');
scoreDisplay.innerHTML = new_score;
});
The first provided argument is the name of the event. The second is the callback function to be executed when the event has been triggered.
Using the cohtml::View::TriggerEvent
method, the event in C++ is triggered :
// set player score to 10000
view->TriggerEvent("PlayerScoreChanged", 10000);
There are several global functions in the cohtml
namespace which can be used:
cohtml::TriggerEvent(view, "ScoreChanged", 10000);
Every TriggerEvent
call starts with a C++ macro:
COHTML_TRIGGER_EVENT_SCOPE(TriggerEvent_N_Params, RuntimeName);
By default, this macro does nothing and is compiled out. However, it can be used to add custom code by defining COHTML_TRIGGER_EVENT_SCOPE
before it’s used in View.h. For example, profiling code such as:
#define COHTML_TRIGGER_EVENT_SCOPE(X, RuntimeName) \
struct GeneratedClass_##X { \
GeneratedClass_##X(const char* name) \
: EventName(name) { \
/* Start profiling */ \
} \
~GeneratedClass_##X() { \
/* End profiling */ \
} \
const char* EventName; \
} __instance(RuntimeName);
To unregister a JavaScript handler for an event, use the engine.off
method. A named function must be used. The above example with engine.on
would become:
function setScoreText(new_score) {
var scoreDisplay = document.getElementById('scoreDisplay');
scoreDisplay.innerHTML = new_score;
}
engine.on('PlayerScoreChanged', setScoreText);
and to unregister, provide the same function using engine.off
when needed:
engine.off('PlayerScoreChanged', setScoreText);
The first provided argument is the name of the event and the second one is the callback function to be removed.
engine.on and engine.off Code Documentation
engine.on - Register handler for an event
@param {String} name - name of the event
@param {Function} callback - callback function to be executed when the event has been triggered
@param context `*this*` context for the function, by default the engine object
engine.off - Remove handler for an event
@param {String} name - name of the event
@param {Function} callback - the callback function to be removed
@param context `*this*` context for the function
JavaScript triggering C++
Registering C++ functions for events triggered by JavaScript should happen in the handler for the cohtml::IViewListener::OnReadyForBindings
event.
Event Handlers
Event handlers are registered with the cohtml::View::RegisterForEvent
method. They cannot return any value to JavaScript but may trigger an event for JavaScript. There may be more than one C++ handler for a given event. There also may be both C++ and JavaScript handlers for the same event.
class Game
{
public:
void Quit()
{
}
} g_Game;
class GameIViewListener : public cohtml::IViewListener
{
public:
virtual void OnReadyForBindings()
{
m_View->RegisterForEvent("OnQuitClicked",
cohtml::MakeHandler(&g_Game, &Game::Quit));
}
};
Triggering the event from JavaScript looks like:
engine.on('OnQuitClicked', function () {
ShowMessage('Bye');
});
function OnClick() {
// this will execute both Game::Quit in C++
// and ShowMessage('Bye') in JavaScript
engine.trigger('OnQuitClicked');
});
cohtml::MakeHandler
uses template argument deduction to guess the signature of the handler. This requires several specializations of cohtml::FunctorTraits
and overloads of cohtml::EventHandler::InvokeStub
. To extend the number of arguments for event handlers supported by Gameface, supply additional specializations and overloads.
Call Handlers
Promises are used to return results from C++ to JavaScript. Gameface implements ECMAScript 6 promises
Call handlers are registered with the cohtml::View::BindCall
method. There may be only one handler for a given call and the handler may return a result.
class Game
{
public:
std::string GetPlayerName()
{
return m_PlayerName;
}
} g_Game;
class GameIViewListener : public cohtml::IViewListener
{
public:
virtual void OnReadyForBindings()
{
m_View->BindCall("getPlayerName", cohtml::MakeHandler(&g_Game,
&Game::GetPlayerName));
}
};
To get the player name in the view is:
// call 'Game::GetPlayerName' with callback for the result
engine.call('getPlayerName').then(function (name) {
var playerName = document.getElementById('playerName');
playerName.innerHTML = name;
});
BindCall
and engine.call
with a single handler and RegisterForEvent
and engine.trigger
with more than one handler.Returning Promises in C++
If you wish to return promises to your game engine, you can create your own C++ class. All asynchronous C++ callbacks should return that class. When a C++ function needs to return an async result to JavaScript, it has to return an instance of that CxxPromise class with a special Id.
struct CxxPromise
{
CxxPromise(): id(nextId++) {};
template <typename T>
void Resolve(cohtml::View* view, const T& result)
{
view->TriggerEvent("c++.promise.resolve", Id, result);
}
template <typename T>
void Reject(cohtml::View* view, const T& result)
{
view->TriggerEvent("c++.promise.reject", Id, result);
}
int Id;
static int nextId = 0;
}
When the promise is resolved, view->TriggerEvent("c++.promise.resolve", Id, result);
can be called.
To handle promises as expected, the JavaScript part of the global engine object has to be modified:
var cxx_promises = {};
function call_async_cxx() {
var result = new Promise((resolve, reject) => {
engine.call.apply(engine, arguments).then(
(cxx_promise) => {
cxx_promises[cxx_promise.id] = {
resolve: resolve,
reject: reject,
};
},
(errors) => {
reject(errors);
}
);
});
return result;
}
engine.on('c++.promise.resolve', (id, value) => {
cxx_promises[id].resolve(value);
delete cxx_promises[id];
});
engine.on('c++.promise.reject', (id, value) => {
cxx_promises[id].reject(value);
delete cxx_promises[id];
});
// usage:
call_async_cxx('RegisteredMethodName').then(...);
The general usage from the C++ side would look like this:
CxxPromise DoStuff()
{
CxxPromise result;
startWork(result);
return result;
)
void OnWorkDone(const CxxPromise& promise)
{
promise.Resolve(view, 42);
)
Special Events
There are a few special events that are fired by Gameface. Some were added for convenience and some are being used internally. In order to avoid clashes and misbehavior, please do not fire events with names such as:
- ‘Ready’ - the ready event, more details below
- ‘*’ - the all event, more details below
- ‘_Unhandled’ - the unhandled event, more details below
- ‘_Result’, ‘_OnReady’, ‘_OnError’ - internal events
engine.whenReady
When the View is ready to receive binding events and manipulate bound models, the Ready
event is triggered.
The engine Object also has a Promise member that is resolved when the engine is ready for bindings - engine.whenReady
. It is recommended to use engine.whenReady
instead of engine.on('Ready')
to avoid caring whether the event has already been fired or not.
Here’s an example of how the C++ code can be notified that the View is ready to use the binding layer:
engine.whenReady.then(() => { engine.trigger('NotifyCpp'); });
Ready
will be deprecated in the future, so it is recommended engine.whenReady
is used instead.engine.on('*') - Subscribe to all events
You can listen to all events coming from C++ in JS by attaching a handler to the engine.on('*') event. This approach can be beneficial in cases where you don’t want to subscribe to multiple events.
class Abilities {
spell(name) { console.log(`Cast ${name}`); }
heal(amount) { console.log(`Healed by ${amount}`); }
}
const abilities = new Abilities();
engine.on('*', (args) => {
switch (name) {
case 'ability:spell':
abilities.spell(args);
break;
case 'ability:heal':
abilities.heal(args);
break;
...
}
});
You can step further and use the names of the events to your advantage. You can create a subscriber helper which will map the handlers to the events. You need to specify the events and the target. The targets must have methods that match the event name. For example, if we use the ability:spell event and treat the colon symbol ( : ) as delimiter we can assume that the left part of the delimiter is the target and the right is the method - ability:spell must call the spell method from the Abilities class instance:
class Subscriber {
constructor() {
this.targets = {};
engine.whenReady.then(() => this.addEventListener());
}
addEventListener() {
engine.on('*', (args) => {
const [targetName, methodName] = args[0].split(':');
const target = this.targets[targetName];
target[methodName].apply(target, args);
});
}
subscribe(events, target) {
for (let event of events) {
const targetName = event.split(':')[0];
if (!this.targets[targetName]) this.targets[targetName] = target;
}
}
}
The subscription for the Mount class would look like this:
class Mount {
summon(name) { console.log(`Summon mount ${name}.`); }
dismiss(name) { console.log(`Dismiss mount ${name}.`); }
}
const mount = new Mount();
subscriber.subscribe(['mount:summon', 'mount:dismiss'], mount);
The ‘*’ event is also useful for debugging purposes like checking the flow of all fired events.
engine.on('_Unhandled') - Catching unhandled events
If an event gets triggered but Gameface doesn’t find a callback registered for it an ‘_Unhandled’ event will be emitted. You can catch unhandled events by registering for the ‘_Unhandled’ event and implementing a custom handling - for example you can throw an exception:
engine.on('_Unhandled', (args) => {
const name = args[0];
throw new Error(`Unhandled Event! No handler was called for event ${name} - none was found.`);
});
You’ll get the following error and the code will stop its execution.
Exposing C++ Types
To be able to use custom C++ types as arguments or results for an event and call handlers, the C++ type must be exposed to Cohtml. C++ types can be exposed to Cohtml by using the following pattern:
class Player
{
public:
std::string Name;
double GetScore() const;
void SetScore(double);
};
// called by Cohtml when an instance of Player goes through
// the C++ / JavaScript boundary
void CoherentBind(cohtml::Binder* binder, Player* player)
{
if (auto type = binder->RegisterType("Player", player))
{
type.Property("name", &Player::Name)
.Property("score", &Player::GetScore, &Player::SetScore)
;
}
}
.Property
syntax, a string literal must be provided. The string literal should stay “alive” at least until the OnBindingsReleased
View callback is called.The CoherentBind
overload must be visible in all places where a Player
instance is exposed to JavaScript, i.e. in any of the following cases:
- a
Player
instance is used as an argument toTriggerEvent
- a C++ handler takes a
Player
as an argument - a C++ handler returns a
Player
to JavaScript
In case the CoherentBind
overload for the Player
class is not visible, the following compilation error will be displayed:
include\cohtml\binding\binding.h(57): error C2338: cohtml::Overload_CoherentBind_For_Your_Type<T*>
1> include\cohtml\binding\binding.h(191) : see reference to function template instantiation 'void CoherentBind<T>(cohtml::Binder *,T *)' being compiled
1> with
1> [
1> T=Player
1> ]
Going through the template instantiation stack will reveal where Player
is used without the CoherentBind
overload visible.
The CoherentBind
overload for Player
is defined in any C++ source (.cpp) file but requires a declaration included with each instance where Player
is exposed to Cohtml.
Depending on the structure of the project, the following patterns are worth considering:
- If the
Player
is dedicated only to the UI, add the declaration ofCoherentBind
forPlayer
in thePlayer
header. This way the overload will be visible everywhere thePlayer
is visible. - If the
Player
is a generic game type - add a PlayerBinding.h or a MySubsystemBindings.h header with the declaration ofCoherentBind
forPlayer
(or for all the types in the particular game subsystem). After that make sure to include the header wherePlayer
is used with Cohtml.
CoherentBind
overload for Player
has to be either in the namespace of Player
or in the cohtml
namespace. This way it will be found using [argument-dependent lookup][adl].cohtml::View::TriggerEvent
must be called with a valid instance of your class. Cohtml uses this instance to cache the exposed properties and in certain cases might need to use this instance. For example in the case of virtual getters or setters, or virtual inheritance, the instance pointer might need to be adjusted before calling a getter, or a setter for a property and getting this adjustment is using the virtual table pointer.STL and container types
Cohtml has built-in support for most STL
containers and std::
classes.
C++ Type | JavaScript Type | Header |
---|---|---|
std::string | String | <cohtml\Binding\String.h> |
std::vector | Array | <cohtml\Binding\Vector.h> |
std::map | Object | <cohtml\Binding\Map.h> |
std::pair | Object | <cohtml\Binding\Pair.h> |
C style array | Array | <cohtml\Binding\Array.h> |
std::shared_ptr | Stored object's type | <cohtml\Binding\SharedPointer.h> |
std::unique_ptr | Stored object's type | <cohtml\Binding\UniquePointer.h> |
raw pointer | Stored object's type | <cohtml\Binding\TypeTraits.h> |
Stored object's type
is the type of the unwrapped object. For example, the JavaScript type of std::shared_ptr<std::string*>
is String
.
Support for additional containers can be added in a similar way.
Support for a custom pointer type can be added by specializing PointerTrait
for it. For example:
template <typename T>
struct PointerTrait<MyPointer<T>> : TrueType
{
typedef T StoredType;
static void* Deref(MyPointer<StoredType>& ptr) // Returns a pointer to the stored object.
{
return ptr.myDeref(); // In case of myDeref returns a pointer to the stored object.
}
};
template <typename T>
struct PointerTrait<MyPointer<const T>> : TrueType
{
typedef T StoredType;
static void* Deref(MyPointer<const StoredType>& ptr)
{
return const_cast<StoredType*>(ptr.myDeref());
}
};
C++ and JavaScript Communication
After registering the Player
type, events can be triggered in both directions with instances of Player
as an argument and call handlers can return Player
s as return types.
class Game
{
public:
void Start()
{
m_View->TriggerEvent("StartWithPlayer", m_Player);
}
private:
Player m_Player;
} g_Game;
Then in JavaScript, we receive the object with the specified properties. The value of each property is the same as at the moment of triggering the event. The JavaScript callback may store a reference to the object, but its properties WILL NOT be synchronized with the actual g_Game.m_Player
in the game.
engine.on('StartWithPlayer', function (player) {
var playerName = document.getElementById('playerName');
playerName.innerHTML = player.name;
var scoreDisplay = document.getElementById('scoreDisplay');
scoreDisplay.innerHTML = player.score;
});
To call a C++ handler with an instance of Player
created in JavaScript, the object must have a property __Type
with the value Player (the same name of the type we gave to cohtml::Binder::RegisterType
in CoherentBind
for Player
. Otherwise, Cohtml cannot treat the object as an instance of Player
.
function CreatePlayer() {
var player = {
__Type: 'Player', // set the type name
name: "MyTestPlayer",
score: 0
};
engine.call('CreatePlayer', player).then(function (success) {
if (success) {
ShowMessage('Welcome ' + player.name);
} else {
ShowMessage('Sorry, try another name');
}
});
});
bool CreatePlayer(const Player& player)
{
if (player.Name.find(' ') == std::string::npos)
{
// create the player
return true;
}
else
{
// sorry, no spaces in player name
return false;
}
}
Customizing Promises
Custom promises have been deprecated! Gameface now supports ECMAScript 6 promises.
JavaScript extensions
Cohtml provides extensions on top of the standard-defined vanilla DOM-related JavaScript. The extensions provide better performance and a more natural UI development workflow. A prime example of those extensions are the StylePositioningAttributes
“Node.leftXX/Node.topXX” methods. In vanilla JS, Node.top
/Node.left
must be called to update the position of an element. However, those functions take a string parameter. To update an element’s left
property:
myNode.left = newPos + "px";
The newPos
variable is a number of pixels, which the runtime then converts to a string, appends the ‘px’ specifier, and sends to the native code. The native code in turn converts it back to a number in order to re-calculate the position. However, this method is very inefficient, creates JavaScript garbage, and leads to unnecessary work.
Gameface provides a more efficient method:
myNode.leftPX = newPos;
This method avoids garbage and is much faster - both in JavaScript and native code. A complete list of extensions is available in the Javascript DOM API section of the documentation.
Exporting C++ objects by reference
Gameface supports exporting C++ objects by reference. This avoids copying the C++ values to the JavaScript heap and makes the communication between the game and the UI faster. However, the user is responsible for keeping the exposed C++ object pointers valid while they are exposed to JavaScript.
The API to expose a C++ object by reference is the same as normal values. The difference is that to expose a reference, the object has to be wrapped in a cohtml::ByRef
holder.
std::shared_ptr
or std::unique_ptr
when a container with dynamic memory will be exposed by reference, because it won’t affect the container’s data and wouldn’t require rebinding. cohtml::ByRef(std::shared_ptr<std::vector<int>>)
Once an object is exposed by reference to JavaScript, there is no need to make any subsequent calls to update the data.
When an exposed object is about to be destroyed (or moved), its reference must be removed from JavaScript to avoid using invalid pointers (use-after-free). To notify JavaScript that a referenced object is no longer available, call the cohtml::View::DestroyExposedObject
method with the address of the object being destroyed. This will remove the reference from the JavaScript values and any attempt to access the properties of the object will now throw an exception in JavaScript.
Here is an example usage of exposing objects by reference:
struct Nameplate
{
float X;
float Y;
float Health;
float MaxHealth;
};
// the same API for exposing the Nameplate struct
//
void CoherentBind(cohtml::Binder* binder, Nameplate* nameplate)
{
if (auto type = binder->RegisterType("nameplate", nameplate))
{
type.Property("x", &Nameplate::X)
.Property("y", &Nameplate::Y)
.Property("health", &Nameplate::Health)
.Property("maxHealth", &Nameplate::MaxHealth);
}
}
class NameplateManager
{
public:
void CreateNameplates(unsigned count)
{
// Create and fill nameplates
// ..
// Send a reference to the nameplates' vector to JavaScript
m_View->TriggerEvent("nameplates.create",
cohtml::ByRef(m_Nameplates));
}
void UpdateNameplates()
{
m_View->TriggerEvent("nameplates.update");
}
void DestroyNameplates()
{
// Notify the JavaScript universe that this reference is no
// longer valid. All accessses to the variable will throw
// exception in JavaScript.
m_View->DestroyExposedObject(m_Nameplates);
}
private:
cohtml::View* m_View;
std::vector<Nameplate> m_Nameplates;
};
To take advantage of this C++ class the JavaScript side can do:
// array to hold reference to our exposed array
let nameplates = [];
// this will point the exposed by C++ nameplate object
function updateNameplate() {
this.position.leftVW = this.X; // will take the current X member
this.position.topVH = this.Y; // will take the current Y member
// update the health with the current value, without fractional part
this.healthDisplay.textContent = this.health.toFixed();
this.maxHealthDisplay.textContent = this.maxHealth.toFixed();
}
function updateNameplates() {
for (let i = 0; i < nameplates.length; i++) {
nameplates[i].update();
}
}
function createNameplates(nameplatesRef) {
nameplates = nameplatesRef;
for (let i = 0; i < nameplates.length; i++) {
// create the DOM elements for the nameplate
let plate = createNameplateDOM(i);
// extend the C++ nameplate with additional JavaScript values - the DOM
// elements that has to be updated when the nameplate is changed
nameplates[i].position = plate.style;
nameplates[i].healthDisplay = plate.childNodes.item(1);
nameplates[i].maxHealthDisplay = plate.childNodes.item(2);
// extend the C++ nameplate with JavaScript function for the update
nameplates[i].update = updateNameplate();
nameplates[i].update();
document.body.appendChild(plate);
}
}
engine.on('nameplates.create', createNameplates);
engine.on('nameplates.update', updateNameplates);
Calling C++ methods from JavaScript
When a C++ object is exposed by reference to JavaScript, its methods can also be called from JavaScript.
class Game
{
void Initialize(cohtml::View* view)
{
// this game object will be accessing as the global "game" variable
// in JavaScript
view->ExposeAsGlobal("game", this);
}
~Game()
{
// make sure JavaScript won't crash us
m_View->DestroyExposedObject(this);
}
bool LoadSave(const char* name);
Quit();
};
void CoherentBind(cohtml::Binder* binder, Game* game)
{
if (auto type = binder->RegisterType("Game", game))
{
type.Method("loadSave", &Game::LoadSave)
.Method("quit", &Game::Quit)
;
}
}
After this, JavaScript can use the game
variable as any other object.
quitButton.addEventListener('touchstart', function() { game.quit(); }, false);
// ...
function LoadGame(name) {
if (!game.loadSave(name)) { // calls the C++ method
ShowError();
}
}
Binding and inheritance
You can rebind properties without an error and the last one will be used.
In the example below the JavaScript object created when binding a MyType
C++ object will have a single prop
member that refers to &MyType::Prop3
.
void CoherentBind(cohtml::Binder* binder, MyType* obj)
{
if (auto type = binder->RegisterType("MyType", obj))
{
type.Property("prop", &MyType::Prop0);
type.Property("prop", &MyType::Prop1);
type.Property("prop", &MyType::Prop2);
type.Property("prop", &MyType::Prop3);
}
}
If you want to expose all properties, you have to name them accordingly.
void CoherentBind(cohtml::Binder* binder, MyType* obj)
{
if (auto type = binder->RegisterType("MyType", obj))
{
type.Property("prop0", &MyType::Prop0);
type.Property("prop1", &MyType::Prop1);
type.Property("prop2", &MyType::Prop2);
type.Property("prop3", &MyType::Prop3);
}
}
This is important when binding the base class of your C++ type through a BaseClass
call. BaseClass<BaseClassType>
calls the CoherentBind
method of the BaseClassType
with the derived object. Instead of creating a new type definition, however, the binder registers all properties from base classes into the type definition of the derived class, so members with the same name in the base class and the derived class collide. Therefore, the order of BaseClass
and Property
is important when binding a type. The example below illustrates different scenarios.
struct SampleBaseClass
{
std::string Type = "Base";
};
struct SampleDerivedClass : public SampleBaseClass
{
std::string Type = "Derived";
};
void CoherentBind(cohtml::Binder* binder, SampleBaseClass* obj)
{
if (auto type = binder->RegisterType("SampleBaseClass", obj))
{
type.Property("type", &SampleBaseClass::Type);
}
}
// This method will expose only SampleDerivedClass::Type to JavaScript.
void CoherentBind(cohtml::Binder* binder, SampleDerivedClass* obj)
{
if (auto type = binder->RegisterType("SampleDerivedClass", obj))
{
type.BaseClass<SampleBaseClass>(obj);
type.Property("type", &SampleDerivedClass::Type);
}
}
/*
* This method will expose only SampleBaseClass::Type to JavaScript.
*
* void CoherentBind(cohtml::Binder* binder, SampleDerivedClass* obj)
* {
* if (auto type = binder->RegisterType("SampleDerivedClass", obj))
* {
* type.Property("type", &SampleDerivedClass::Type);
* type.BaseClass<SampleBaseClass>(obj);
* }
* }
*/
/*
* This method will expose both properties to JavaScript as "type" and "SampleDerivedClassType".
*
* void CoherentBind(cohtml::Binder* binder, SampleDerivedClass* obj)
* {
* if (auto type = binder->RegisterType("SampleDerivedClass", obj))
* {
* type.Property("SampleDerivedClassType", &SampleDerivedClass::Type);
* type.BaseClass<SampleBaseClass>(obj);
* }
* }
*/
A CoherentBind
that registers multiple methods with the same name is not supported.
The following is true for binding by reference and data-binding.
- Calling a bound virtual method from JS works as expected.
- Binding an object that doesn’t define virtual methods in its base class, but defines virtual methods in a derived class isn’t supported.
- Binding an object that uses multiple inheritance isn’t supported.
- The only case that is supported is when the address of the base class part of the object matches the address of the object.
The following is true for binding by value.
- Multiple inheritance works as expected when binding by value.
Bind vector with polymorphic types of data
You can bind base and derived objects, and use virtual methods with polymorphism. This means that if you have an inheritance hierarchy of classes, registering the base class using CoherentBind
will ensure that in JavaScript you can call the correct methods based on the object type for the entire hierarchy of classes.
In the example below, if ModelWithPolymorphicVector
contains objects derived from SampleBaseClass
, their overriding GetType
methods will be dynamically dispatched when used from JavaScript.
class ModelWithPolymorphicVector
{
public:
std::vector<SampleBaseClass*> items;
};
void CoherentBind(cohtml::Binder* binder, ModelWithPolymorphicVector* p)
{
if (auto type = binder->RegisterType("ModelWithPolymorphicVector", p))
{
type.Property("items", &ModelWithPolymorphicVector::items);
}
}
void CoherentBind(cohtml::Binder* binder, SampleBaseClass* p)
{
if (auto type = binder->RegisterType("SampleBaseClass", p))
{
type.Method("getType", &SampleBaseClass::GetType);
}
}
Custom handlers
The class cohtml::FunctorTraits
provides the following information about a function object:
- return type
- number of arguments
- whether it is a pointer to a member function
- the type of each argument (with const and references stripped)
To support custom handlers, use the following pattern:
class SquareCalculator
{
public:
void double operator()(double r) const
{
return r * r;
}
};
// provide support for SquareCalculator to FunctorTraits
namespace Coherent
{
namespace cohtml
{
template <>
struct FunctorTraits<SquareCalculator>
: FunctorTraits<double (double)>
{
};
}
}
Bind big integers
Binding BigInts is supported for numbers representable with up to 64 bits.