Table of Contents
BigWorld provides two mechanisms for extending Entity and server functionality. These are known as Entity Extras and Controllers.
Entity extras provide a mechanism for adding additional methods to all BigWorld cell entities. They are created on the spot when accessed by an entity, and are otherwise stateless. They are lost when the entity changes cells. There is at most one instance of EntityExtra per entity, and it may be easily retrieved from an Entity reference.
Controllers provide a standard method to perform CPU-intensive work on entities in C++. They are instantiated by, and attached to a cell entity, and travel with it between cells. They are useful for performing actions that are either unfeasible or inefficient to implement in Python. There may be multiple Controller instances per entity, each with its own ID, which the entity script (or another Controller) may use to cancel or access the entity.
Thus, it is not possible to retrieve a Controller instance by type from an entity reference, since it would not be possible to determine the instance retrieved. Of course, a friendly EntityExtra could store a pointer to it, if this were desired.
BigWorld comes packaged with a selection of proven useful entity extras and Controllers, including facilities for performing the following functions:
-
Movement and navigation.
-
Vehicle management.
-
Entity vision and visibility.
-
Timed events.
Depending on game design, however, additional facilities may be needed. Game design may also dictate the need for different implementations of one or more of the supplied facilities. Custom entity extras and Controllers are often useful for implementing these game-specific features.
Entity extras attach additional methods to the
BigWorld
.Entity
Python class
on the cell. All entities have access to all methods of entity extras.
However, the class implementing an entity extra is not instantiated until
one of those methods is used, thus saving memory.
While entity extras are useful for extending entities in ways that can only be done via C++, it is worth remembering that for many things a simple Python base class will suffice.
Entity extras should not contain any state for an entity, as they are not streamed from one cell to another during the cell's ghosting process.
A minimal entity extra consists of the following header file (replacing any references to EgExtra with the appropriate name):
#ifndef EGEXTRA_HPP #define EGEXTRA_HPP #include "cellapp/entity_extra.hpp" /** * Simple example entity extra... can print a message to the screen */ #undef PY_METHOD_ATTRIBUTE_WITH_DOC #define PY_METHOD_ATTRIBUTE_WITH_DOC PY_METHOD_ATTRIBUTE_ENTITY_EXTRA_WITH_DOC class EgExtra : public EntityExtra { Py_EntityExtraHeader( EgExtra ); public: EgExtra( Entity& e ); ~EgExtra(); PyObject * pyGetAttribute( const char * attr ); int pySetAttribute( const char * attr, PyObject * value ); static const Instance<EgExtra> instance; }; #undef PY_METHOD_ATTRIBUTE_WITH_DOC #define PY_METHOD_ATTRIBUTE_WITH_DOC PY_METHOD_ATTRIBUTE_BASE_WITH_DOC #endif
EgExtra header file
bigworld/src/examples/cellapp_extension/egextra.hpp
‐ Minimal definition
The code above contains the declarations necessary to integrate an entity extra into any entity in the BigWorld system.
After the header guards,
bigworld/src/server/cellapp/entity_extra.hpp is
included which defines the EntityExtra
class.
It also overrides the macro PY_METHOD_ATTRIBUTE, in order to make automatically declared Python attributes in EntityExtras work[40]. This allows BigWorld to search through and automatically instantiate extras by using only the name of the method that has been called.
In the EgExtra class, we derive from EntityExtra, and use the macro Py_EntityExtraHeader to declare some additional methods and properties that are used to keep track of these classes.
We provide the methods pyGetAttribute and pySetAttribute so that we can act like a Python object.
A static specialisation EntityExtra::instance member is also declared to access the entity extras. With it, we can get a reference to the EgExtra for any Entity& ent using the code:
EgExtra& eg = EgExtra instance( ent );
If the extra does not exist, it will be instantiated and returned. If we wanted to check first whether the extra exists, we can query it with the following code:
bool hasEgExtra = EgExtra instance.exists( ent );
We implement the outline of the EgExtra class as follows:
#include "egextra.hpp" DECLARE_DEBUG_COMPONENT(0); PY_TYPEOBJECT( EgExtra ) PY_BEGIN_METHODS( EgExtra ) PY_END_METHODS() PY_BEGIN_ATTRIBUTES( EgExtra ) PY_END_ATTRIBUTES() const EgExtra::Instance<EgExtra> EgExtra::instance( &EgExtra::s_attributes_.di_ ); EgExtra::EgExtra( Entity& e ) : EntityExtra( e ) { } EgExtra::~EgExtra() { } PyObject * EgExtra::pyGetAttribute( const char * attr ) { PY_GETATTR_STD(); return this->EntityExtra::pyGetAttribute( attr ); } int EgExtra::pySetAttribute( const char * attr, PyObject * value ) { PY_SETATTR_STD(); return this->EntityExtra::pySetAttribute( attr, value ); }
EgExtra implementation file
bigworld/src/examples/cellapp_extension/egextra.cpp
‐ Class outline
These two files together constitute the framework that we will use
to implement any entity extra. These files can be found in BigWorld
distribution, in the directory bigworld/src/examples/cellapp_extension
.
We can now add methods to this entity extra. We do this by declaring the method in the class declaration, and exposing it to Python with the BigWorld Python macros.
As a simple example, to implement a method that prints the message 'hello world' to the server debug log, we add the following code to the class declaration:
// ... class EgExtra : public EntityExtra { Py_EntityExtraHeader( EgExtra ); public: // ... void helloWorld(); PY_AUTO_METHOD_DECLARE( RETVOID, helloWorld, END ); }; // ...
EgExtra header file ‐ Declaration of method helloWorld
And in the implementation file, we add a simple 'stub' implementation:
// ... PY_TYPE_OBJECT( EgExtra ) PY_BEGIN_METHODS( EgExtra ) PY_METHOD( helloWorld ) PY_END_METHODS() // ... void EgExtra::helloWorld() { DEBUG_MSG( "egextra: hello world\n" ); }
EgExtra implementation file ‐ Definition of method helloWorld
After compiling this module, then for any entity in the world you can call:
self.helloWorld()
The call above outputs the text 'egextra: hello world' to the server debug log.
Entity extras have an entity
() function,
which returns a reference to the entity they have been attached to.
Controllers enable us to dynamically add stateful C++ objects to entities on the CellApp. They can be used for property updates that need to be continuous, or for extensions that need to interact with the server at a level lower than the one exposed via the scripted entity model.
To implement a Controller, inherit from the CellApp class named
Controller
. The Controller declaration has to
include the special macro DECLARE_CONTROLLER_TYPE,
which includes definitions required to register the Controller in the
BigWorld system.
The stub declaration file contains the following code:
#ifndef EGCONTROLLER_HPP #define EGCONTROLLER_HPP #include "cellapp/controller.hpp" class EgController : public Controller { DECLARE_CONTROLLER_TYPE( EgController ); public: }; #endif
Controller header file ‐
bigworld/src/examples/cellapp_extension/egcontroller.hpp
A Controller may be a member of the real domain (denoted by DOMAIN_REAL), or the ghost domain (denoted by DOMAIN_GHOST).
-
A Controller member of DOMAIN_REAL works with reals.
An example would be a movement Controller. Positional data is already sent via the entity (due to the necessity of placing the ghost in the right position), but other clients are unlikely to need to know the entity's final destination. Consequently, there is no need to send ghost information for this entity. Therefore, the Controller needs to operate only in the real domain.
-
A Controller member of DOMAIN_GHOST works with ghosts.
One such controller is BigWorld's
VisibilityController
, which simply publishes information for other entities to query. Other entities need to query this Controller (through an entity extra) to determine whether they can see that entity, even when the entity is a ghost.
BigWorld directs Controllers by calling various virtual methods on
the Controller
class. There are two types of such
methods:
-
Communication methods
These methods serialise data onto a stream between cells, so that the Controller representation can be moved from one machine to another.
There are four of these, one each for reading/writing to the real/ghost domains, as described below:
-
Method: Read
-
DOMAIN_REAL: bool readRealFromStream( BinaryIStream& )
Returns TRUE on success. Default implementation: return TRUE.
-
DOMAIN_GHOST: bool readGhostFromStream( BinaryIStream& )
Returns TRUE on success. Default implementation: return TRUE.
-
-
Method: Write
-
DOMAIN_REAL: void writeRealToStream( BinaryOStream& )
Default implementation: Do nothing
-
DOMAIN_GHOST: bool void writeGhostToStream( BinaryOStream& )
Default implementation: Do nothing.
-
-
-
Start/stop methods
Controllers often request to be called back from the BigWorld code. However, a real Controller should not be executed when it is attached to a ghost entity, and a ghost Controller should not be executed when it is attached to a real entity.
To allow this, BigWorld uses four Controller methods to notify a controller when it should change its processing strategy ‐ start methods should request the callbacks, and stop methods should cancel them, as described below:
-
Method: Start
-
DOMAIN_REAL: void startReal( bool isInitialStart )
Default implementation: Do nothing.
-
DOMAIN_GHOST: void startGhost( )
Default implementation: Do nothing.
-
-
Method: Stop
-
DOMAIN_REAL: void stopReal( bool isFinalStop )
Default implementation: Do nothing.
-
DOMAIN_GHOST: void stopGhost( )
Default implementation: Do nothing.
-
-
A Controller can be a member of both domains (DOMAIN_REAL, and DOMAIN_GHOST) if it has the (uncommon) need to present aspects of itself as a ghost Controller, and aspects of itself as a real Controller.
Once an initial decision has been made as to which domains a controller belongs to, a stub implementation file needs to be set up. At first, it does not have to declare the domain, as illustrated below:
#include "egcontroller.hpp" #include "cellapp/cellapp.hpp" DECLARE_DEBUG_COMPONENT(0); // controller type declaration needs to go here
Controller stub implementation file ‐
bigworld/src/examples/cellapp_extension/egcontroller.cpp
Next, a macro needs to be placed to implement the controller integration. There are two possible macros for that:
-
IMPLEMENT_CONTROLLER_TYPE( CLASS_NAME, DOMAIN )
This macro declares a standard Controller type, which will be instantiated using an EntityExtra. See below for more details.
-
IMPLEMENT_CONTROLLER_TYPE_WITH_PY_FACTORY( CLASS_NAME, DOMAIN )
This macro declares that the controller will be instantiated using an automatically generated addControllerClassName method in Python. The ControllerClassName used omits the trailing word 'Controller' if it is present (e.g., TimerController becomes addTimer).
A factory method has to be created to use this feature. Its declaration goes into the class declaration, and its name is New:
public: static FactoryFnRet New( int userArg ); PY_AUTO_CONTROLLER_FACTORY_DECLARE( EgController, ARG( int, END ) )
This method is implemented in the .cpp file:
Controller::FactoryFnRet EgController::New( int userArg ) { return FactoryFnRet( new EgController(), userArg ); }
When implementing a controller, the Controller class exposes the following useful methods:
-
entity()
Returns an Entity& object, referring to the entity that owns this Controller.
For more details on methods exposed by the Entity class, see the CellApp C API and Client C API.
-
cancel()
Cancels this controller. Call this method only on real entities.
-
ghost()
Informs the cell to update the ghosted portion of this (real entity) controller.
-
standardCallback (
methodName
)Calls the 'standard' Controller Python notification method on the associated entity. Standard notification methods have the following Python signature: def methodName( self, controllerID, userData ).
A PortalConfigController configures the portal that its owning entity straddles. It is added with the entity method addPortalConfig, and takes the following arguments:
-
permissive
Boolean value indicating whether the portal allows objects in the collision scene to pass through it.
-
triFlags
uint32 value for the triangle flags that should be returned on collision tests that intersect the portal when it is not permissive.
-
navigable
Boolean value not currently used, and which should be set to the same value as the permissive argument.
In the future, this argument may be used to indicate whether the navigation system should consider the portal to be passable. For now however, the navigation system uses the same flag as the collision scene (permissive). For details, on the navigation system, see Navigation System.
The controller is cancelled with the Entity method cancel, as with other controllers. It is a ghost controller, and therefore the portal configuration is correctly replicated across cells when more than one can see it. It is not however propagated to the client, if required it must be done via script properties or messages.
The direction of the entity to which the PortalConfigController is attached must be the same as the normal of the portal that is to be configured, i.e., if the portal runs east-west between two chunks, then the entity must face either south or north, otherwise the portal will probably not be found.
Note
An entity should not be moved while it has a PortalConfigController attached, or there may be undesirable results across cells.
If the entity needs to be moved, then cancel PortalConfigController, move the entity, and recreate the controller.
To the extent that portals are uni-directional and come in pairs, PortalConfigController configures only the portal whose normal faces approximately the same direction as the entity, i.e., the portal in the chunk that is found just in front (10 cm) of the entity's position. Another entity with opposite direction may be created if a separate configuration is desired for the opposing portal. For most purposes however (i.e., navigation and collision) it is only necessary to configure one portal differently to the default permissive state.
Note
The method configureConnection has been deprecated, since it does not work across cells.
Entity extras are commonly used to provide a more sophisticated means of instantiating Controllers. They might provide a factory that selects one of several types of Controller to instantiate, or provide a means of limiting the number of Controllers that are instantiated.
To begin exploring this, we implement something similar to the automatic instantiation provided by the macro IMPLEMENT_CONTROLLER_TYPE_WITH_PY_FACTORY.
We add an instantiation method called addEgController to EgExtra, taking an integer user argument:
class EgExtra : public EntityExtra { Py_EntityExtraHeader( EgExtra ); public: // ... PY_AUTO_METHOD_DECLARE( RETOWN, addEgController, ARG( int, END ) ); PyObject * addEgController( int userArg ); static const Instance<EgExtra> instance; };
EgExtra header file
bigworld/src/examples/cellapp_extension/egextra.hpp
‐ Declaration of instantiation method
addEgController
The next step is to implement the method addEgController. To do so, we need to:
-
Add a macro PY_METHOD to declare the Python/C++ binding code.
-
Ensure that the method is being called on a real entity.
-
Instantiate an EgController.
-
Add the new Controller to the entity.
-
Return the controller ID.
Note
Most Controllers do not need any more functionality than the built-in Python factory method supplies.
Creating unnecessary EntityExtra instances should be avoided, since every declared EntityExtra type uses 4 bytes per entity (including ghost entities), even when it is not instantiated.
This will mean the following changes to EgExtra:
#include "egextra.hpp" #include "egcontroller.hpp" DECLARE_DEBUG_COMPONENT(0); PY_TYPEOBJECT( EgExtra ) PY_BEGIN_METHODS( EgExtra ) PY_METHOD( helloWorld ) PY_METHOD( addEgController ) PY_END_METHODS() // ... PyObject * EgExtra::addEgController( int userArg ) { if (!entity_.isReal()) { PyErr_SetString( PyExc_TypeError, "Entity.addEgController() may only be called on real entities" ); return NULL; } ControllerPtr pController = new EgController(); ControllerID controllerID = entity_.addController( pController, userArg ); return Script::getData( controllerID ); }
EgExtra implementation file
bigworld/src/examples/cellapp_extension/egextra.cpp
‐ Definition of instantiation method
addEgController
If you want to restrict the entities to have only one Controller each, then the class EgExtra may be changed so that it maintains a pointer to the current Controller, and the method addEgController should ensure that the pointer is NULL before allowing a new one to be added.
The class EgController would then, in its method startReal, send its pointer to the class EgExtra, and communicate to it that it has been cancelled in its method stopReal. The pointer must not be directly set in the method addEgController, as it would break the symmetry of the Controller 'owning' that pointer in the EntityExtra.
Setting the pointer in the mentioned methods in the Controller works correctly when the entity is offloaded to another cell.
The line to add to method startReal would be similar to the line below:
EgExtra::instance( *this->entity() ).setEgControllerPtr( this )
Note that the object EgExtra will be instantiated if it does not already exist.
Keeping a pointer to a related Controller can also be useful for an entity extra to maintain states across cell transitions.
For example, to calculate the age of an entity extra, it might provide a method getEgAge, and the Controller might store the game time when it is created. The entity extra can then calculate its age by subtracting the current game time from the Controller's stored game time (after checking that the Controller pointer is not NULL).