mirror of
https://github.com/tribes2/engine.git
synced 2026-01-19 19:24:45 +00:00
891 lines
73 KiB
Plaintext
891 lines
73 KiB
Plaintext
//----------------------------------------------------------------------------
|
|
/*! \mainpage V12 Engine Documentation
|
|
|
|
\section EngOverview Engine Overview
|
|
<ul>
|
|
<li> \ref ControlFlow
|
|
<li> \ref PlatformLayer
|
|
<ul>
|
|
<li> \ref PlatformOverview
|
|
<li> \ref PlatformEvent
|
|
<li> \ref PlatformDevice
|
|
<li> \ref PlatformUtil
|
|
<li> \ref PlatformNetwork
|
|
<li> \ref PlatformGame
|
|
</ul>
|
|
<li> \ref Console
|
|
<ul>
|
|
<li> \ref ConOverview
|
|
<li> \ref ConRef
|
|
<li> \ref ConFunctions
|
|
<li> \ref ConClasses
|
|
<li> \ref ConFields
|
|
<li> \ref ConDynamic
|
|
<li> \ref ConNamespace
|
|
<li> \ref ConObject
|
|
<li> \ref ConPackage
|
|
<li> \ref ConVariable
|
|
<li> \ref ConArray
|
|
<li> \ref ConStringOp
|
|
<li> \ref ConCompiler
|
|
<li> \ref ConDebugger
|
|
<li> \ref ConInterface
|
|
</ul>
|
|
<li> \ref Simulation
|
|
<ul>
|
|
<li> \ref SimOver
|
|
<li> \ref SimBase
|
|
<li> \ref SimManager
|
|
<li> \ref SimDB
|
|
</ul>
|
|
<li> \ref FileIO
|
|
<ul>
|
|
<li> \ref FileOverview
|
|
<li> \ref FilePaths
|
|
<li> \ref FileVolumes
|
|
<li> \ref FileResourceObject
|
|
</ul>
|
|
|
|
<li> \ref InputModel
|
|
<ul>
|
|
<li> \ref InputPlatform
|
|
<li> \ref InputAction
|
|
<li> \ref InputBind
|
|
<li> \ref InputBindCmd
|
|
<li> \ref InputGlobalMap
|
|
<li> \ref InputMapStack
|
|
<li> \ref InputModKeys
|
|
<li> \ref InputGui
|
|
</ul>
|
|
|
|
<li> \ref Graphics
|
|
<ul>
|
|
<li> \ref GraphicsOverview
|
|
<li> \ref GraphicsInit
|
|
<li> \ref GraphicsDgl
|
|
<li> \ref GraphicsTexture
|
|
<li> \ref GraphicsPrimitive
|
|
<li> \ref GraphicsPrimRender
|
|
<li> \ref Graphics3DRender
|
|
</ul>
|
|
|
|
<li> \ref GUI
|
|
<ul>
|
|
<li> \ref GUIOver
|
|
<li> \ref GuiInput
|
|
<li> \ref GuiRender
|
|
<li> \ref GuiConsole
|
|
</ul>
|
|
|
|
<li> \ref Render3D
|
|
<ul>
|
|
<li> \ref Render3DOver
|
|
<li> \ref Render3DSceneGraph
|
|
<li> \ref Render3DTerain
|
|
<li> \ref RenderInterior
|
|
<li> \ref Render3Space
|
|
</ul>
|
|
|
|
<li> \ref Networking
|
|
<ul>
|
|
<li> \ref NetOver
|
|
<li> \ref NetPlatform
|
|
<li> \ref NetProtocol
|
|
<li> \ref NetConnection
|
|
<li> \ref NetBitstream
|
|
<li> \ref NetEvents
|
|
<li> \ref NetGhost
|
|
<li> \ref NetGameConnection
|
|
<li> \ref NetDatablocks
|
|
<li> \ref NetStringTable
|
|
<li> \ref NetConsole
|
|
</ul>
|
|
</ul>
|
|
*/
|
|
//---------------------------------------------------------
|
|
/*! \page ControlFlow Basic Control Flow
|
|
Overview
|
|
Because different platforms can have different main() entry points for applications, the V12 Engine main() function resides in the target OS platform library. In the case of Windows, this is in file engine/platformWin32/winWindow.cc, where both main() (for console apps) and WinMain() are defined. These in turn call run() which calls Game->main(int argc, const char **argv). Game is a global object pointer referencing an instance of the GameInterface class that can be overridden for specific game behavior.
|
|
|
|
The V12 example program's main initialization occurs in engine/game/main.cc in V12Game::main(). This function initializes libraries, initializes game functions and then cycles in the main game loop until the program is terminated. The main loop basically calls platform library functions (engine/platform/platform.h) to produce platform events, which then drive the main simulation.
|
|
|
|
The main.cc file also has V12Game function overrides for some of the basic event procession functions: processMouseMoveEvent (which dispatches Windows mouse movements to the GUI), processInputEvent (which processes other input related events), and processTimeEvent which computes an elapsed time value based on the time scale setting of the simulation and then:
|
|
|
|
1. processes time for server objects (serverProcess() in engine/game/game.cc)
|
|
2. checks for server network packet sends (serverNetProcess() in engine/game/netDispatch.cc)
|
|
3. advances simulation event time (Sim::advanceTime() in engine/console/simManager.cc)
|
|
4. processes time for client objects (clientProcess() in engine/game/game.cc)
|
|
5. checks for client network packet sends (clientNetProcess() in engine/game/netDispatch.cc)
|
|
6. renders the current frame (GuiCanvas::render() in engine/gui/guiCanvas.cc)
|
|
7. checks for network timeouts (dispatchCheckTimeouts() in engine/game/netDispatch.cc)
|
|
|
|
Incoming UDP network packets are processed in V12Game::processPacketReceiveEvent (defined in engine/game/netDispatch.cc), and incoming TCP connection data or information is processed in V12Game::processConnected*Event (defined in engine/game/TCPObject.cc).
|
|
*/
|
|
|
|
//---------------------------------------------------
|
|
/*! \page PlatformLayer Platform layer
|
|
\subsection PlatformOverview Overview
|
|
The platform layer is the foundation of the game - as the lowest level it provides a common cross platform/architecture interface to the game. The platform layer is responsible for handling file and network IO, graphics initialization, device input and generation of time events. Standard library calls are replicated in the platform layer and system header files are (pretty much) only included from the platform library. The platform layer is broken up into several sections - the cross-platform definition library (engine/platform), and the platform specific interface libraries (engine/platformWin32, engine/platformMacOS, engine/platformX86UNIX).
|
|
|
|
\subsection PlatformEvent Event model
|
|
The game, as much as possible, is driven by a stream of events from the platform library. The following events are defined in engine/platform/event.h: InputEvent, MouseMoveEvent, PacketReceiveEvent, TimeEvent, QuitEvent, ConsoleEvent, ConnectedReceiveEvent, ConnectedAcceptEvent, and ConnectedNotifyEvent. By journaling the stream of events from the platform layer, the game portion of the simulation session can deterministically replayed for debugging purposes.
|
|
InputEvent - input from a physical input device such as a keyboard, mouse or joystick.
|
|
MouseMoveEvent - special screen-space mouse movement event for when the game is running in a window.
|
|
PacketReceiveEvent - unguaranteed data network packet event from some other host on the network.
|
|
TimeEvent - elapsed time event is passed in when no other events are pending. This allows the simulation to move forward and frames to be rendered.
|
|
QuitEvent - platform specific method for notifying the game that the host wishes to exit the game.
|
|
ConsoleEvent - command typed into a platform specific console (like the Win32 console for a dedicated server).
|
|
ConnectedReceiveEvent - Message data received on a TCP network connection.
|
|
ConnectedAcceptEvent - Notification event when a connection has been accepted from a remote client.
|
|
ConnectedNotifyEvent - Update event on the status of a connection with another host - DNSResolved, DNSFailed, Connected, ConnectFailed, Disconnected. Note that these events are only received for TCP connections.
|
|
|
|
\subsection PlatformDevice Device management
|
|
The platform layer is responsible for initializing all devices used by the game - sound, OpenGL graphics, input devices and network interfaces.
|
|
|
|
\subsection PlatformUtil Platform Utilities
|
|
Memory Manager - the V12 engine has its own memory management library (engine/platform/platformMemory.cc) designed to quickly allocate memory on cache aligned boundaries and perform debugging checks for overruns, multiple block frees and memory leaks.
|
|
Profiler - the profiler is a hierarchal time analysis toolkit that allows specific portions of the game to be timed - useful for finding performance bottlenecks.
|
|
Thread management - utility classes for creating threads, mutexes and semaphores.
|
|
Standard library functions - all standard library functions are redefined as dFuncname, for example sprintf becomes dSprintf, strcpy becomes dStrcpy, etc.
|
|
|
|
\subsection PlatformNetwork Network functionality
|
|
The platform library has a common interface for opening and closing network ports, connecting to other hosts, translating network addresses and sending and receiving network data. Guaranteed and unguaranteed protocols are supported.
|
|
|
|
\subsection PlatformGame GameInterface
|
|
The GameInterface class, defined in engine/platform/gameInterface.* is the interface through which the platform and libraries communicate with the game, as well as the main handler for journaling. All platform events are passed through GameInterface::processEvent, which in turn feeds them to separate virtual event handlers. One instance of this class (or a subclass) should exist in the program and a single global pointer named Game points to it.
|
|
*/
|
|
|
|
*/
|
|
//------------------------------------------------------
|
|
/*! \page Simulation Simulation
|
|
|
|
\section SimOver Overview
|
|
The simulation of objects is handled almost entirely in the game portion of the engine example. All simulation object classes are derived from GameBase, which is a subclass of SceneObject. GameBase objects that wish to be notified of the passage of time can be added to one of the two process lists - the global server or global client process list, depending on whether the object is a server object or a client ghost. All objects in the process list are "ticked" once every 32 milliseconds. The ordering of the objects is determined by the GameBase::processAfter method - called if an object must be specifically processed at some time after another object (not necessarily immediately afterward). For example, a player mounted to a vehicle would be set to processAfter the vehicle, so that after the vehicle moved the player's position could be updated to the correct position on the vehicles new position.
|
|
|
|
Game objects are updated in three separate functions, overridden from GameBase: GameBase::processTick, which takes a single Move structure as an argument (can be NULL) and advances the object in time by one 32 ms tick, GameBase::interpolateTick, which interpolates a client object backwards from the end of its current tick to the present time, and GameBase::advanceTime, which allows a client object to advance animations and effects by the full duration of the time event.
|
|
|
|
Server side objects are only simulated on even tick boundaries, but client objects, in order to present a smooth view when the frame rate is high, are simulated after each time event. The processTick method is still only invoked on even tick boundaries, but at the end of the time advance, objects are essentially rewound by the time difference to the end of the tick. Also, client objects that need to animate only by the total elapsed time can do so in the GameBase::advanceTime function, which is only called once per time advancement.
|
|
|
|
One item to note: if the control object is sent new object state from the server, it will be advanced by every Move the client has recorded that the server has not yet acknowledged - this is because the server has old data as far as the client is concerned, so the client skips forward through all the new data it knows about.
|
|
|
|
\section SimDB Container database
|
|
The Container class maintains a database on client and server for objects positioned in the simulation. It supports a set of functions for quickly inserting, removing and moving objects in the world (Container::addObject, Container::removeObject, Container::checkBins), as well as query functions for line, box and polyhedron intersection tests (Container::castRay, Container::collideBox, Container::findObjects).
|
|
*/
|
|
|
|
//-------------------------------------------
|
|
/*! \page Console Console
|
|
|
|
\section ConOverview Overview
|
|
The console library (engine/console) is a combined compiler and interpreter runtime that serves as the foundation for V12 applications. All GUIs, game objects and interface and game logic are handled through the console. The language itself is syntactically similar to a typeless C++, with some additional features that allow for easier mod development. Console scripts can be loaded via the exec() console command from the console window (brought up using the ~ key) or they can be loaded automatically from a mod via that mod's main.cs.
|
|
|
|
\section ConRef Console language ref
|
|
The V12 console language scanner and parser were built using the tools lex and yacc. The scan and grammar files are engine/console/scan.l and engine/console/gram.y respectively. The grammar is shown in a somewhat more understandable way in the attached document. <link to grammar doc>
|
|
|
|
\subsection ConFunctions Functions
|
|
Console functions can be declared in either console scripts or in the C++ game/engine code. A simple script function declaration for example:
|
|
|
|
Example.cs:
|
|
|
|
\code
|
|
function helloWorld ()
|
|
{
|
|
echo("Hello World!");
|
|
}
|
|
helloWorld();
|
|
\endcode
|
|
|
|
or, for a method on a class:
|
|
|
|
\code
|
|
function SimObject::helloWorld(%this)
|
|
{
|
|
echo("Hello World!");
|
|
echo("Called on object: " @ %this);
|
|
}
|
|
$object = new SimObject();
|
|
$object.helloWorld();
|
|
\endcode
|
|
|
|
Unlike in C++, the this variable is not implicit in the function declaration. To declare console functions in the game or engine code, the ConsoleFunction macro (declared in engine/console/console.h) should be used.
|
|
|
|
Example.cc:
|
|
|
|
\code
|
|
ConsoleFunction(helloWorld, void, 1, 1, "helloWorld()")
|
|
{
|
|
Con::printf("Hello World!");
|
|
}
|
|
\endcode
|
|
|
|
The first argument to the macro is the name of the function as it will appear to scripts. Second is the return type, followed by the minimum and maximum number of arguments allowed to the function. Since the function name is always argument 0, 1 is minimum argument count allowed. Passing a 0 for max arguments will allow any number of arguments to the function. The last parameter is a usage string, which is displayed as an error message if the function is called with an incorrect number of arguments.
|
|
|
|
Methods for classes can also be declared in C++, using the ConsoleMethod macro:
|
|
|
|
\code
|
|
ConsoleMethod(SimObject, helloWorld, void, 2, 2, "object.helloWorld()")
|
|
{
|
|
Con::printf("Hello World!");
|
|
Con::printf("Called on object: %s", argv[1]);
|
|
Con::printf("Also called on object: %s", object->getName());
|
|
}
|
|
\endcode
|
|
|
|
In this case, the first argument is the name of the class, followed by the arguments to ConsoleFunction. These macros create a function declaration in C++ with several arguments defined: argc (integer), argv (array of strings), and, in the case of ConsoleMethod, object (SimObject *).
|
|
|
|
\subsection ConClasses Classes, Objects and Namespaces
|
|
Declaring Console Classes
|
|
Objects in the scripting language are simply instances of C++ classes (derived from SimObject) declared in the game engine and processed with a special set of macros (declared in engine/console/consoleObject.h). The DECLARE_CONOBJECT(class_name) macro is placed inside the class definition and the IMPLEMENT_CONOBJECT (class_name) macro is placed in a linked source file. IMPLEMENT_CONOBJECT has several versions, depending on the type of class:
|
|
|
|
IMPLEMENT_CONOBJECT - simple console object - no special network attributes.
|
|
|
|
IMPLEMENT_CO_NETOBJECT - ghostable network object classs. Any object that will be ghosted from server to client needs to be declared as a NETOBJECT. See the section on the network layer for more details about NetObjects.
|
|
|
|
IMPLEMENT_CO_DATABLOCK - The class is a datablock class.
|
|
There are 3 more IMPLEMENT_CONOBJECTs that deal specifically with network events.
|
|
|
|
Every class declared as a Console class MUST declare a Parent typedef in its private member section referencing its parent SimObject class. This allows the console to properly determine the console object class hierarchy for method dispatch in the console.
|
|
|
|
Example:
|
|
|
|
\code
|
|
class SampleObject : public SimObject
|
|
{
|
|
typedef SimObject Parent;
|
|
public:
|
|
DECLARE_CONOBJECT(SampleObject);
|
|
S32 someVariable; // signed integer variable
|
|
};
|
|
IMPLEMENT_CONOBJECT(SampleObject);
|
|
\endcode
|
|
|
|
Then in script:
|
|
|
|
\code
|
|
function foo()
|
|
{
|
|
%obj = new SampleObject(MySampleObject);
|
|
echo(%obj.getName());
|
|
}
|
|
\endcode
|
|
|
|
\subsection ConFields Adding class member fields
|
|
C++ data members of classes can be accessed from within the scripting language. When the game starts the Console calls AbstractClassRep::initialize() (defined in engine/console/consoleObject.cc), which assigns network ids to classes, links class hierarchies, and initializes field data for each class. To do this, each class with accessible data members declares a static member function called initPersistFields(). This function calls the addField static member function for each data member of the class:
|
|
|
|
\code
|
|
void SampleObject::initPersistFields()
|
|
{
|
|
Parent::initPersistFields(); // adds the parent class's fields as well
|
|
addField("someVariable", TypeS32, Offset(someVariable, SampleObject));
|
|
}
|
|
\endcode
|
|
|
|
The type must be properly specified, and the Offset macro can be used to determine the relative address of the data member in the class. Once this is defined, scripts can use the member field of the object directly:
|
|
|
|
\code
|
|
function bar()
|
|
{
|
|
%obj = new SampleObject(MySampleObject);
|
|
%obj.someVariable = 100;
|
|
echo(%obj.someVariable);
|
|
}
|
|
\endcode
|
|
|
|
Field types are declared in engine/console/consoleTypes.h. New console data types can be added by adding a type to consoleTypes.h and calling Con::registerType(typeId, typeSize, getDataFunc, setDataFunc). See engine/console/consoleTypes.cc for examples of how types are defined.
|
|
|
|
\subsection ConDynamic Dynamically defined fields
|
|
Member fields of objects can also be defined from within the script itself. For example:
|
|
|
|
\code
|
|
function foo()
|
|
{
|
|
%object = new SampleObject();
|
|
%object.scriptVariable = "Hello World!";
|
|
echo(%object.scriptVariable);
|
|
}
|
|
\endcode
|
|
|
|
This script field is applicable only to that object instance.
|
|
|
|
\subsection ConNamespace Namespaces
|
|
Namespaces (declared in engine/console/consoleInternal.h) are collections of class member functions. Every SimObject belongs to exactly one namespace. By default, an object belongs to the namespace that corresponds to its class - so an instance of GameBase will have the GameBase namespace. Each namespace has a parent, so methods can invoke parent class methods by calling Parent::function(args). New namespaces (not class-based) can be added in the engine via the Con::linkNamespaces(parentNamespace, newNamespace) function. Assigning the mNameSpace field of SimObject then assigns the new namespace to an object. For example, in GuiControl::onAdd() (engine/gui/guiControl.cc), if a control has a name its name is used as its namespace. This allows a named GUI control to have special behavior for clicks or actions.
|
|
|
|
The ScriptObject class (defined in engine/console/scriptObject.cc) allows for the creation of "classes" within the scripting language:
|
|
|
|
\code
|
|
new ScriptObject(MyObject) {
|
|
class = Bar;
|
|
superClass = Foo;
|
|
};
|
|
|
|
function Bar::doSomething(%this)
|
|
{
|
|
echo("Hi!");
|
|
}
|
|
|
|
MyObject.doSomething();
|
|
> Hi!
|
|
|
|
function Foo::doSomething(%this)
|
|
{
|
|
echo("Hi! Foo");
|
|
}
|
|
|
|
function Bar::go(%this)
|
|
{
|
|
%this.doSomething();
|
|
Parent::doSomething(%this);
|
|
}
|
|
|
|
MyObject.go();
|
|
> Hi!
|
|
> Hi! Foo
|
|
\endcode
|
|
|
|
\subsection ConObject Objects
|
|
Every SimObject in the system can be addressed either by name or by id. So in the example above, MyObject.go() searches the object dictionary (engine/console/simBase.cc, engine/console/simManager.cc) for an object named MyObject and calls the go() method on that object.
|
|
|
|
\subsection ConDatablock DataBlocks
|
|
Datablocks are special objects that are used to transmit static data from server to client. Datablocks are declared as followed:
|
|
|
|
\code
|
|
datablock datablock_class (datablock_name)
|
|
{
|
|
field1 = value;
|
|
};
|
|
\endcode
|
|
|
|
More information about datablocks can be found in the network programming section <link>.
|
|
|
|
\subsection ConPackage Packages
|
|
Packages are collections of functions that can be enabled and disabled at runtime. Package functions can override (redefine) the behavior of existing functions in the global state or in packages that have been activated earlier. Prior versions of a function can then be accessed using the Parent call. For example:
|
|
|
|
\code
|
|
function foo()
|
|
{
|
|
echo("foo!");
|
|
}
|
|
|
|
package SamplePackage
|
|
{
|
|
|
|
function foo()
|
|
{
|
|
echo("Haha!");
|
|
Parent::foo();
|
|
}
|
|
|
|
}
|
|
|
|
foo();
|
|
> foo!
|
|
ActivatePackage(SamplePackage);
|
|
foo();
|
|
> Haha!
|
|
> foo!
|
|
\endcode
|
|
|
|
Packages are useful for creating mods to games or specific game modes.
|
|
|
|
\subsection ConVariable Variables
|
|
The console language supports global variables and local (function scoped) variables. Global variables are specified by a preceding $, and local variables by a % sign. Example:
|
|
|
|
\code
|
|
$someGlobal = "This is some global.";
|
|
|
|
function foo(%local1, %local2)
|
|
{
|
|
%local3 = $someGlobal;
|
|
}
|
|
\endcode
|
|
|
|
\subsection ConArray Arrays
|
|
The console language supports associative single- and multi-dimensional arrays. Arrays actually construct new variables with the names concatenated - so for example $array[10] is the same as $array10. Strings can be used as array indexes as well: $array["foo"] = 100;. Array dimensions are separated with commas inside the brackets - $array[1, 0] = 10;
|
|
|
|
\subsection ConStringOp Special String Operators
|
|
There are several special operators for strings in the scripting language:
|
|
|
|
$= Case insensitive string comparison. True if strings are equal.
|
|
!$= Negative case insensitive string comparison. True if strings are not equal
|
|
@ String concatenation operator: "Hello " @ "World!" == "Hello World!"
|
|
TAB String concatenation with a tab. "Hello" TAB "World!" == "Hello\tWorld!"
|
|
NL String concatenation with a newline. "Hello" NL "World!" == "Hello\nWorld!"
|
|
SPC String concatenation with a space. "Hello" SPC "World!" == "Hello World!"
|
|
|
|
\section ConCompiler Compiler
|
|
Scripts are executed in a two step process: First the script is compiled into a tokenized instruction stream (engine/console/compiler.*), then the instruction stream is processed using the compiled evaluator (engine/console/compiledEval.cc).
|
|
|
|
\section ConDebugger Debugger
|
|
The console supports remote debugging via another instance of the V12 example program. In the game instance to be debugged, debugger port and password must be set using the dbgSetParameters(port, password); Then, in the instance to be used as the debugger, the guis and scripts in base/debugger/ must be loaded. More info soon <should get this working>
|
|
|
|
\section ConInterface Interfacing with C++ code
|
|
The C++ game and engine code can be called from the scripts as described above, and the game code can also call into script using the console execute and evaluate functions:
|
|
|
|
\code
|
|
// simple execute of a console function using argv array:
|
|
const char *execute(S32 argc, const char* argv[]);
|
|
|
|
// simple execute of a console function, without stuffing an array
|
|
const char *executef(S32 argc, ...);
|
|
|
|
// execution of a method on a SimObject using argv array:
|
|
// first param is func name, second param MUST be empty
|
|
// also, MUST have at least those two params
|
|
const char *execute(SimObject *, S32 argc, const char *argv[]);
|
|
|
|
// execution of a method on a SimObject without stuffing an array
|
|
// first param is funcName, remaining params are args
|
|
const char *executef(SimObject *, S32 argc, ...);
|
|
|
|
// evaluation of an arbitrary console command script:
|
|
const char *evaluate(const char* string, bool echo, const char *fileName);
|
|
|
|
// evaluation of a formatted (ala printf) command string:
|
|
const char *evaluatef(const char* string, ...);
|
|
\endcode
|
|
|
|
Examples:
|
|
|
|
\code
|
|
SimObject *mySimObject = new SimObject;
|
|
|
|
Con::executef(mySimObject, 4, "doSomething", Con::getIntArg(20), "Bye", "Hi");
|
|
Con::evaluatef("mySimObject.doSomething(%d,\"%s\",\"%s\");", 20, "Bye", "Hi");
|
|
|
|
char *argv[5];
|
|
argv[0] = "doSomething";
|
|
argv[1] = NULL;
|
|
argv[2] = Con::getIntArg(20);
|
|
argv[3] = "Bye";
|
|
argv[4] = "Hi";
|
|
|
|
Con::execute(mySimObject, 5, argv);
|
|
\endcode
|
|
|
|
The functions Con::getIntArg, Con::getFloatArg and Con::getArgBuffer(size) are used to allocate on the console stack string variables that will be passed into the next console function called. This allows the console to avoid copying some data.
|
|
|
|
\section SimBase SimBase
|
|
SimBase (engine/console/simBase.*) defines the foundation SimObject classes that form the basis of the simulation engine.
|
|
|
|
SimObject is the base class for all objects that the console language can create and manipulate. All game classes (Player, InteriorInstance, Terrain, etc.) and GUI classes (GuiCanvas, GuiControl, etc). are all derived from SimObject. SimObject maintains the list of dynamic fields, has name and id properties, and can register itself with a global object manager.
|
|
|
|
SimSet is a simple collection of SimObjects. The set has console methods for adding and removing objects and iterating through the set.
|
|
|
|
SimGroup is a derivative of SimSet that "owns" the objects in its collection. When a SimGroup object is destroyed, it destroys all of its members. GuiControl is derived from SimGroup - thus making the Gui a hierarchal set of objects.
|
|
|
|
SimEvent is a special class objects can use to send time-delayed messages to objects.
|
|
|
|
\section SimManager SimManager
|
|
SimManager (engine/console/simManager.cc) is a collection of functions for managing all of the objects and events in the simulation. Objects are collected in a hierarchy of SimGroups and can be searched for by name or by object id.
|
|
Object Persistence and Inspection
|
|
Objects in the V12 can be saved to a script file using the SimObject::save() method. This basically dumps the current state of all the object's registered fields and dynamic fields, as well as, in the case of a SimGroup, all of that object's sub objects. GUIs and missions are examples of how the engine's editors save objects.
|
|
|
|
Objects' fields can also be modified in-game using the inspector, a GUI window that lists out the fields of an object and allows those fields to be changed and saved. Before new field values are to be saved to an object its onPreApply method will be called, then the fields will be changed, then the object's onPostApply method will be invoked.
|
|
Useful Console Commands
|
|
There are several console commands that are useful to get an idea of what's going on in the system: trace(true); turns on console trace output, trace(false); to disable it. Tree() displays a graphical hierarchy and object inspector for all objects currently registered in the system. SimObject::dump() dumps a list of all console commands and instance variables on an object, and SimSet::listObjects() lists all of the objects in a set.
|
|
*/
|
|
|
|
//-----------------------------------------------------------
|
|
/*! \page FileIO Files, Streams and the Resource Manager
|
|
|
|
\subsection FileOverview FileIO Files, Streams and the Resource Manager
|
|
The V12 engine uses many game resources - terrain files, bitmaps, shapes, material lists, fonts and interiors are all examples of game resources. In order to manage the large number of game resources effectively and provide a common interface for loading and saving resources, the V12 uses the ResourceManager (engine/core/resManager.*). Resources have the special property that only one instance of a resource will ever be loaded at a time. Resource objects are reference counted so that when a second request is made for the same resource, the original loaded instance is returned. The resource manager also defines a resource template class that acts as a transparent pointer to various types of game resources.
|
|
|
|
\subsection FilePaths Searchable Paths
|
|
Documentation in developement
|
|
|
|
\subsection FileVolumes Volume Files
|
|
Documentation in developement
|
|
\subsection FileResourceObject ResourceObject and template <class T> class Resource
|
|
Documentation in developement
|
|
*/
|
|
|
|
//-------------------------------------------------------------
|
|
/*! \page InputModel Input Model
|
|
|
|
\section InputOverview Overview
|
|
Input Events come from the host OS/platform, are translated in the platform layer and then posted to the game. By default the game checks the input event against a global action map (which supercedes all other action handlers). If there is no action specified for the event, it is passed on to the GUI system. If the GUI does not handle the input event it is passed to the currently active (non-global) action map stack.
|
|
Example: the user presses the ~ (tilde) key, which is bound in the global action map (in example/client/scripts/default.bind.cs) to toggleConsole. This causes the console function associated with the bind to be executed, which in this case is toggleConsole, resulting in the console output window being shown. If the key had not been bound in the global map, it would have passed to the first gui that could have handled it, and if none did, it would pass to any game actions that were bound to that key.
|
|
|
|
\section InputPlatform Platform Input
|
|
Platform specific code translates Win32/Xwindows/Mac events into uniform V12 input events. These events are posted into the main application event queue via Game->processEvent. The default behavior for the GameInterface class (engine/platform/gameInterface.*) is to pass all input events to Game->processInputEvent, which in the example V12Game (engine/game/V12Game.h, engine/game/main.cc) calls ActionMap::handleEventGlobal, followed by Canvas->processInputEvent (if not handled by the global map), and if neither of those handles it, passes it to ActionMap::handleEvent.
|
|
|
|
\section InputAction Action Maps
|
|
Action maps map platform input events to console commands. Any platform input event can be bound in a single generic way - so in theory the game doesn't need to know if the event came from the keyboard, mouse, joystick or some other input device. This allows users of the game to map keys and actions according to their own preferences.
|
|
|
|
\subsection InputBind ActionMap::bind(device, inputName, functionName)
|
|
Bind - calls the specified function name when the named input for the particular device changes state. The function named should take a single argument, which is the current numeric state value of the named input. For button inputs (keys, mouse and joystick buttons), a state value of 1 indicates that the key is depressed. So if the user presses and releases the w key (which in the example is bound to moveforward), the moveforward function will be called twice, first with a 1.0 as the value and then, upon key release with a value of 0. Non-binary devices (joystick and mouse axis) are called with values depending on the device type.
|
|
|
|
\subsection InputBindCmd ActionMap::bindCmd(device, inputName, downScript, upScript)
|
|
BindCmd - executes the console script downScript when the specified button input is depressed and upScript when it is released. Somewhat more useful than bind() in some cases.
|
|
|
|
\subsection InputGlobalMap Global Action Map
|
|
There is one defined ActionMap object that is processed first for all events called GlobalActionMap.
|
|
|
|
\subsection InputMapStack Action Map Stack
|
|
Game action maps are arranged in a stack for processing - so individual parts of the game can define specific actions - for example when the player jumps into a vehicle it could push a vehicle action map and pop the default player action map.
|
|
|
|
\subsection InputModKeys Modifier Keys
|
|
The inputName of an action can be modified by one of the three modifier keys - alt, shift and control. For example, the fullscreen/window toggle is bound to "alt enter". If an action is called with a modifier, releasing the modifier key(s) will not cause the break event to fire - only when the key itself is released.
|
|
|
|
\section InputGui GUI Event passing
|
|
See the section below on the GUI system <link>
|
|
*/
|
|
//----------------------------------------------------
|
|
/*! \page Graphics Graphics
|
|
|
|
\section GraphicsOverview Overview
|
|
The V12 Engine does not implement its own graphics rasterization layer. OpenGL was chosen as the graphics API for the V12 to primarily for its cross-platform nature and ease-of-use. The V12 includes a utility library called dgl (engine/dgl) that extends OpenGL to support higher level primitives and resources.
|
|
|
|
\section GraphicsInit Platform initialization
|
|
The platform layer is responsible for initializing the OpenGL state. For PlatformWin32 this can include loading a DLL that converts OpenGL calls to Direct3D (OpenGL2D3D.DLL).
|
|
|
|
\section GraphicsDgl Dgl
|
|
Dgl is a collection of utility classes and functions that add support for complex primitives/resources like fonts and bitmaps as well as add simple functions for more easily managing textures and 2D rasterization.
|
|
|
|
\subsection GraphicsTexture The Texture Manager
|
|
Dgl includes a texture manager (engine/dgl/gTexManager.*) that tracks the loading and unloading of all textures in the game. When the game requests a texture, it uses the TextureHandle class - which acts as a sort of special resource handle for textures in the game. Only one instance of a texture is ever loaded at once, and after load is handed off to OpenGL. When the game switches graphics modes or video devices, the Texture Manager can transparently reload and re-download all the game's textures.
|
|
|
|
\subsection GraphicsPrimitive Primitive support
|
|
GFont - fonts in the V12 are alpha textures created by the platform layer from OS dependent outline fonts. The font class is defined in engine/dgl/gFont.*
|
|
|
|
GBitmap - the V12 supports several bitmap file types - PNG, JPEG, GIF, BMP and the custom Bm8 format - an 8-bit color quantized texture format used to cut texture memory overhead. The Bitmap class is defined in engine/dgl/gBitmap.*
|
|
|
|
MaterialList - a material list is a resource that manages a list of bitmaps. It is used for shapes and interiors that have more than one texture.
|
|
|
|
\subsection GraphicsPrimRender Primitive rendering
|
|
The dgl 2D render support functions (declared in engine/dgl/dgl.h) support a wide variety of common 2D rendering primitives. Bitmaps (loaded as textures) can be rendered via the dglDrawBitmap, dglDrawBitmapStretch, dglDrawBitmapSR (sub-region), and dglDrawBitmapStretchSR functions. Text fonts can be rendered using dglDrawText and dglDrawTextN. Dgl also supports drawing of lines, rectangles and filled rectangles.
|
|
|
|
Unlike default OpenGL, the screen coordinate space set up for 2D rendering in the V12 is the traditional 2D scheme whereby the coordinate 0,0 is in the upper left corner of the screen and +Y goes down the screen. This requires (in the case of 3D) calling dglSetViewport rather than glViewport.
|
|
|
|
For 2D rendering, dgl viewport management is simplified by the dglSetClipRect function (defined in engine/dgl/dgl.cc).
|
|
|
|
\section Graphics3DRender 3D Rendering
|
|
There are several key differences in how the V12 does rendering from the default OpenGL. First, the coordinate system is set up to look down the +Y axis instead of -Z. This means dgl replaces the call to glFrustum with the dglSetFrustum function (defined in engine/dgl/dglMatrix.cc). Also, all V12 matrices are organized in standard C array form - with the second element in the array corresponding to the first row, second column. This is the opposite of OpenGL, so dgl supplies alternates to glLoadMatrix and glMultMatrix, appropriately named dglLoadMatrix and dglMultMatrix respectively.
|
|
|
|
3D points can be converted to 2D screen points using the dglPointToScreen function, and a measure of projected screen size of an object can be determined using the dglProjectRadius function.
|
|
*/
|
|
|
|
//----------------------------------------------------------------
|
|
/*! \page GUI The Graphical User Interface (GUI)
|
|
|
|
\section GUIOver Overview
|
|
The GUI library manages the user interface of V12 applications. Based very loosely on the NEXTSTEP interface class library, the GUI library is designed specifically for the needs of game UI development. Some important classes are:
|
|
|
|
GuiCanvas (engine/gui/guiCanvas.*) - The Canvas object, a singleton instance of the GuiCanvas class, is the root of the currently active GUI hierarchy. It is responsible for processing and dispatching mouse and keyboard events, managing update regions and cursor handling, and calling the GuiControl render methods when it is time to draw the next frame. The GuiCanvas keeps track of a stack of content controls - separate hierarchies of GuiControls that render from bottom to top. The main content control is a screen in the shell, and can be covered by any number of floating windows or dialogs. The root of each content control hierarchy is sized to cover the entire canvas, so generally dialogs are composed of a content control containing a dialog control.
|
|
|
|
GuiControlProfile (engine/gui/guiTypes.*) - The GuiControlProfile class instances maintain common instance data across a set of controls. Common information such as font face, colors, bitmaps and sound data are all stored in instances of GuiControlProfile, so that they don't need to be replicated on each control.
|
|
|
|
GuiControl (engine/gui/guiControl.*) - The GuiControl class is the root class for all the GUI controls in the system. GuiControl is derived from SimGroup and can contain any number of child GUI controls. Each GuiControl maintains a bounding rectangle in the coordinate system of its parent control. GuiControl has virtual methods for processing input events (onMouseDown, onMouseUp, onMouseEnter, onMouseLeave, onMouseMove, onMouseDragged, onRightMouseDown, onKeyDown, onKeyUp,onKeyRepeat), methods called by the Canvas for rendering (onPreRender, onRender), methods to control the lock focus of the mouse (mouseLock, mouseUnlock), coordinate conversion methods (localToGlobalCoord, globalToLocalCoord), and automatic sizing behavior when its parent control is resized (for example, when the screen resolution changes). When a control is made visible, it (if not already loaded) loads the data associated with its GuiControlProfile.
|
|
|
|
\section GuiInput Input Event Processing in the GUI system
|
|
Input events are first processed for dispatch in GuiCanvas::processInputEvent. Depending on the type of input event (keyboard or mouse), the Canvas processes the event in different ways.
|
|
|
|
For keyboard events, the Canvas maintains an instance variable call the first responder (mFirstResponder). The first responder is essentially the control that has keyboard focus - for example a text field that the user is typing into or a button that the user has tabbed to. For any keyboard event, the first responder has the first chance to process it. If it does not process the event it passes it up to its parent class and then in GuiControl finally to its parent control. If the keyboard event is not handled by any control in the responder chain (from first responder up to the root content control), the Canvas checks to see if it is a special key - tab and shift-tab set the first responder to the next and previous controls in the tab list respectively. If it is not tab or shift-tab, the Canvas checks to see if the key is in the accelerator map for any of the controls visible (for example an OK button mapped to the enter key). If it is not mapped, the Canvas passes back false, signifying that the event should be processed by the action map handler.
|
|
|
|
If the event is a mouse event and the cursor is on, it is handled by the GUI system. Each mouse event can potentially generate several virtual method calls to controls. For example, moving the mouse cursor from one control into another will cause the first control to receive an onMouseLeave method call, followed by an onMouseEnter method call to the new control and finally an onMouseMove to that control as well. GuiControls can lock the mouse focus on the canvas so that only that control receives mouse method invocations - for example the GuiButtonCtrl control locks the mouse on mouse down and unlocks it on mouse up so that even if the user moves the cursor outside the control, the button still maintains focus and can re-highlight when the cursor is moved back inside. The GuiControl virtual method pointInControl by default returns true if the cursor is inside the bounding rectangle of the control. Control subclasses with non-rectangular shapes can override this method to provide controls with irregular mouse boundaries.
|
|
|
|
\section GuiRender Control Rendering in the GUI system
|
|
The Canvas object is the root of the control rendering hierarchy and is responsible for the rendering process. When the game receives a time event from the platform layer it advances the state of the simulation and then instructs the canvas to render itself.
|
|
|
|
Before actually doing any rendering the Canvas instructs each control on the screen to onPreRender() itself, allowing each control to determine what if anything needs to be rendered on that control. The canvas, in order to render more quickly, maintains a set of "dirty" bounding boxes signifying areas of the screen that need to be repainted - a button may change, for example, if the mouse has just been pressed inside it, causing it to glow in the depressed state. During or before the onPreRender, a control can call the setUpdate() method on itself signifying that it needs to be repainted. Because the V12 engine works with page flipping devices and there may be as many as three separate buffers with old screen data, the Canvas maintains three dirty rectangles representing the dirty state of each of the (up to) three buffers.
|
|
|
|
After onPreRender, the Canvas renders the hierarchy of controls by telling the root of each layer (content control) to onRender() itself, and passing in a rectangle of the visible area of that control, which is initially set to the dirty area of the screen. When a control renders it can optionally call renderChildControls with this visible rectangle which will clip the rectangle to the bounds of each of its child controls, and if there is any area visible, will call onRender() on the child with the clipped rectangle as the visible area.
|
|
|
|
\section GuiConsole The GUI system and the Console
|
|
The GUI system is designed to work tightly with the Console language. The GuiControl class has an instance variable called mConsoleVariable which is the name of a global console variable that will, if set, reflect the state of that control in a control-dependant way. For example, a variable that is mapped to a checkbox control will have the value of 1 when the control is checked and 0 when it is not. Each GuiControl also has an instance variable called mConsoleCommand and mAltConsoleCommand that get executed in different cases depending on the control - in the case of a GuiButton the mConsoleCommand script gets evaluated when the button is clicked (in GuiControl::onAction()).
|
|
|
|
Another way the GUI system interacts with the console is via Console namespaces - when you name a control (like MainMenuQuitButton) that control name is registered as a namespace whose parent is the control's class, and special console methods can then be invoked from code on that control - in the case of a button the buttons onAction console method will be called when the button is pressed. Custom controls can be built in this way to execute several different commands with custom arguments.
|
|
*/
|
|
|
|
//-------------------------------------------------------
|
|
/*! \page Render3D 3D Rendering
|
|
|
|
\section Render3DOver Overview
|
|
The V12 library has a modular, extensible 3D world rendering system. Game subclass(es) of the GuiTSCtrl (defined in gui/guiTSControl.*) override the GuiTSCtrl::processCameraQuery and GuiTSCtrl::renderWorld methods to define the camera orientation/fov and draw the 3D scene using OpenGL drawing commands respectively. The GuiTSCtrl class manages the setting up the viewport, modelview matrix and projection matrix. The V12 example code GameTSCtrl class calls the global functions GameProcessCameraQuery and GameRenderWorld functions (defined in engine/game/game.cc). The GameProcessCameraQuery function returns the viewing camera of the current control object (the object in the simulation that the player is currently controlling) and then GameRenderWorld calls the client scene graph object to render the world.
|
|
|
|
\section Render3DSceneGraph SceneGraph
|
|
The scene graph library (engine/sceneGraph) is, on the client, responsible for traversing the world scene and determining which objects in the world should be rendered given the current camera position, and on the server, determines what objects should be sent to each client based on that client's position in the world.
|
|
|
|
The world in the SceneGraph is divided into zones - volumes of space bounded by solid areas and portals. The outside world is a single zone, and interior objects can have multiple interior zones. The SceneGraph::findZone() function finds the zone of a given 3D point and which SceneObject owns that zone. The SceneGraph::rezoneObject() function determines which zone or zones contain a SceneObject instance. At render time - SceneGraph::renderScene - the scene is traversed starting from the zone that contains the camera, clipping each zone's objects to the visible portal set from the zones before it. Scoping of network objects is performed in SceneGraph::scopeScene.
|
|
|
|
The scene graph traversal is complicated by transform portals. Transform portals are objects like mirrors or teleporters through which the world can be viewed using a different transform than the normal camera transform. When SceneGraph::buildSceneTree() encounters an object with a transform portal, it constructs a new SceneState object for rendering that portal's contents.
|
|
|
|
Every renderable world object in the scene derives from the SceneObject base class. As the world is traversed, visible objects are asked to prepare one or more SceneRenderImage objects (in SceneObject::prepRenderImage) that are then inserted into the current SceneState via SceneState::insertRenderImage. Render images are then sorted based on translucency and rendered from SceneObject::renderObject. This system allows, for example, an interior object with multiple translucent windows to render the building first, followed by other objects, followed by the building's windows. Objects can insert any number of images for rendering.
|
|
|
|
\section Render3DTerain Terrain
|
|
The terrain library (engine/terrain) is the home for objects that render the outside world, including instances of the Sky, TerrainBlock and WaterBlock classes. The Sky object renders the outside sky and cloud layers and maintains the visible distance and fog distance settings for the world. The sky also tracks vertical fog layers and installs them into the SceneGraph for rendering.
|
|
|
|
The TerrainBlock class (declared in engine/terrain/terrData.h) manages a single 256x256 infinitely repeating block of heightfield terrain. Terrain heightfield data is stored and loaded using the TerrainFile resource class (Resource<TerrainFile>) so that a single terrain data file can be shared between server and client, when both are on the same execution instance. The TerrainRender static class is used by TerrainBlock instances for rendering. The TerrainRender::renderBlock function renders the current repeating block of terrain.
|
|
|
|
The terrain is textured by software blending base material textures into new material textures and then mapping those across 16 or more terrain squares based on the distance from the square. The Blender class (engine/terrain/blender.*) performs the blending of terrain textures and includes a MMX assembly version to speed the process (x86 architectures only).
|
|
|
|
The WaterBlock class manages a single block of water, which may or may not be infinitely repeating. Water is dynamically detailed based on distance, so nearby water is more highly tessellated. Though the surface of a water block is rectangular, the actual coverage of the water area can be set to seed fill from a point on the surface, allowing the water to fill a mountain crater, for example, without leaking outside the corner edges.
|
|
|
|
\section RenderInterior Interior
|
|
The Interior library (engine/interior) manages the rendering, collision and IO for interior objects. The InteriorInstance SceneObject class manages a single interior. The InteriorResource class manages the data associated with one definition of an interior, multiple instances of which may exist at any one time. Interiors manage zones for the scene graph, and may have subobjects that, for example, render a mirrored view (MirrorSubObject). The InteriorLMManager class manages lightmaps for all currently loaded interiors - sharing lightmaps among instances where possible.
|
|
|
|
Interior resources are built and lit by the morian interior importer. The source files are just Quake-style .map files - lists of convex physical "brushes" that define the solid areas of the interior. Special brushes are used to define zone portal boundaries and objects such as doors and platforms.
|
|
|
|
\section Render3Space 3Space (TS)
|
|
The 3Space library (engine/ts) manages the display and animation of shape models in the world. The 3Space shape resource class TSShape can be shared between multiple TSShapeInstance instances. The TSShape class manages all the static data for a shape - mesh data, animation keyframes, material lists, decal information, triggers and detail levels (for dynamically detailed shapes).
|
|
|
|
The TSShapeInstance class manages animation, rendering and detail selection for an instance of a shape. The TSShapeInstance class uses the TSThread class to manage one of the concurrently running animations on an instance. The TSShapeInstance::addThread() method initializes a new thread on a shape instance, and TSShapeInstance::setSequence() sets an animation sequence for a given thread. Each thread can be individually advanced in time, or can be set on a time scale that is used when all threads are advanced in TSShapeInstance::advanceTime. A thread can also manage transitions between sequences with the TSShapeInstance::transitionToSequence method.
|
|
|
|
TSShape animation sequences can be composed of node/bone animation (for example, joints in an explosion), material animation (a texture animation on an explosion) and mesh animation (a morphing blob - note most mesh animations can be accomplished with node scale and rotation animations). Animations can also contain visibility tracks so that some meshes in the shape are not visible until an animation is played.
|
|
*/
|
|
|
|
//------------------------------------------------------
|
|
/*! \page Networking Networking
|
|
|
|
\section NetOver Overview
|
|
The V12 was designed from the foundation to offer robust client/server network simulation support. Performance over the internet drove the design for the networking model. The V12 attempts to deal with three fundamental problems of network simulation programming - limited bandwidth, packet loss and latency. For a more detailed, if somewhat outdated, description of the V12 network architecture, see <a href='http://www.garagegames.com/articles/gdc_networking/index.html'>"The Tribes II Engine Networking Model"</a> paper by Tim Gift and Mark Frohnmayer and the accompanying <a href='http://www.garagegames.com/articles/gdc_networking/slides.html'>PowerPoint slides</a>.
|
|
|
|
An instance of the V12 example can be set up as a dedicated server, a client, or a client and server both. If the game is a client and server both, it still behaves as a client connected to a server - instead of using the network, however, the NetConnection object has a short-circuit link to another NetConnection object in the same application instance.
|
|
|
|
Bandwidth is a problem because in the large, open environments the V12 allows, and with the large number of clients the V12 supports (up to 128 per server), potentially many different objects can be moving and updating at once. The V12 uses three main strategies to maximize available bandwidth. First, prioritize data - send updates to what is most "important" to a client at a greater frequency than update data that is less important. Second, send only data that is necessary - using the BitStream class, only the absolute minimum number of bits needed for a given piece of data can be sent. Also, when object state changes, the V12 only sends the part of the object state that changed. Last, the V12 caches common strings (NetStringTable) and data (SimDataBlock) so that they need only be transmitted once.
|
|
|
|
Packet loss is a problem because the information in lost data packets must somehow be retransmitted, yet in many cases the data in the dropped packet, if resent directly, will be stale by the time it gets to the client - for example, suppose that packet 1 contains a position update for a player and packet 2 contains a more recent position update for that same player. If packet 1 is dropped but packet 2 makes it across the engine shouldn't resend the data that was in packet 1 - it is older than the version that was received by the client. In order to minimize data that gets resent unnecessarily, the engine classifies data into four groups:
|
|
|
|
1. Unguaranteed Data (NetEvent) - if this data is lost, don't re-transmit it. An example of this type of data could be real-time voice traffic - by the time it is resent subsequent voice segments will already have played.
|
|
2. Guaranteed Data (NetEvent) - if this data is lost, resend it. Chat messages, messages for players joining and leaving the game and mission end messages are all examples of guaranteed data.
|
|
3. Most-Recent State Data (NetObject) - Only the most current version of the data is important - if an update is lost, send the current state, unless it has been sent already.
|
|
4. Guaranteed Quickest Data (Move) - critical data that must get through as soon as possible.
|
|
|
|
Latency is a problem in the simulation because the network delay in information transfer (which, for modems, can be up to a quarter of a second or more) makes the client's view of the world perpetually out-of-sync with the server. Twitch FPS games, for which the V12 was initially designed, require instant control response in order to feel anything but sluggish. Also, fast moving objects can be difficult for highly latent players to hit. In order to solve these problems the V12 employs several strategies:
|
|
|
|
1. Interpolation is used to smoothly move an object from where the client thinks it is to where the server says it is.
|
|
2. Extrapolation is used to guess where the object is going based on its state and rules of movement.
|
|
3. Prediction is used to form an educated guess about where an object is going based on rules of movement and client input.
|
|
|
|
The network architecture is layered: at the bottom is the OS/platform layer, above that the notify protocol layer, followed by the NetConnection object and event management layer. The following sections explain how each layer addresses some or all of the fundamental network simulation problems.
|
|
|
|
\section NetPlatform Platform Networking Layer (TCP/UDP)
|
|
The platform library provides the interface between the game engine and the OS dependent network functionality. The platform library's Net interface contains functions for opening reliable and unreliable communication sockets, converting between string and numeric network addresses and sending and receiving data.
|
|
|
|
The Net::openPort function opens an unreliable socket, of which only one is allowed per application instance. Net::sendto sends an unreliable datagram to the specified NetAddress. Net::openListenPort opens a reliable socket for incoming TCP connections. Net::openConnectTo begins the process of asynchronously connecting to a remote TCP socket. Net::sendtoSocket sends data over an established TCP connection. Net::process processes the platform network layer, possibly generating network related events that are then posted into the simulation via GameInterface::processEvent.
|
|
Connection Negotiation
|
|
The negotiation of a game network connection is not actually a part of the network class tree in the V12 - instead a set of functions, declared in engine/game/netDispatch.cc perform this service. The function V12Game::processPacketReceiveEvent is the main dispatch function for incoming network packets.
|
|
|
|
The first step of the connection process is the console function connect(), which initiates a connection attempt by sending a connect challenge request packet to the server from sendConnectChallengeRequest.
|
|
|
|
The server, in function handleConnectChallengeRequest, may issue the client a connect challenge response, which the client will process in handleConnectChallengeResponse. The client will in turn issue a connect request (sendConnectRequest) with the challenge information it received from the server. The server processes this message in handleConnectRequest. If the server decides to accept the request, it issues a sendConnectAccept back to the client and constructs a NetConnection object on the server to handle that client. The client, in handleConnectAccept creates a complementary NetConnection object to manage the client side of the connection. The dispatchCheckTimeouts function periodically checks if a connection request or challenge has been waiting too long and reissues the request if it has.
|
|
|
|
\section NetProtocol ConnectionProtocol
|
|
Once a connection has been established, the function of the ConnectionProtocol class is to provide a common low-level mechanism for supporting the delivery of the four fundamental types of network data in the V12. The ConnectionProtocol abstract base class implements a sliding window connected message stream over an unreliable transport (UDP). Rather than supporting guaranteed messages directly, the ConnectionProtocol class implements a notify protocol. Each packet sent is prepended with a message header containing tracking information, including what packets the other end of the connection has received or were dropped in transit. When a ConnectionProtocol instance determines that a packet it sent has been either received or dropped, it calls ConnectionProtocol::handleNotify. Notifies are always delivered in the order packets were sent - so for every packet sent through a ConnectionProtocol object, eventually a notification of successful (ack) or unsuccessful (nack) delivery will be executed.
|
|
|
|
Because the base network protocol exports the inherently unreliable nature of the network to the simulation, at a higher level the V12 can directly support different types of data guarantee: for unguaranteed data, if it is nacked, there is no need to resend it. For guaranteed data, if it is nacked, the engine queues it up for resend (NetConnection::eventPacketDropped). If the data is most recent state data and the packet is nacked and that object's state hasn't been subsequently changed and resent, queue the data up for resend (NetConnection::ghostPacketDropped). If the data is set for quickest possible delivery, continue sending the data with every packet until a packet containing the data is acked (GameConnection::readPacket).
|
|
|
|
\section NetConnection NetConnection
|
|
The NetConnection class is derivative from both SimGroup and ConnectionProtocol, and is responsible for managing the data streaming between client and server. The NetEvent class encapsulates the guaranteed and unguaranteed message delivery types and the ghost management portion of the NetConnection class handles state updates of world objects from server to client. The V12 example game-specific sublclass of NetConnection is GameConnection and handles transmission of game specific data such as player moves.
|
|
|
|
The NetConnection class sends packets of a fixed size in a regular stream between the client and server. When a message is posted for transmission, it is aggregated with other messages and sent based on the packet rate and packet size settings for that connection.
|
|
|
|
\section NetBitstream The BitStream
|
|
The BitStream class is a utility class used to pack data for transmission. BitStream has methods for reading and writing variable-sized integers (BitStream::readInt, BitStream::writeInt), floats, vectors, Huffman-coded strings and bits.
|
|
|
|
When a NetConnection instance determines it is ready to send a packet across the network (NetConnection::checkPacketSend), it allocates a BitStream and calls NetConnection::writePacket with the stream. When a packet is received it is processed through the corresponding NetConnection::readPacket function.
|
|
|
|
\section NetEvents Network Events
|
|
The NetEvent class provides a foundation for guaranteed, guaranteed ordered and unguaranteed message transmission. NetEvent uses the same class instance creation mechanism as the console, but rather than instantiating by name, NetEvents use a class id, assigned when the console initializes.
|
|
|
|
A simple network event:
|
|
\code
|
|
class SimpleMessageEvent : public NetEvent
|
|
{
|
|
char *msg;
|
|
public:
|
|
SimpleMessageEvent(const char *message = NULL)
|
|
{
|
|
if(message)
|
|
msg = dStrdup(message);
|
|
else
|
|
msg = NULL;
|
|
}
|
|
~SimpleMessageEvent()
|
|
{ dFree(msg); }
|
|
|
|
virtual void pack(NetConnection* ps, BitStream *bstream)
|
|
{ bstream->writeString(msg); }
|
|
virtual void write(NetConnection*, BitStream *bstream)
|
|
{ bstream->writeString(msg); }
|
|
virtual void unpack(NetConnection* ps, BitStream *bstream)
|
|
{ char buf[256]; bstream->readString(buf); msg = dStrdup(buf); }
|
|
virtual void process(NetConnection *connection)
|
|
{ Con::printf("RMSG %d %s", connection->getId(), msg); }
|
|
|
|
DECLARE_CONOBJECT(SimpleMessageEvent);
|
|
};
|
|
|
|
IMPLEMENT_CO_NETEVENT_V1(SimpleMessageEvent);
|
|
|
|
ConsoleMethod(NetConnection, sendMsg, void, 3, 3, "con.sendMsg(messageString)")
|
|
{
|
|
(NetConnection *) object)->postNetEvent(new SimpleMessageEvent(argv[2]));
|
|
}
|
|
\endcode
|
|
|
|
Some items to note - events have virtual methods to pack and unpack themselves into the network packet BitStream. If the read and write methods don't match in terms of what they read and write into the stream, serious network errors can occur. The client and server should gracefully disconnect in these cases, but the errors themselves can be very difficult to track down. If the DEBUG_NET macro is defined, a special key will be written into the packet stream after each event and object update, and the system will assert immediately when it detects that this problem has occurred.
|
|
|
|
NetEvent instances may be unpacked out of order (if, for example there was a dropped packet), so, for guaranteed ordered events, the process function will not be called until the previous events have been processed. The NetEvent::write function is specifically for demo recording - all as yet unprocessed events on the client side of a connection are written using this method.
|
|
|
|
All network events must use the DECLARE_CONOBJECT macro in the class definition, and must use the IMPLEMENT_CO_NETEVENT_V1 macro outside the class definition. There are two special forms of IMPLEMENT_CO_NETEVENT: IMPLEMENT_CO_CLIENTEVENT_V1, which specifies an event that can only travel from the server to the client, and IMPLEMENT_CO_SERVEREVENT_V1 which specifies an event that can only travel to the server.
|
|
|
|
\section NetGhost Network Ghosts and Scoping
|
|
The NetObject class is a derivative of SimObject that can replicate (ghost) itself across a network connection. All world object classes are subclassed from NetObject (the superclass of SceneObject). In order to best utilize the available bandwidth, the NetConnection attempts to determine which objects are "interesting" to each client - and among those objects, which ones are most important. If an object is interesting to a client it is said to be "in scope" - for example, a visible enemy to a player in a first person shooter would be in scope.
|
|
|
|
Each NetConnection object maintains a scoping object - responsible for determining which objects are in scope for that client. Before the NetConnection writes ghost update information into each packet in NetConnection::ghostWritePacket, it calls the scope object's onCameraScopeQuery function which performs two services: first, it determines which objects are "in scope" for that client and calls NetConnection::objectInScope for each object on that client. Second, the onCameraScopeQuery call fills in the CameraScopeQuery structure which is then used to determine the priority of object updates.
|
|
|
|
The default NetObject::onCameraScopeQuery function scopes everything in the world, but the V12 game example overrides this in ShapeBase::onCameraScopeQuery. ShapeBase calls the server SceneGraph::scopeScene function to traverse the scene from the client's point of view and scope all potentially visible objects. Each scoped object that needs to be updated is then prioritized based on the return value from the NetObject::getUpdatePriority function, which by default returns a constant value. This function is overridden in ShapeBase::getUpdatePriority to take into account the object's distance from the camera, its velocity perpendicular to the view vector, and other factors.
|
|
|
|
Rather than always sending the full state of the object each time it is updated across the network, the V12 supports only sending portions of the object's state that have changed. To facilitate this, each NetObject can specify up to 32 independent sub-states that can be modified individually. For example, a player object might have a movement state, detailing its position and velocity, a damage state, detailing its damage level and hit locations, and an animation state, signifying what animation, if any, the player is performing. Each state data group is assigned a bit position in the class. When an object's state changes, the object notifies the network system with the NetObject::setMaskBits function. When the object is to be written into a packet in NetObject::packUpdate, the object's current state mask is passed in. The object's state mask is NOT written into the packet directly - it is the responsibility of the pack function to accurately encode which states are updated.
|
|
|
|
Initially an object's state mask is set to all 1's - signifying that all the object's states need to be updated.
|
|
|
|
An example NetObject:
|
|
|
|
\code
|
|
class SimpleNetObject : public NetObject
|
|
{
|
|
public:
|
|
char message1[256];
|
|
char message2[256];
|
|
enum {
|
|
Message1Mask = (1 << 0),
|
|
Message2Mask = (1 << 1),
|
|
};
|
|
SimpleNetObject()
|
|
{
|
|
// in order for an object to be considered by the network system,
|
|
// the Ghostable net flag must be set.
|
|
// the ScopeAlways flag indicates that the object is always scoped
|
|
// on all active connections.
|
|
mNetFlags.set(ScopeAlways | Ghostable);
|
|
dStrcpy(message1, "Hello World 1!");
|
|
dStrcpy(message2, "Hello World 2!");
|
|
}
|
|
U32 packUpdate(NetConnection *, U32 mask, BitStream *stream)
|
|
{
|
|
// check which states need to be updated, and update them
|
|
if(stream->writeFlag(mask & Message1Mask))
|
|
stream->writeString(message1);
|
|
if(stream->writeFlag(mask & Message2Mask))
|
|
stream->writeString(message2);
|
|
// the return value from packUpdate can set which states still
|
|
// need to be updated for this object.
|
|
return 0;
|
|
}
|
|
void unpackUpdate(NetConnection *, BitStream *stream)
|
|
{
|
|
// the unpackUpdate function must be symmetrical to packUpdate
|
|
if(stream->readFlag())
|
|
{
|
|
stream->readString(message1);
|
|
Con::printf("Got message1: %s", message1);
|
|
}
|
|
if(stream->readFlag())
|
|
{
|
|
stream->readString(message2);
|
|
Con::printf("Got message2: %s", message2);
|
|
}
|
|
}
|
|
void setMessage1(const char *msg)
|
|
{
|
|
setMaskBits(Message1Mask);
|
|
dStrcpy(message1, msg);
|
|
}
|
|
void setMessage2(const char *msg)
|
|
{
|
|
setMaskBits(Message2Mask);
|
|
dStrcpy(message2, msg);
|
|
}
|
|
DECLARE_CONOBJECT(SimpleNetObject);
|
|
};
|
|
|
|
IMPLEMENT_CO_NETOBJECT_V1(SimpleNetObject);
|
|
|
|
ConsoleMethod(SimpleNetObject, setMessage1, void, 3, 3, "obj.setMessage1(msg)")
|
|
{
|
|
((SimpleNetObject *) object)->setMessage1(argv[2]);
|
|
}
|
|
|
|
ConsoleMethod(SimpleNetObject, setMessage2, void, 3, 3, "obj.setMessage2(msg)")
|
|
{
|
|
((SimpleNetObject *) object)->setMessage2(argv[2]);
|
|
}
|
|
\endcode
|
|
|
|
\section NetGameConnection GameConnection, Moves and the Control Object
|
|
The GameConnection class is the game-specific subclass of NetConnection. Applications can subclass NetConnection to directly write and read data from packets, as well as hook into the notify mechanism. The NetConnection::allocNotify function is called at the beginning of a packet write and is used to allocate a NetConnection::PacketNotify structure. This structure is used to store information about the data written into the network packet. When the packet is either acked or nacked, this notify structure is passed into the NetConnection::handleNotify function. Subclasses of NetConnection can subclass the PacketNotify structure and override the allocNotify method to add custom data to the packet tracking record.
|
|
|
|
The GameConnection in the V12 example introduces the concept of the control object. The control object is simply the object that the client associated with that network connection controls. By default in the example the control object is an instance of the Player class, but can also be an instance of Camera (when editing the mission, for example).
|
|
|
|
The V12 example uses a model in which the server is the authoritative master of the simulation. To prevent clients from cheating, the server simulates all player moves and then tells the client where his player is in the world. This model, while secure, can have problems - if the network latency is high, this round-trip time can give the player a very noticeable sense of movement lag. To correct this problem, the example uses a form of prediction - it simulates the movement of the control object on the client and on the server both. This way the client doesn't need to wait for round-trip verification of his moves - only in the case of a force acting on the control object on the server that doesn't exist on the client does the client's position need to be forcefully changed.
|
|
|
|
To support this, all control objects (derivative of ShapeBase) must supply a writePacketData and readPacketData function that send enough data to accurately simulate the object on the client. These functions are only called for the current control object, and only when the server can determine that the client's simulation is somehow out of sync with the server. This occurs usually if the client is affected by a force not present on the server (like an interpolating object) or if the server object is affected by a server only force (such as the impulse from an explosion).
|
|
|
|
The Move structure is a 32 millisecond snapshot of player input, containing x, y, and z positional and rotational changes as well as trigger state changes. When time passes in the simulation moves are collected (depending on how much time passes), and applied to the current control object on the client. The same moves are then packed over to the server in GameConnection::writePacket, for processing on the server's version of the control object.
|
|
|
|
\section NetDatablocks Datablocks
|
|
Datablocks (derivate of SimDataBlock) are used in the network system to store common instance data for objects. For example, a datablock may store animation data, model information, physical movement properties, etc, all of which are shared across a set of common objects. All declared datablocks are sent to clients upon connection as guaranteed events (SimDataBlockEvent), and can then be referenced and sent as part of the initial ghost update. An advantage of datablocks is that they are declared only on the server, so mods to the game can be created without forcing the client to downloading any script data.
|
|
|
|
\section NetStringTable NetStringTable
|
|
The NetStringTable class manages string data across connections. Every tagged string in the console - those enclosed by single quotes ('), will be sent across a connection only a single time. Every subsequent time that string is sent, an integer tag is substituted for the actual string data. Strings like player names can be added with the addTaggedString console function and removed with the removeTaggedString console function.
|
|
|
|
\section NetConsole Network Console Commands
|
|
There are two remote procedure call network console commands - commandToServer and commandToClient. The commandToServer function takes the form: commandToServer(functionNameTag, arg1, arg2, arg3, ... ), where functionNameTag is some string tag. This call is converted into a RemoteCommandEvent and set across to the server. Once there the server calls the local script function serverCmdXXX(clientId, arg1, arg2, arg3, ... ), where XXX is the text of the string tag. The commandToClient function takes the form: commandToClient(clientId, functionNameTag, arg1, arg2, arg3, ... ) where the clientId argument is the object id of the connection object to send to.
|
|
|
|
The commandTo* functions perform string argument substitution automatically using the in-string % modifier. For example:
|
|
|
|
\code
|
|
commandToClient('EchoMessage',
|
|
'This %1 guy is super %2',
|
|
'Got Milk?',
|
|
'slow at writing documentation');
|
|
\endcode
|
|
|
|
is executed on the client as:
|
|
|
|
\code function clientCmdEchoMessage(%message, %a1, %a2, %a3, %a4)
|
|
{
|
|
// tagged strings must be detagged in order to be displayed.
|
|
echo(detag(%message));
|
|
echo("a1 = " @ detag(%a1));
|
|
echo("a2 = " @ detag(%a2));
|
|
echo("a3 = " @ detag(%a3));
|
|
echo("a4 = " @ detag(%a4));
|
|
}
|
|
\endcode
|
|
|
|
and would echo:
|
|
\code
|
|
This Got Milk? guy is super slow at writing documentation
|
|
a1 = Got Milk?
|
|
a2 = slow at writing documentation
|
|
a3 =
|
|
a4 =
|
|
\endcode
|
|
|
|
The string substitution number (after the %) refers to the argument position n spaces after the current argument:
|
|
|
|
\code
|
|
CommandToClient('EchoMessage',
|
|
'%1 is a good %2 for %3',
|
|
'%1 the good %2',
|
|
'Role Model',
|
|
'SuperDood %1',
|
|
'the dude of super');
|
|
\endcode
|
|
|
|
Would echo:
|
|
|
|
\code
|
|
Role Model the good SuperDood the dude of super is a good Role Model
|
|
for SuperDood the dude of super
|
|
A1 = Role Model the good SuperDood the dude of super
|
|
A2 = Role Model
|
|
A3 = SuperDood the dude of super
|
|
A4 = the dude of super
|
|
\endcode
|
|
|
|
This functionality is especially useful for status and game messages coming from the server, because each text message compresses into just a small array of tag identifiers.
|
|
*/ |