Changes between Version 10 and Version 11 of SimulationSyntax


Ignore:
Timestamp:
Dec 16, 2009, 7:54:01 PM (14 years ago)
Author:
Philip Taylor
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • SimulationSyntax

    v10 v11  
    1 [[TOC]]
    2 
    3 == Defining interfaces in C++ ==
    4 
    5 Think of a name for the component. We'll use "Example" in this example; replace it with your chosen name in all the filenames and code samples below.
    6 
    7 (If you copy-and-paste from the examples below, be aware that the [wiki:Coding_Conventions coding conventions] require indentation with tabs, not spaces, so make sure you get it right.)
    8 
    9 Create the file '''`simulation2/components/ICmpExample.h`''':
    10 {{{
    11 #!cpp
    12 /* Copyright (C) 2010 Wildfire Games.
    13  * ...the usual copyright header...
    14  */
    15 
    16 #ifndef INCLUDED_ICMPEXAMPLE
    17 #define INCLUDED_ICMPEXAMPLE
    18 
    19 #include "simulation2/Interface.h"
    20 
    21 ...any other forward declarations and includes you might need...
    22 
    23 /**
    24  * Documentation to describe what this interface and its associated component types are
    25  * for, and roughly how they should be used.
    26  */
    27 class ICmpExample : public IComponent
    28 {
    29 public:
    30     /**
    31      * Documentation for each method.
    32      */
    33     virtual int DoWhatever(int x, int y) = 0;
    34 
    35     ...
    36 
    37     DECLARE_INTERFACE_TYPE(Example)
    38 };
    39 
    40 #endif // INCLUDED_ICMPEXAMPLE
    41 }}}
    42 This defines the interface that C++ code will use to access components.
    43 
    44 Create the file '''`simulation2/components/ICmpExample.cpp`''':
    45 {{{
    46 #!cpp
    47 /* Copyright (C) 2010 Wildfire Games.
    48  * ...the usual copyright header...
    49  */
    50 
    51 #include "precompiled.h"
    52 
    53 #include "ICmpExample.h"
    54 
    55 #include "simulation2/InterfaceScripted.h"
    56 
    57 BEGIN_INTERFACE_WRAPPER(Example)
    58 DEFINE_INTERFACE_METHOD_2("DoWhatever", int, ICmpExample, DoWhatever, int, int)
    59 // DEFINE_INTERFACE_METHOD for all the other methods too
    60 END_INTERFACE_WRAPPER(Example)
    61 }}}
    62 This defines a !JavaScript wrapper, so that scripts can access methods of components implementing that interface. See a later section for details.
    63 
    64 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.
    65 
    66 Every interface must define a script wrapper, though in some cases they might contain no methods.
    67 
    68 Now update the file '''`simulation2/TypeList.h`''' and add
    69 {{{
    70 #!cpp
    71 INTERFACE(Example)
    72 }}}
    73 `TypeList.h` is used for various purposes - it will define the interface ID number `IID_Example` (in both C++ and JS), and it will hook the new interface into the interface registration system.
    74 
    75 Remember to run the `update-workspaces` script after adding or removing any source files, so that they will be added to the makefiles or VS projects.
    76 
    77 === Interface method script wrappers ===
    78 
    79 Interface methods are defined with the macro:
    80   `DEFINE_INTERFACE_METHOD_`''!NumberOfArguments''`("`''!MethodName''`", `''!ReturnType''`, ICmpExample, `''!MethodName''`, `''!ArgType0''`, `''!ArgType1, ...''`)`
    81 corresponding to the C++ method ''!ReturnType''` ICmpExample::`''!MethodName''`(`''!ArgType0''`, `''!ArgType1, ...''`)`
    82 
    83 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.)
    84 
    85 The two ''!MethodName''s 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 `ICmpExample::DoWhatever_wrapper()` method which does some extra conversions or checks or whatever.
    86 
    87 For methods exposed to scripts like this, the arguments should be pass-by-value. E.g. use `std::wstring` arguments, not `const std::wstring&`.
    88 
    89 To convert types between C++ and JS, `ToJSVal<ReturnType>` and `FromJSVal<ArgTypeN>` must be defined, as below.
    90 
    91 === Script type conversions ===
    92 
    93 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).
    94 
    95 To convert from a C++ type `T` to a JS value, define:
    96 {{{
    97 #!cpp
    98 template<> jsval ScriptInterface::ToJSVal<T>(JSContext* cx, T const& val)
    99 {
    100     ...
    101 }
    102 }}}
    103 Use the standard !SpiderMonkey JSAPI functions to do the conversion (possibly calling `ToJSVal` recursively). 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).
    104 
    105 To convert from a JS value to a C++ type `T`, define:
    106 {{{
    107 #!cpp
    108 template<> bool ScriptInterface::FromJSVal<T>(JSContext* cx, jsval v, T& out)
    109 {
    110     ...
    111 }
    112 }}}
    113 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).
    114 
    115 == Defining component types in C++ ==
    116 
    117 Now we want to implement the `Example` interface. We need a name for the component type - if there's only ever going to be one implementation of the interface, we might as well call it `Example` too. If there's going to be more than one, they should have distinct names like `ExampleStatic` and `ExampleMobile` etc.
    118 
    119 Create '''`simulation2/components/CCmpExample.cpp`''':
    120 {{{
    121 #!cpp
    122 ... copyright header ...
    123 
    124 #include "precompiled.h"
    125 
    126 #include "Component.h"
    127 #include "ICmpExample.h"
    128 
    129 ... any other includes needed ...
    130 
    131 class CCmpExample : public ICmpExample
    132 {
    133 public:
    134     static void ClassInit(CComponentManager& componentManager)
    135         ...
    136 
    137     DEFAULT_COMPONENT_ALLOCATOR(Example)
    138 
    139     ... member variables ...
    140 
    141     virtual void Init(const CSimContext& context, const CParamNode& paramNode)
    142         ...
    143 
    144     virtual void Deinit(const CSimContext& context)
    145         ...
    146 
    147     virtual void Serialize(ISerializer& serialize)
    148         ...
    149 
    150     virtual void Deserialize(const CSimContext& context, const CParamNode& paramNode, IDeserializer& deserialize)
    151         ...
    152 
    153     virtual void HandleMessage(const CSimContext&, const CMessage& msg)
    154         ...
    155 
    156     ... Implementation of interface functions: ...
    157     virtual int DoWhatever(int x, int y)
    158     {
    159         return x+y;
    160     }
    161 };
    162 
    163 REGISTER_COMPONENT_TYPE(IID_Example, Example)
    164 }}}
    165 The only optional method is `HandleMessage` - all others must be defined.
    166 
    167 Update the file '''`simulation2/TypeList.h`''' and add
    168 {{{
    169 #!cpp
    170 COMPONENT(Example)
    171 }}}
    172 
    173 === Message handling ===
    174 
    175 First you need to register for all the message types you want to receive, in `ClassInit`:
    176 {{{
    177 #!cpp
    178 static void ClassInit(CComponentManager& componentManager)
    179 {
    180     componentManager.SubscribeToMessageType(CID_Example, MT_Update);
    181     ...
    182 }
    183 }}}
    184 (`CID_Example` is derived from the name of the component type, ''not'' the name of the interface.)
    185 
    186 Then you need to respond to the messages in `HandleMessage`:
    187 {{{
    188 #!cpp
    189 virtual void HandleMessage(const CSimContext& context, const CMessage& msg)
    190 {
    191     switch (msg.GetType())
    192     {
    193     case MT_Update:
    194     {
    195         const CMessageUpdate& msgData = static_cast<const CMessageUpdate&> (msg);
    196         Update(msgData.turnLength); // or whatever processing you want to do
    197         break;
    198     }
    199     }
    200 }
    201 }}}
    202 The `CMessage` structures are defined in `simulation2/MessageTypes.h`. Be very careful that you're casting `msg` to the right type.
    203 
    204 === Component creation ===
    205 
    206 Component type instances go through one of two lifecycles:
    207 {{{
    208 #!cpp
    209 CCmpExample();
    210 Init(context, paramNode);
    211 // any sequence of HandleMessage and Serialize and interface methods
    212 Deinit(context);
    213 ~CCmpExample();
    214 }}}
    215 {{{
    216 #!cpp
    217 CCmpExample();
    218 Deserialize(context, paramNode, deserialize);
    219 // any sequence of HandleMessage and Serialize and interface methods
    220 Deinit(context);
    221 ~CCmpExample();
    222 }}}
    223 
    224 The order of `Init`/`Deserialize`/`Deinit` between components is undefined, so they must not rely on other entities or components already existing; ''except'' that the `SYSTEM_ENTITY` is created before anything else and therefore may be used.
    225 
    226 The same `context` object will be used in all these calls. (The component could safely store it in a `CSimContext* m_Context` member if necessary.)
    227 
    228 In a typical component:
    229 
    230  * The constructor should do very little, other than perhaps initialising some member variables - usually the default constructor is fine so there's no need to write one.
    231  * `Init` should parse the `paramNode` (the data from the entity template) and store any needed data in member variables.
    232  * `Deserialize` should often explicitly call `Init` first (to load the original template data), and then read any instance-specific data from the deserializer.
    233  * `Deinit` should clean up any resources allocated by `Init`/`Deserialize`.
    234  * The destructor should clean up any resources allocated by the constructor - usually there's no need to write one.
    235 
    236 == Allowing interfaces to be implemented in JS ==
    237 
    238 If we want to allow both C++ and JS implementations of `ICmpExample`, we need to define a special component type that proxies all the C++ methods to the script. Add the following to '''`ICmpExample.cpp`''':
    239 {{{
    240 #!cpp
    241 #include "simulation2/scripting/ScriptComponent.h"
    242 
    243 ...
    244 
    245 class CCmpExampleScripted : public ICmpExample
    246 {
    247 public:
    248     DEFAULT_SCRIPT_WRAPPER(ExampleScripted)
    249 
    250     virtual int DoWhatever(int x, int y)
    251     {
    252         return m_Script.Call<int> ("DoWhatever", x, y);
    253     }
    254 };
    255 
    256 REGISTER_COMPONENT_SCRIPT_WRAPPER(IID_Example, ExampleScripted)
    257 }}}
    258 
    259 Then add to '''`TypeList.h`''':
    260 {{{
    261 #!cpp
    262 COMPONENT(ExampleScripted)
    263 }}}
    264 
    265 `m_Script.Call` takes the return type as a template argument, then the name of the JS function to call and the list of parameters. You could do extra conversion work before calling the script, if necessary. You need to make sure the types are handled by `ToJSVal` and `FromJSVal` (as discussed before) as appropriate.
    266 
    267 == Defining component types in JS ==
    268 
    269 Now we want a JS implementation of `ICmpExample`. Think up a new name for this component, like `ExampleTwo` (but more imaginative). Then write '''`binaries/data/mods/public/simulation/comonents/ExampleTwo.js`''':
    270 {{{
    271 #!js
    272 function ExampleTwo() {}
    273 
    274 ExampleTwo.prototype.Init = function() {
    275     ...
    276 };
    277 
    278 ExampleTwo.prototype.Deinit = function() {
    279     ...
    280 };
    281 
    282 ExampleTwo.prototype.OnUpdate = function(msg) {
    283     ...
    284 };
    285 
    286 Engine.RegisterComponentType(IID_Example, "ExampleTwo", ExampleTwo);
    287 }}}
    288 
    289 This uses JS's ''prototype'' system to create what is effectively a class, called `ExampleTwo`. (If you wrote `new ExampleTwo()`, then JS would construct a new object which inherits from `ExampleTwo.prototype`, and then would call the `ExampleTwo` function with `this` set to the new object. "Inherit" here means that if you read a property (or method) of the object, which is not defined in the object, then it will be read from the prototype instead.)
    290 
    291 `Engine.RegisterComponentType` tells the engine to start using the JS class `ExampleTwo`, exposed (in template files etc) with the name `"ExampleTwo"`, and implementing the interface ID `IID_Example` (i.e. the `ICmpExample` interface).
    292 
    293 The `Init` and `Deinit` functions are optional. Unlike C++, there are no `Serialize`/`Deserialize` functions - each JS component instance is automatically serialized and restored. (This automatic serialization restricts what you can store as properties in the object - e.g. you cannot store function closures, because they're too hard to serialize. The details should be documented on some other page eventually.)
    294 
    295 Instead of `ClassInit` and `HandleMessage`, you simply add functions of the form `On`''`MessageType`''. When you call `RegisterComponentType`, it will find all such functions and automatically subscribe to the messages. The `msg` parameter is usually a straightforward mapping of the relevant `CMessage` class onto a JS object (e.g. `OnUpdate` can read `msg.turnLength`).
    296 
    297 == Defining a new message type ==
    298 
    299 ...
     1Moved to http://svn.wildfiregames.com/docs/writing-components.html