Coherent GT allows for easy communication between your application and the UI via several mechanisms - this document outlines the simplemost option based on message passing. This option requires writing some amount of JS. If you'd rather not write JS, see Data Binding.
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;});
Then we trigger the event in C++, using the Coherent::UIGT::View::TriggerEvent method:
// set player score to 10000view->TriggerEvent('PlayerScoreChanged', 10000);
There are also global functions in the Coherent::UIGT namespace which can be used such as:
Coherent::UIGT::TriggerEvent(view, 'ScoreChanged', 10000);
Every TriggerEvent
call starts with a C++ macro:
COHERENTGT_TRIGGER_EVENT_SCOPE(TriggerEvent_N_Params, RuntimeName);
By default this macro does nothing and is compiled out. You can, however, add custom code there by defining COHERENTGT_TRIGGER_EVENT_SCOPE
before it's used in View.h
. For example, you can add some profiling code such as:
#define COHERENTGT_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);
Note that in order for the binding to work you need to include the coherent.js library in the head section of your HTML page.
engine
, see JavaScript .Registering C++ functions for events triggered by JavaScript should happen in the handler of the Coherent::UIGT::ViewListener::OnReadyForBindings event.
Event handlers are registered with the Coherent::UIGT::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;{public:{m_View->RegisterForEvent("OnQuitClicked",Coherent::UIGT::MakeHandler(&g_Game, &Game::Quit));}};
Triggering the event from JavaScript looks like:
engine.on('OnQuitClicked', function () {ShowMessage('Bye');});// using jQuery to simplify the sample$('#QuitButton').click(function () {// this will execute both Game::Quit in C++// and ShowMessage('Bye') in JavaScriptengine.trigger('OnQuitClicked');});
Coherent::UIGT::MakeHandler
uses template argument deduction to guess the signature of the handler. This requires several specializations of Coherent::UIGT::FunctorTraits
and overloads of Coherent::UIGT::EventHandler::InvokeStub. To extend the number of arguments for event handlers supported by Coherent GT, you have to add additional specializations and overloads.
Call handlers are registered with the Coherent::UIGT::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;{public:{m_View->BindCall("getPlayerName", Coherent::UIGT::MakeHandler(&g_Game,&Game::GetPlayerName));}};
To get the player name in the view is:
// call 'Game::GetPlayerName' with callback for the resultengine.call('getPlayerName').then(function (name) {var playerName = document.getElementById('playerName');playerName.innerHTML = name;});
To be able to use your C++ types as arguments or results for event and call handlers, the C++ type must be exposed to Coherent GT. To expose a C++ type to Coherent GT use the following pattern:
class Player{public:std::string Name;double GetScore() const;void SetScore(double);};// called by Coherent GT when an instance of Player goes through// the C++ / JavaScript boundaryvoid CoherentBind(Coherent::UIGT::Binder* binder, Player* player){{type.Property("name", &Player::Name).Property("score", &Player::GetScore, &Player::SetScore);}}
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:
Player
instance is used as an argument to TriggerEvent
Player
as an argumentPlayer
to JavaScriptIn case the CoherentBind
overload for the Player
class is not visible, you will get the following compilation error:
include\coherent\uigt\binding\binding.h(57): error C2338: Coherent::UIGT::Overload_CoherentBind_For_Your_Type<T*> 1> include\coherent\uigt\binding\binding.h(191) : see reference to function template instantiation 'void CoherentBind<T>(Coherent::UIGT::Binder *,T *)' being compiled 1> with 1> [ 1> T=Player 1> ]
Going down the template instantiation stack, you can find where you are using Player
without the CoherentBind
overload visible.
You can define the CoherentBind
overload for Player
in any C++ source (.cpp) file, but you'll need to have a declaration for it, that is included everywhere Player
is exposed to Coherent GT.
Depending on the structure of your project, you may consider the following patterns:
Player
is dedicated only for the UI - then add the declaration of CoherentBind for Player
in the Player
header. This way the overload will be visible everywhere Player
is visible.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 Coherent GT.CoherentBind
overload for Player
has to be either in the namespace of Player
or in the Coherent::UIGT
namespace. This way it will be found using argument dependent lookup.Coherent GT has built-in support for most STL containers and std::
classes.
C++ Type | JavaScript Type | Header |
---|---|---|
std::string | String | <Coherent\UIGT\Binding\String.h> |
std::vector | Array | <Coherent\UIGT\Binding\Vector.h> |
std::map | Object | <Coherent\UIGT\Binding\Map.h> |
std::pair | Object | <Coherent\UIGT\Binding\Pair.h> |
C style array | Array | <Coherent\UIGT\Binding\Array.h> |
Support for additional containers can be added in a similar way.
Registering type Player
triggering events in both directions with instances of Player
as argument and as a result of a call handler.
class Game{public:void Start(){m_View->TriggerEvent("StartWithPlayer", m_Player);}private:Player m_Player;} g_Game;
Then in JavaScript, we receive an 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 synchronised 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;});
If you want to call C++ handler with an instance of Player
created in JavaScript there is one important detail - the object must have a property __Type
with value Player (the same name of the type we gave to Coherent::UIGT::Binder::RegisterType in CoherentBind
for Player
. Otherwise Coherent GT cannot treat the object as an instance of Player
.
$('#doCreatePlayer').click(function () {var player = {__Type: 'Player', // set the type namename: $('#playerName').val(),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 playerreturn true;}else{// sorry, no spaces in player namereturn false;}}
For some calls it's possible that there is no meaningful value to return. For example -
Item Player::GetItem(int slot){if (HasItemAt(slot)){return GetItemAt(slot);}else{// notify the view that there is not item at this slotm_View->SetScriptError(Coherent::UIGT::SCE_NoResult, "no item at slot");return Item();}}
engine.call('GetItem', slot).then(function (item) {ShowMessage('Item at slot ' + slot + ' costs ' + item.Price);},// called when there is no item at this slotfunction (errorType, message) {console.log('could not get item at slot ' + slot);}});
Coherent GT 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 Coherent::UIGT::ByRef
or Coherent::UIGT::ByRefThreadSafe
holder. The only difference between Coherent::UIGT::ByRef
and Coherent::UIGT::ByRefThreadSafe
is that ByRefThreadSafe is guaranteed to work correctly during a simultaneous execution. Which means that ByRef is faster, but must be used carefully because of various concurrency issues (e.g. race conditions).
Once an object is exposed by reference to JavaScript, any subsequent TriggerEvent
calls using ByRef
or ByRefThreadSafe
with the same object will result in the same JavaScript variable sent 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 Coherent::UIGT::View::RemoveExposedInstance
method with the address of the object being destroyed. It will remove the reference from the JavaScript values and make any attempt to access the properties of the object throw an exception in JavaScript. If you expose an array by ref you need to call Coherent::UIGT::View::RemoveExposedArray instead, with the address of the first element as an argument.
Coherent::UIGT::View::RemoveExposedInstance
method is asynchronous, so the JavaScript code should have stopped using the C++ object, before it can be safely removed.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(Coherent::UIGT::Binder* binder, 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 JavaScriptm_View->TriggerEvent('nameplates.create',Coherent::UIGT::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->RemoveExposedInstance(&nameplate);}}// The game requires to modify a nameplate, so we store it in the// modified list in order to update the UI laterNameplate& ModifyNameplate(unsigned id){auto& nameplate = m_Nameplates[id];m_Modified.push_back(&nameplate);return nameplate;}// Update the modified nameplates since the last update// Again 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", Coherent::UIGT::ByRef(modified));}m_Modified.clear();}private:Coherent::UIGT::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 objectfunction updateNameplate() {this.position.left = this.X + 'vw'; // will take the current X memberthis.position.top = this.Y + 'vh'; // will take the current Y member// update the health with the current value, without fractional partthis.health.textContent = this.Health.toFixed();}function createNameplate(nameplate) {// create the DOM elements for the nameplatevar plate = createNameplateDOM();// extend the C++ nameplate with additional JavaScript values - the DOM// elements that has to be updated when the nameplate is changednameplate.position = plate.style;nameplate.health = plate.childNodes.item(1);// extend the C++ nameplate with JavaScript function for the updatenameplate.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 elementsnameplate.update();}
When a C++ object is exposed by reference to JavaScript, its methods also might be called from JavaScript.
class Game{void Initialize(Coherent::UIGT::View* view){// this game object will be accessing as the global "game" variable// in JavaScript}~Game(){// make sure JavaScript won't crash usm_View->RemoveExposedInstance(this);}bool LoadSave(const char* name);Quit();};void CoherentBind(Coherent::UIGT::Binder* binder, 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('click', function () { game.quit(); }, false);// ...function LoadGame(name) {if (!game.loadSave(name)) { // calls the C++ methodShowError();}}
Coherent GT provides a default version of CoherentBind which will error out if you don't provide a template specialization of CoherentBind for your type. In some cases you might want to implement the default by yourself (e.g. binding all types via the reflection system of your engine). To do that you need to declare a template specialization for Coherent::UIGT::IsDefaultBindEnabled
structure in the way shown below.
For example let's declare DisableDefaultBinding
specialization for user-defined type Player
:
namespace Coherent{namespace UIGT{template<>struct DisableDefaultBinding<Player, Player> : TrueType{};}}
In the case you need to write a more generic version, you can use the second template parameter where you can use SFINAE and enable_if techniques. If you wish to disable default binding for classes and unions you can write something similar to this:
namespace Coherent{namespace UIGT{template<typename T>struct DisableDefaultBinding<T, typename std::enable_if<std::is_class<T>::value || std::is_union<T>::value, T>::type> : TrueType{};}}