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