# Documentation updates

This was SVN commit r7457.
This commit is contained in:
Ykkrosh 2010-04-15 19:59:07 +00:00
parent 3a1abb5d8f
commit d6ab843f9d
5 changed files with 203 additions and 20 deletions

View File

@ -71,6 +71,7 @@ public:
"</optional>"
"<element name='Actor'><text/></element>";
}
virtual void Init(const CSimContext& context, const CParamNode& paramNode)
{
if (!context.HasUnitManager())

View File

@ -21,6 +21,11 @@ public:
// ... member variables ...
static std::string GetSchema()
{
return "<ref name='anything'/>";
}
virtual void Init(const CSimContext& context, const CParamNode& paramNode)
{
// ...
@ -41,7 +46,7 @@ public:
// ...
}
virtual void HandleMessage(const CSimContext& context, const CMessage& msg)
virtual void HandleMessage(const CSimContext& context, const CMessage& msg, bool UNUSED(global))
{
// ...
}

View File

@ -31,8 +31,10 @@
- @ref defining-cpp-components
- @ref messages
- @ref component-creation
- @ref schema
- @ref allowing-js-interfaces
- @ref defining-js-components
- @ref defining-js-interfaces
- @ref defining-message
- @ref communication
- @ref message-passing
@ -61,14 +63,15 @@ Create the file @b simulation2/components/ICmpExample.cpp:
@include ICmpExample.cpp
This defines a JavaScript wrapper, so that scripts can access methods of components
implementing that interface. See a later section for details.
implementing that interface. See @ref script-wrapper for details.
This wrapper should only contain methods that are safe to access from simulation scripts:
they must not crash (even with invalid or malicious inputs), they must return deterministic
results, etc.
Methods that are intended for use solely by C++ should not be listed here.
Every interface must define a script wrapper, though in some cases they might contain no methods.
Every interface must define a script wrapper with @c BEGIN_INTERFACE_WRAPPER,
though in some cases they might be empty and not define any methods.
Now update the file simulation2/TypeList.h and add
@ -94,26 +97,29 @@ Interface methods are defined with the macro:
corresponding to the C++ method
<code><var>ReturnType</var> ICmpExample::<var>MethodName</var>(<var>ArgType0</var>, <var>ArgType1</var>, ...)</code>
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/system/InterfaceScripted.h and increase @ref SCRIPT_INTERFACE_MAX_ARGS in scriptinterface/ScriptInterface.h.
(Not sure if anything else needs changing.)
For methods exposed to scripts like this, the arguments should be simple types and pass-by-value.
E.g. use <code>std::wstring</code> arguments, not <code>const std::wstring&</code>.
The arguments and return types will be automatically converted between C++ and JS values.
To do this, @c ToJSVal<ReturnType> and @c FromJSVal<ArgTypeN> must be defined (if they
haven't already been defined for another method), as described below.
The two <var>MethodName</var>s don't have to be the same - in rare cases you might want to expose it as
@c DoWhatever to scripts but link it to the @c ICmpExample::DoWhatever_wrapper() method
which does some extra conversions or checks or whatever.
For methods exposed to scripts like this, the arguments should be pass-by-value.
E.g. use <code>std::wstring</code> arguments, not <code>const std::wstring&</code>.
To convert types between C++ and JS, @c ToJSVal<ReturnType> and @c FromJSVal<ArgTypeN> must be defined, as below.
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/system/InterfaceScripted.h and increase @ref SCRIPT_INTERFACE_MAX_ARGS in scriptinterface/ScriptInterface.h.
(Not sure if anything else needs changing.)
@section script-conversions Script type conversions
If you try to use a type without having defined conversions, you'll probably get mysterious
linker errors that mention @c ToJSVal or @c FromJSVal.
In most cases you can skip this section.
But if you define a script-accessible method with new types without having defined conversions,
you'll probably get mysterious linker errors that mention @c ToJSVal or @c 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.
@ -129,7 +135,8 @@ template<> jsval ScriptInterface::ToJSVal<T>(JSContext* cx, T const& val)
}
@endcode
Use the standard SpiderMonkey JSAPI functions to do the conversion (possibly calling @c ToJSVal recursively).
Use the standard <a href="https://developer.mozilla.org/en/JSAPI_Reference">SpiderMonkey JSAPI functions</a>
to do the conversion (possibly calling @c ToJSVal recursively).
On error, you should return @c JSVAL_VOID (JS's @c 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).
@ -161,7 +168,7 @@ Create @b simulation2/components/CCmpExample.cpp:
\include CCmpExample.cpp
The only optional method is @c HandleMessage - all others must be defined.
The only optional methods are @c HandleMessage and @c GetSchema - all others must be defined.
Update the file simulation2/TypeList.h and add:
@ -184,10 +191,14 @@ static void ClassInit(CComponentManager& componentManager)
(@c CID_Example is derived from the name of the component type, @em not the name of the interface.)
You can also use SubscribeGloballyToMessageType, to intercept messages sent with PostMessage
that are targeted at a @em different entity. (Typically this is used by components that want
to hear about all MT_Destroy messages.)
Then you need to respond to the messages in @c HandleMessage:
@code
virtual void HandleMessage(const CSimContext& context, const CMessage& msg)
virtual void HandleMessage(const CSimContext& context, const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
{
@ -224,9 +235,10 @@ Deinit(context);
~CCmpExample();
@endcode
The order of <code>Init</code>/<code>Deserialize</code>/<code>Deinit</code> between components is undefined,
The order of <code>Init</code>/<code>Deserialize</code>/<code>Deinit</code> between entities is mostly undefined,
so they must not rely on other entities or components already existing; @em except that the SYSTEM_ENTITY is
created before anything else and therefore may be used.
created before anything else and therefore may be used, and that the components for a single entity will be
processed in the order determined by TypeList.h.
The same @c context object will be used in all these calls.
(The component could safely store it in a <code>CSimContext* m_Context</code> member if necessary.)
@ -241,6 +253,69 @@ In a typical component:
- The destructor should clean up any resources allocated by the constructor - usually there's no need to write one.
@subsection schema Component XML schemas
The @c paramNode passed to @c Init is constructed from XML entity template definition files.
Components should define a schema, which is used for several purposes:
- Documentation of the XML structure expected by the component.
- Automatic error checking that the XML matches the expectation, so the component doesn't have to do error checking itself.
- (Hopefully at some point in the future) Automatic generation of editing tool UI.
@c GetSchema must return a Relax NG fragment, which will be used to construct a single global schema file.
(You can run the game with the @c -dumpSchema command-line argument to see the schema).
The <a href="http://relaxng.org/tutorial-20011203.html">official tutorial</a> describes most of the details
of the RNG language.
In simple cases, you would write something like:
@code
static std::string GetSchema()
{
return
"<element name='Name'><text/></element>"
"<element name='Height'><data type='nonNegativeInteger'/></element>"
"<optional>"
"<element name='Eyes'><empty/></element>"
"</optional>";
}
}
@endcode
i.e. a single string (C++ automatically concatenates the quoted lines) which defines a list of elements,
corresponding to an entity template XML file like:
@code
<Entity>
<Example>
<Name>Barney</Name>
<Height>235</Height>
<Eyes/>
</Example>
<!-- ... other components ... -->
</Entity>
@endcode
In the schema, each <code>&lt;element></code> has a name and some content.
The content will typically be one of:
- <code>&lt;empty/></code>
- <code>&lt;text/></code>
- <code>&lt;data type='boolean'/></code>
- <code>&lt;data type='decimal'/></code>
- <code>&lt;data type='nonNegativeInteger'/></code>
- <code>&lt;data type='positiveInteger'/></code>
- <code>&lt;ref name='nonNegativeDecimal'/></code>
- <code>&lt;ref name='positiveDecimal'/></code>
(The last two are slightly different since they're not standard data types.)
Elements can be wrapped in <code>&lt;optional></code>.
Groups of elements can be wrapped in <code>&lt;choice></code> to allow only one of them.
The content of an <code>&lt;element></code> can be further nested elements, but note that
elements may be reordered when loading an entity template:
if you specify a sequence of elements it should be wrapped in <code>&lt;interleave></code>,
so the schema checker will ignore reorderings of the sequence.
For early development of a new component, you can set the schema to <code>&lt;ref name='anything'/></code> to allow any content.
If you don't define @c GetSchema, then the default is <code>&lt;empty/></code> (i.e. there must be no elements).
@section allowing-js-interfaces Allowing interfaces to be implemented in JS
@ -289,6 +364,8 @@ Then write @b binaries/data/mods/public/simulation/components/ExampleTwo.js:
@code
function ExampleTwo() {}
ExampleTwo.prototype.Schema = "<ref name='anything'/>";
ExampleTwo.prototype.Init = function() {
...
};
@ -320,12 +397,30 @@ each JS component instance is automatically serialized and restored.
because they're too hard to serialize. The details should be documented on some other page eventually.)
Instead of @c ClassInit and @c HandleMessage, you simply add functions of the form <code>On<var>MessageType</var></code>.
(If you want the equivalent of SubscribeGloballyToMessageType, then use <code>OnGlobal<var>MessageType</var></code> instead.)
When you call @c RegisterComponentType, it will find all such functions and automatically subscribe to the messages.
The @c msg parameter is usually a straightforward mapping of the relevant CMessage class onto a JS object
(e.g. @c OnUpdate can read @c msg.turnLength).
@section defining-js-interfaces Defining interface types in JS
If an interface is only ever used by JS components, and never implemented or called directly by C++ components,
then you don't need to do all of the work with defining ICmpExample.
Simply create a file @b binaries/data/mods/public/simulation/components/interfaces/Example.js:
@code
Engine.RegisterInterface("Example");
@endcode
You can then use @c IID_Example in JS components.
(There's no strict requirement to have a single .js file per interface definition,
it's just a convention that allows mods to easily extend the game with new interfaces.)
@section defining-message Defining a new message type
Think of a name. We'll use @c Example again. (The name should typically be a present-tense verb, possibly

View File

@ -85,8 +85,22 @@ public:
void RegisterComponentType(InterfaceId, ComponentTypeId, AllocFunc, DeallocFunc, const char*, const std::string& schema);
void RegisterComponentTypeScriptWrapper(InterfaceId, ComponentTypeId, AllocFunc, DeallocFunc, const char*, const std::string& schema);
void SubscribeToMessageType(MessageTypeId);
void SubscribeGloballyToMessageType(MessageTypeId);
/**
* Subscribe the current component type to the given message type.
* Each component's HandleMessage will be called on any BroadcastMessage of this message type,
* or on any PostMessage of this type targeted at the component's entity.
* Must only be called by a component type's ClassInit.
*/
void SubscribeToMessageType(MessageTypeId mtid);
/**
* Subscribe the current component type to all messages of the given message type.
* Each component's HandleMessage will be called on any BroadcastMessage or PostMessage of this message type,
* regardless of the entity.
* Must only be called by a component type's ClassInit.
*/
void SubscribeGloballyToMessageType(MessageTypeId mtid);
/**
* @param cname Requested component type name (not including any "CID" or "CCmp" prefix)

View File

@ -27,6 +27,74 @@
class XMBFile;
class XMBElement;
/**
* An entity initialisation parameter node.
* Each node has a text value, plus a number of named child nodes (in a tree structure).
* Child nodes are unordered, and there cannot be more than one with the same name.
* Nodes are immutable.
*
* Nodes can be initialised from XML files. Child elements are mapped onto child nodes.
* Attributes are mapped onto child nodes with names prefixed by "@"
* (e.g. the XML <code>&lt;a b="c">&lt;d/>&lt;/a></code> is loaded as a node with two
* child nodes, one called "@b" and one called "d").
*
* They can also be initialised from @em multiple XML files,
* which is used by ICmpTemplateManager for entity template inheritance.
* Loading one XML file like:
* @code
* <Entity>
* <Example1>
* <A attr="value">text</A>
* </Example1>
* <Example2>
* <B/>
* </Example2>
* <Example3>
* <C/>
* </Example3>
* </Entity>
* @endcode
* then a second like:
* @code
* <Entity>
* <Example1>
* <A>example</A> <!-- replace the content of the old A element -->
* <D>new</D> <!-- add a new child to the old Example1 element -->
* </Example1>
* <Example2 delete=""/> <!-- delete the old Example2 element -->
* <Example3 replace=""> <!-- replace all the old children of the Example3 element -->
* <D>new</D>
* </Example3>
* </Entity>
* @endcode
* is equivalent to loading a single file like:
* @code
* <Entity>
* <Example1>
* <A attr="value">example</A>
* <D>new</D>
* </Example1>
* <Example3>
* <D>new</D>
* </Example3>
* </Entity>
* @endcode
*
* Parameter nodes can be translated to JavaScript objects. The previous example will become the object:
* @code
* { "Entity": {
* "Example1": {
* "A": { "@attr": "value", "_string": "example" },
* "D": "new"
* },
* "Example3": {
* "D": "new"
* }
* }
* }
* @endcode
* (Note the special @c _string for the hopefully-rare cases where a node contains both child nodes and text.)
*/
class CParamNode
{
public: