1.4.0.3
Prysm
Communicating with the game

Prysm interprets and executes JavaScript code that can be used for scripting the UI. The execution itself is performed by a JavaScript virtual machine. Prysm uses different VMs on different platforms to provide the best available option.

  • On platforms where code JIT compilation is allowed (Windows and Android), Prysm uses V8
  • On iOS it uses the ChakraCore
  • On all other platforms, JavaScriptCore is used.

JavaScriptCore is planned to be replaced with ChakraCore 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

EngineTrigger_ToUI.png

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;
});

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);
Warning
The first argument of the macro is a compile time token and can be used for creating new tokens during compilation. The second argument is a runtime string literal and is valid within the trigger event scope. Using it as a compile-time token will always return "name".
Note
In order for the binding to work, the cohtml.js library must be included in the HTML page.

JavaScript triggering C++

EngineTrigger_ToGame.png

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 Prysm, supply additional specializations and overloads.

Call Handlers

Promises are used to return results from C++ to JavaScript. Prysm implements ECMAScript 6 promises

EngineCall.png

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;
});
Note
For better performance it is recommended to use BindCall and engine.call with a single handler and RegisterForEvent and engine.trigger with more that one handlers.

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:

CxxPromise DoStuff()
{
CxxPromise result;
startWork(result);
return result;
)
void OnWorkDone(const CxxPromise& promise)
{
promise.Resolve(view, 42);
)

Special Events

When the View is ready to receive binding events and manipulate bound models, the Ready event is triggered.

engine.whenReady

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'); });
Note
The event Ready will be deprecated in the future, so it is recommended engine.whenReady is used instead.

Exposing C++ Types

To be able to use custom C++ types as arguments or results for 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)
;
}
}
Note
When exposing custom properties with the .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 to TriggerEvent
  • 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 Player is dedicated only for the UI, add the declaration of CoherentBind for Player in the Player header. This way the overload will be visible everywhere Player is visible.
  • If Player is generic game type - add a PlayerBinding.h or a MySubsystemBindings.h header with the declaration of CoherentBind for Player (or for all the types in the particular game subsystem). After that make sure to include the header where Player is used with Cohtml.
Note
If namespaces are used, then the 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.
Warning
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 in a similar way 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 Players 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 in 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 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! Prysm 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 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.

Prysm 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

Prysm 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.

Warning
Destroying or moving a C++ object exposed by reference to another location, without notifying the JavaScript universe, will cause undefined behavior when the variable is used by JavaScript.

The API to expose a C++ object by reference is the same as normal values. The difference is that in order to expose a reference, the object has to be wrapped in a cohtml::ByRef holder.

Once an object is exposed by reference to JavaScript, any subsequent TriggerEvents using ByRef with the same object will result in the same JavaScript variable send to JavaScript. This allows for 1-to-1 mapping between the C++ and JavaScript objects.

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.

Note
When a complex object is exposed by references, all of its subobjects are also exposed by reference. This means that the JavaScript should avoid keeping references to them.

Here is a example usage of exposing objects by reference:

struct Nameplate
{
float X;
float Y;
float Health;
};
// 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)
;
}
}
class NameplateManager
{
public:
void CreateNameplates(unsigned count)
{
// Create and fill nameplates
// ...
for (auto i = 0; i < count; ++i)
{
// Send a reference to the nameplate to JavaScript
m_View->TriggerEvent("nameplates.create",
cohtml::ByRef(&m_Nameplates[i]));
}
}
void DestroyNameplates()
{
for (auto& nameplate: m_Nameplates)
{
// Notify the JavaScript universe that this reference is no
// longer valid. All accessses to the variable will throw
// exception in JavaScript.
m_View->DestroyExposedObject(&nameplate);
}
}
// The game requires to modify a nameplate, so we store it in the
// modified list in order to update the UI later
Nameplate& ModifyNameplate(unsigned id)
{
auto& nameplate = m_Nameplates[id];
m_Modified.push_back(&nameplate);
return nameplate;
}
// Update the modified nameplates since the last update
// Expose the nameplates by reference. This way the JavaScript
// will receive the very same JavaScript object as the one created.
void Update()
{
for (auto modified : m_Modified)
{
m_View->TriggerEvent("nameplates.update",
cohml::ByRef(modified));
}
m_Modified.clear();
}
private:
cohtml::View* m_View;
// Beware that the vector may grow and relocate the nameplates.
// If this happens all the exposed references must be destroyed and
// recreated.
std::vector<Nameplate> m_Nameplates;
std::vector<Nameplate*> m_Modified;
};

To take advantage of this C++ classes the JavaScript side can do:

// 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.health.textContent = this.Health.toFixed();
}
function createNameplate(nameplate) {
// create the DOM elements for the nameplate
var plate = createNameplateDOM();
// extend the C++ nameplate with additional JavaScript values - the DOM
// elements that has to be updated when the nameplate is changed
nameplate.position = plate.style;
nameplate.health = plate.childNodes.item(1);
// extend the C++ nameplate with JavaScript function for the update
nameplate.update = updateNameplate();
nameplate.update();
document.body.appendChild(plate);
}
engine.on('nameplates.create', createNameplate);
engine.on('nameplates.update', function (nameplate) {
// This the same JavaScript value, so it already has an update method
// and references to the DOM elements
nameplate.update();
}

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();
}
}