Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
SimulationDocs.h
Go to the documentation of this file.
1 /* Copyright (C) 2013 Wildfire Games.
2  * This file is part of 0 A.D.
3  *
4  * 0 A.D. is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * 0 A.D. is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 /**
19 
20 @page writing-components How to write components
21 
22 <i>See the <a href="http://trac.wildfiregames.com/wiki/TDD_Simulation">Trac wiki</a> for more documentation about this system.</i>
23 
24 <!--
25  egrep '@(sub)*section' source/simulation2/docs/SimulationDocs.h|sed 's/@//; s/section/- @ref/; s/^sub/ /g; s/\(- \S* \S*\).*$/\1/'
26 -->
27 
28 - @ref defining-cpp-interfaces
29 - @ref script-wrapper
30 - @ref script-conversions
31 - @ref defining-cpp-components
32  - @ref messages
33  - @ref component-creation
34  - @ref schema
35 - @ref allowing-js-interfaces
36 - @ref defining-js-components
37 - @ref defining-js-interfaces
38 - @ref defining-cpp-message
39 - @ref defining-js-message
40 - @ref communication
41  - @ref message-passing
42  - @ref query-interface
43 - @ref testing
44  - @ref testing-cpp
45  - @ref testing-js
46 
47 @section defining-cpp-interfaces Defining interfaces in C++
48 
49 Think of a name for the component. We'll use "Example" in this example; replace
50 it with your chosen name in all the filenames and code samples below.
51 
52 (If you copy-and-paste from the examples below, be aware that the
53 <a href="http://trac.wildfiregames.com/wiki/Coding_Conventions">coding conventions</a>
54 require indentation with tabs, not spaces, so make sure you get it right.)
55 
56 Create the file @b simulation2/components/ICmpExample.h:
57 
58 @include ICmpExample.h
59 
60 This defines the interface that C++ code will use to access components.
61 
62 Create the file @b simulation2/components/ICmpExample.cpp:
63 
64 @include ICmpExample.cpp
65 
66 This defines a JavaScript wrapper, so that scripts can access methods of components
67 implementing that interface. See @ref script-wrapper for details.
68 
69 This wrapper should only contain methods that are safe to access from simulation scripts:
70 they must not crash (even with invalid or malicious inputs), they must return deterministic
71 results, etc.
72 Methods that are intended for use solely by C++ should not be listed here.
73 
74 Every interface must define a script wrapper with @c BEGIN_INTERFACE_WRAPPER,
75 though in some cases they might be empty and not define any methods.
76 
77 Now update the file simulation2/TypeList.h and add
78 
79 @code
80 INTERFACE(Example)
81 @endcode
82 
83 TypeList.h is used for various purposes - it will define the interface ID number @c IID_Example
84 (in both C++ and JS), and it will hook the new interface into the interface registration system.
85 
86 Remember to run the @c update-workspaces script after adding or removing any source files,
87 so that they will be added to the makefiles or VS projects.
88 
89 
90 
91 @section script-wrapper Interface method script wrappers
92 
93 Interface methods are defined with the macro:
94 
95  <code>DEFINE_INTERFACE_METHOD_<var>NumberOfArguments</var>(<var>"MethodName"</var>,
96  <var>ReturnType</var>, ICmpExample, <var>MethodName</var>, <var>ArgType0</var>, <var>ArgType1</var>, ...)</code>
97 
98 corresponding to the C++ method
99 <code><var>ReturnType</var> ICmpExample::<var>MethodName</var>(<var>ArgType0</var>, <var>ArgType1</var>, ...)</code>
100 
101 For methods exposed to scripts like this, the arguments should be simple types and pass-by-value.
102 E.g. use <code>std::wstring</code> arguments, not <code>const std::wstring&</code>.
103 
104 The arguments and return types will be automatically converted between C++ and JS values.
105 To do this, @c ToJSVal<ReturnType> and @c FromJSVal<ArgTypeN> must be defined (if they
106 haven't already been defined for another method), as described below.
107 
108 The two <var>MethodName</var>s don't have to be the same - in rare cases you might want to expose it as
109 @c DoWhatever to scripts but link it to the @c ICmpExample::DoWhatever_wrapper() method
110 which does some extra conversions or checks or whatever.
111 
112 There's a small limit to the number of arguments that are currently supported - if you need more,
113 first try to save yourself some pain by using fewer arguments, otherwise you'll need to add a new
114 macro into simulation2/system/InterfaceScripted.h and increase @ref SCRIPT_INTERFACE_MAX_ARGS in scriptinterface/ScriptInterface.h.
115 (Not sure if anything else needs changing.)
116 
117 
118 
119 @section script-conversions Script type conversions
120 
121 In most cases you can skip this section.
122 But if you define a script-accessible method with new types without having defined conversions,
123 you'll probably get mysterious linker errors that mention @c ToJSVal or @c FromJSVal.
124 First, work out where the conversion should be defined.
125 Basic data types (integers, STL containers, etc) go in scriptinterface/ScriptConversions.cpp.
126 Non-basic data types from the game engine typically go in simulation2/scripting/EngineScriptConversions.cpp.
127 (They could go in different files if that turns out to be cleaner - it doesn't matter where they're
128 defined as long as the linker finds them).
129 
130 To convert from a C++ type @c T to a JS value, define:
131 
132 @code
133 template<> jsval ScriptInterface::ToJSVal<T>(JSContext* cx, T const& val)
134 {
135  ...
136 }
137 @endcode
138 
139 Use the standard <a href="https://developer.mozilla.org/en/JSAPI_Reference">SpiderMonkey JSAPI functions</a>
140 to do the conversion (possibly calling @c ToJSVal recursively).
141 On error, you should return @c JSVAL_VOID (JS's @c undefined value) and probably report an error message somehow.
142 Be careful about JS garbage collection (don't let it collect the objects you're constructing before you return them).
143 
144 To convert from a JS value to a C++ type @c T, define:
145 
146 @code
147 template<> bool ScriptInterface::FromJSVal<T>(JSContext* cx, jsval v, T& out)
148 {
149  ...
150 }
151 @endcode
152 
153 On error, return @c false (doesn't matter what you do with @c out).
154 On success, return @c true and put the value in @c out.
155 Still need to be careful about garbage collection (@c v is rooted, but it might have getters
156 that execute arbitrary code and return unrooted values when you access properties,
157 so don't let them be collected before you've finished using them).
158 
159 
160 
161 @section defining-cpp-components Defining component types in C++
162 
163 Now we want to implement the @c Example interface.
164 We need a name for the component type - if there's only ever going to be one implementation of the interface,
165 we might as well call it @c Example too.
166 If there's going to be more than one, they should have distinct names like @c ExampleStatic and @c ExampleMobile etc.
167 
168 Create @b simulation2/components/CCmpExample.cpp:
169 
170 \include CCmpExample.cpp
171 
172 The only optional methods are @c HandleMessage and @c GetSchema - all others must be defined.
173 
174 Update the file simulation2/TypeList.h and add:
175 
176 @code
177 COMPONENT(Example)
178 @endcode
179 
180 
181 @subsection messages Message handling
182 
183 First you need to register for all the message types you want to receive, in @c ClassInit:
184 
185 @code
186 static void ClassInit(CComponentManager& componentManager)
187 {
188  componentManager.SubscribeToMessageType(CID_Example, MT_Update);
189  ...
190 }
191 @endcode
192 
193 (@c CID_Example is derived from the name of the component type, @em not the name of the interface.)
194 
195 You can also use SubscribeGloballyToMessageType, to intercept messages sent with PostMessage
196 that are targeted at a @em different entity. (Typically this is used by components that want
197 to hear about all MT_Destroy messages.)
198 
199 Then you need to respond to the messages in @c HandleMessage:
200 
201 @code
202 virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
203 {
204  switch (msg.GetType())
205  {
206  case MT_Update:
207  {
208  const CMessageUpdate& msgData = static_cast<const CMessageUpdate&> (msg);
209  Update(msgData.turnLength); // or whatever processing you want to do
210  break;
211  }
212  }
213 }
214 @endcode
215 
216 The CMessage structures are defined in simulation2/MessageTypes.h. Be very careful that you're casting @c msg to the right type.
217 
218 
219 @subsection component-creation Component creation
220 
221 Component type instances go through one of two lifecycles:
222 
223 @code
224 CCmpExample();
225 Init(paramNode);
226 // any sequence of HandleMessage and Serialize and interface methods
227 Deinit();
228 ~CCmpExample();
229 @endcode
230 
231 @code
232 CCmpExample();
233 Deserialize(paramNode, deserialize);
234 // any sequence of HandleMessage and Serialize and interface methods
235 Deinit();
236 ~CCmpExample();
237 @endcode
238 
239 The order of <code>Init</code>/<code>Deserialize</code>/<code>Deinit</code> between entities is mostly undefined,
240 so they must not rely on other entities or components already existing; @em except that the SYSTEM_ENTITY is
241 created before anything else and therefore may be used, and that the components for a single entity will be
242 processed in the order determined by TypeList.h.
243 
244 In a typical component:
245 
246 - The constructor should do very little, other than perhaps initialising some member variables -
247  usually the default constructor is fine so there's no need to write one.
248 - @c Init should parse the @c paramNode (the data from the entity template) and store any needed data in member variables.
249 - @c Deserialize should often explicitly call @c Init first (to load the original template data), and then read any instance-specific data from the deserializer.
250 - @c Deinit should clean up any resources allocated by @c Init / @c Deserialize.
251 - The destructor should clean up any resources allocated by the constructor - usually there's no need to write one.
252 
253 
254 @subsection schema Component XML schemas
255 
256 The @c paramNode passed to @c Init is constructed from XML entity template definition files.
257 Components should define a schema, which is used for several purposes:
258 
259 - Documentation of the XML structure expected by the component.
260 - Automatic error checking that the XML matches the expectation, so the component doesn't have to do error checking itself.
261 - (Hopefully at some point in the future) Automatic generation of editing tool UI.
262 
263 @c GetSchema must return a Relax NG fragment, which will be used to construct a single global schema file.
264 (You can run the game with the @c -dumpSchema command-line argument to see the schema).
265 The <a href="http://relaxng.org/tutorial-20011203.html">official tutorial</a> describes most of the details
266 of the RNG language.
267 
268 In simple cases, you would write something like:
269 @code
270 static std::string GetSchema()
271 {
272  return
273  "<element name='Name'><text/></element>"
274  "<element name='Height'><data type='nonNegativeInteger'/></element>"
275  "<optional>"
276  "<element name='Eyes'><empty/></element>"
277  "</optional>";
278  }
279 }
280 @endcode
281 i.e. a single string (C++ automatically concatenates the quoted lines) which defines a list of elements,
282 corresponding to an entity template XML file like:
283 @code
284 <Entity>
285  <Example>
286  <Name>Barney</Name>
287  <Height>235</Height>
288  <Eyes/>
289  </Example>
290  <!-- ... other components ... -->
291 </Entity>
292 @endcode
293 
294 In the schema, each <code>&lt;element></code> has a name and some content.
295 The content will typically be one of:
296 - <code>&lt;empty/></code>
297 - <code>&lt;text/></code>
298 - <code>&lt;data type='boolean'/></code>
299 - <code>&lt;data type='decimal'/></code>
300 - <code>&lt;data type='nonNegativeInteger'/></code>
301 - <code>&lt;data type='positiveInteger'/></code>
302 - <code>&lt;ref name='nonNegativeDecimal'/></code>
303 - <code>&lt;ref name='positiveDecimal'/></code>
304 
305 (The last two are slightly different since they're not standard data types.)
306 
307 Elements can be wrapped in <code>&lt;optional></code>.
308 Groups of elements can be wrapped in <code>&lt;choice></code> to allow only one of them.
309 The content of an <code>&lt;element></code> can be further nested elements, but note that
310 elements may be reordered when loading an entity template:
311 if you specify a sequence of elements it should be wrapped in <code>&lt;interleave></code>,
312 so the schema checker will ignore reorderings of the sequence.
313 
314 For early development of a new component, you can set the schema to <code>&lt;ref name='anything'/></code> to allow any content.
315 If you don't define @c GetSchema, then the default is <code>&lt;empty/></code> (i.e. there must be no elements).
316 
317 
318 @section allowing-js-interfaces Allowing interfaces to be implemented in JS
319 
320 If we want to allow both C++ and JS implementations of @c ICmpExample,
321 we need to define a special component type that proxies all the C++ methods to the script.
322 Add the following to @b ICmpExample.cpp:
323 
324 @code
325 #include "simulation2/scripting/ScriptComponent.h"
326 
327 // ...
328 
329 class CCmpExampleScripted : public ICmpExample
330 {
331 public:
332  DEFAULT_SCRIPT_WRAPPER(ExampleScripted)
333 
334  virtual int DoWhatever(int x, int y)
335  {
336  return m_Script.Call<int> ("DoWhatever", x, y);
337  }
338 };
339 
340 REGISTER_COMPONENT_SCRIPT_WRAPPER(ExampleScripted)
341 @endcode
342 
343 Then add to TypeList.h:
344 
345 @code
346 COMPONENT(ExampleScripted)
347 @endcode
348 
349 @c m_Script.Call takes the return type as a template argument,
350 then the name of the JS function to call and the list of parameters.
351 You could do extra conversion work before calling the script, if necessary.
352 You need to make sure the types are handled by @c ToJSVal and @c FromJSVal (as discussed before) as appropriate.
353 
354 
355 
356 @section defining-js-components Defining component types in JS
357 
358 Now we want a JS implementation of ICmpExample.
359 Think up a new name for this component, like @c ExampleTwo (but more imaginative).
360 Then write @b binaries/data/mods/public/simulation/components/ExampleTwo.js:
361 
362 @code
363 function ExampleTwo() {}
364 
365 ExampleTwo.prototype.Schema = "<ref name='anything'/>";
366 
367 ExampleTwo.prototype.Init = function() {
368  ...
369 };
370 
371 ExampleTwo.prototype.Deinit = function() {
372  ...
373 };
374 
375 ExampleTwo.prototype.OnUpdate = function(msg) {
376  ...
377 };
378 
379 Engine.RegisterComponentType(IID_Example, "ExampleTwo", ExampleTwo);
380 @endcode
381 
382 This uses JS's @em prototype system to create what is effectively a class, called @c ExampleTwo.
383 (If you wrote <code>new ExampleTwo()</code>, then JS would construct a new object which inherits from
384 @c ExampleTwo.prototype, and then would call the @c ExampleTwo function with @c this set to the new object.
385 "Inherit" here means that if you read a property (or method) of the object, which is not defined in the object,
386 then it will be read from the prototype instead.)
387 
388 @c Engine.RegisterComponentType tells the engine to start using the JS class @c ExampleTwo,
389 exposed (in template files etc) with the name "ExampleTwo", and implementing the interface ID @c IID_Example
390 (i.e. the ICmpExample interface).
391 
392 The @c Init and @c Deinit functions are optional. Unlike C++, there are no @c Serialize/Deserialize functions -
393 each JS component instance is automatically serialized and restored.
394 (This automatic serialization restricts what you can store as properties in the object - e.g. you cannot store function closures,
395 because they're too hard to serialize. This will serialize Strings, numbers, bools, null, undefined, arrays of serializable
396 values whose property names are purely numeric, objects whose properties are serializable values. Cyclic structures are allowed.)
397 
398 Instead of @c ClassInit and @c HandleMessage, you simply add functions of the form <code>On<var>MessageType</var></code>.
399 (If you want the equivalent of SubscribeGloballyToMessageType, then use <code>OnGlobal<var>MessageType</var></code> instead.)
400 When you call @c RegisterComponentType, it will find all such functions and automatically subscribe to the messages.
401 The @c msg parameter is usually a straightforward mapping of the relevant CMessage class onto a JS object
402 (e.g. @c OnUpdate can read @c msg.turnLength).
403 
404 
405 
406 @section defining-js-interfaces Defining interface types in JS
407 
408 If an interface is only ever used by JS components, and never implemented or called directly by C++ components,
409 then you don't need to do all of the work with defining ICmpExample.
410 Simply create a file @b binaries/data/mods/public/simulation/components/interfaces/Example.js:
411 
412 @code
413 Engine.RegisterInterface("Example");
414 @endcode
415 
416 You can then use @c IID_Example in JS components.
417 
418 (There's no strict requirement to have a single .js file per interface definition,
419 it's just a convention that allows mods to easily extend the game with new interfaces.)
420 
421 
422 
423 @section defining-cpp-message Defining a new message type in C++
424 
425 Think of a name. We'll use @c Example again. (The name should typically be a present-tense verb, possibly
426 with a prefix to make its meaning clearer: "Update", "TurnStart", "RenderSubmit", etc).
427 
428 Add to TypeList.h:
429 
430 @code
431 MESSAGE(Example)
432 @endcode
433 
434 Add to MessageTypes.h:
435 
436 @code
437 class CMessageExample : public CMessage
438 {
439 public:
440  DEFAULT_MESSAGE_IMPL(Example)
441 
442  CMessageExample(int x, int y) :
443  x(x), y(y)
444  {
445  }
446 
447  int x;
448  int y;
449 };
450 @endcode
451 
452 containing the data fields that are associated with the message. (In some cases there may be no fields.)
453 
454 (If there are too many message types, MessageTypes.h could be split into multiple files with better organisation.
455 But for now everything is put in there.)
456 
457 Now you have to add C++/JS conversions into MessageTypeConversions.cpp, so scripts can send and receive messages:
458 
459 @code
460 jsval CMessageExample::ToJSVal(ScriptInterface& scriptInterface) const
461 {
462  TOJSVAL_SETUP();
463  SET_MSG_PROPERTY(x);
464  SET_MSG_PROPERTY(y);
465  return OBJECT_TO_JSVAL(obj);
466 }
467 
468 CMessage* CMessageExample::FromJSVal(ScriptInterface& scriptInterface, jsval val)
469 {
470  FROMJSVAL_SETUP();
471  GET_MSG_PROPERTY(int, x);
472  GET_MSG_PROPERTY(int, y);
473  return new CMessageExample(x, y);
474 }
475 @endcode
476 
477 (You can use the JS API directly in here, but these macros simplify the common case of a single object
478 with a set of scalar fields.)
479 
480 If you don't want to support scripts sending/receiving the message, you can implement stub functions instead:
481 
482 @code
483 jsval CMessageExample::ToJSVal(ScriptInterface& UNUSED(scriptInterface)) const
484 {
485  return JSVAL_VOID;
486 }
487 
488 CMessage* CMessageExample::FromJSVal(ScriptInterface& UNUSED(scriptInterface), jsval UNUSED(val))
489 {
490  return NULL;
491 }
492 @endcode
493 
494 
495 
496 @section defining-js-message Defining a new message type in JS
497 
498 If a message will only be sent and received by JS components, it can be defined purely in JS.
499 For example, add to the file @b interfaces/Example.js:
500 
501 @code
502 // Message of the form { "foo": 1, "bar": "baz" }
503 // sent whenever the example component wants to demonstrate the message feature.
504 Engine.RegisterMessageType("Example");
505 @endcode
506 
507 Note that the only specification of the structure of the message is in comments -
508 there is no need to tell the engine what properties it will have.
509 
510 This message type can then be used from JS exactly like the @c CMessageExample defined in C++.
511 
512 
513 
514 @section communication Component communication
515 
516 @subsection message-passing Message passing
517 
518 For one-to-many communication, you can send indirect messages to components.
519 
520 From C++, use CComponentManager::PostMessage to send a message to a specific entity, and
521 CComponentManager::BroadcastMessage to send to all entities.
522 (In all cases, messages will only be received by components that subscribed to the corresponding message type).
523 
524 @code
525 CMessageExample msg(10, 20);
526 GetSimContext().GetComponentManager().PostMessage(ent, msg);
527 GetSimContext().GetComponentManager().BroadcastMessage(msg);
528 @endcode
529 
530 From JS, use @ref CComponentManager::Script_PostMessage "Engine.PostMessage" and
531 @ref CComponentManager::Script_BroadcastMessage "Engine.BroadcastMessage", using the
532 @c MT_* constants to identify the message type:
533 
534 @code
535 Engine.PostMessage(ent, MT_Example, { x: 10, y: 20 });
536 Engine.BroadcastMessage(MT_Example, { x: 10, y: 20 });
537 @endcode
538 
539 Messages will be received and processed synchronously, before the PostMessage/BroadcastMessage calls return.
540 
541 @subsection query-interface Retrieving interfaces
542 
543 You can also directly retrieve the component implementing a given interface for a given entity,
544 to call methods on it directly.
545 
546 In C++, use CmpPtr (see its class documentation for details):
547 
548 @code
549 #include "simulation2/components/ICmpPosition.h"
550 ...
551 CmpPtr<ICmpPosition> cmpPosition(context, ent);
552 if (!cmpPosition)
553  // do something to avoid dereferencing null pointers
554 cmpPosition->MoveTo(x, y);
555 @endcode
556 
557 In JS, use @ref CComponentManager::Script_QueryInterface "Engine.QueryInterface":
558 
559 @code
560 var cmpPosition = Engine.QueryInterface(ent, IID_Position);
561 cmpPosition.MoveTo(x, y);
562 @endcode
563 
564 (The use of @c cmpPosition in JS will throw an exception if it's null, so there's no need
565 for explicit checks unless you expect the component may legitimately not exist and you want
566 to handle it gracefully.)
567 
568 
569 
570 @section testing Testing components
571 
572 Tests are critical for ensuring and maintaining code quality, so all non-trivial components should
573 have test cases. The first part is testing each component in isolation, to check the following aspects:
574 
575 - Initialising the component state from template data.
576 - Responding to method calls to modify and retrieve state.
577 - Responding to broadcast/posted messages.
578 - Serializing and deserializing, for saved games and networking.
579 
580 To focus on these, the communication and interaction with other components is explicitly not tested here
581 (though it should be tested elsewhere).
582 The code for the tested component is loaded, but all other components are replaced with <i>mock objects</i>
583 that implement the expected interfaces but with dummy implementations (ignoring calls, returning constants, etc).
584 The details differ depending on what language the component is written in:
585 
586 
587 @subsection testing-cpp Testing C++ components
588 
589 Create the file @b simulation2/components/tests/test_Example.h, and copy it from something like test_CommandQueue.h.
590 In particular, you need the @c setUp and @c tearDown functions to initialise CXeromyces, and you should use
591 ComponentTestHelper to set up the test environment and construct the component for you.
592 Then just use the component, and use CxxTest's @c TS_* macros to check things, and use
593 ComponentTestHelper::Roundtrip to test serialization roundtripping.
594 
595 Define mock component objects similarly to MockTerrain. Put it in ComponentTest.h if it's usable by many
596 component tests, or in the test_*.h file if it's specific to one test.
597 Instantiate a mock object on the stack, and use ComponentTestHelper::AddMock to make it accessible
598 by QueryInterface.
599 
600 @subsection testing-js Testing JS components
601 
602 Create the file @b binaries/data/mods/public/simulation/components/tests/test_ExampleTwo.js, and write
603 
604 @code
605 Engine.LoadComponentScript("ExampleTwo.js");
606 var cmp = ConstructComponent(1, "ExampleTwo");
607 @endcode
608 
609 where @c ExampleTwo.js is the component script to test, @c 1 is the entity ID, @c "ExampleTwo" is the component name.
610 Then call methods on @c cmp to test it, using the @c TS_* functions defined in
611 @b binaries/data/tests/test_setup.js for common assertions.
612 
613 Create mock objects like
614 
615 @code
616 AddMock(1, IID_Position, {
617  GetPosition: function() {
618  return {x:1, y:2, z:3};
619  },
620 });
621 @endcode
622 
623 giving the entity ID, interface ID, and an object that emulates as much of the interface as is needed
624 for the test.
625 
626 */