{"id":315,"date":"2022-10-20T01:39:09","date_gmt":"2022-10-20T01:39:09","guid":{"rendered":"https:\/\/merserver.com\/?p=315"},"modified":"2022-10-20T01:39:09","modified_gmt":"2022-10-20T01:39:09","slug":"the-tribes-engine-networking-modelor-how-to-make-the-internet-rock-for-multiplayer-games","status":"publish","type":"post","link":"http:\/\/merserver.com\/index.php\/2022\/10\/20\/the-tribes-engine-networking-modelor-how-to-make-the-internet-rock-for-multiplayer-games\/","title":{"rendered":"The TRIBES Engine Networking Model<br>or How to Make the Internet Rock for Multi\u00adplayer Games"},"content":{"rendered":"\n<p><br>by Mark Frohnmayer and Tim Gift<br>Mark.Frohnmayer@Dynamix.com Tim.Gift@Dynamix.com<\/p>\n\n\n\n<p><br>Abstract<br>This paper discusses the networking model developed to support a<br>&#8220;real\u00adtime&#8221; multi\u00adplayer gaming environment. This model is being<br>developed for TRIBES II, and was first implemented in Starsiege<br>TRIBES, a multi\u00adplayer online team game published in December<br>&#8217;98. The three major features of this model are: support for<br>multiple data delivery requirements, partial object state updates<br>and a packet delivery notification protocol.<\/p>\n\n\n\n<p><br>Overview<br>Starsiege TRIBES supports two modes of play: single player or<br>multi\u00adplayer over a LAN or the Internet. The multi\u00adplayer mode<br>supports up to 128 human or AI controlled players in a single<br>game. Performance over the Internet drove the design of the<br>networking model. The model supports low end modem connections<br>and is designed to deal with low bandwidth, high latency and<br>intermittent packet loss.<br>The model deals primarily with the delivery of data and a key<br>concept is the classification of delivery requirements.<br>All data is classified into one of several requirement categories<br>and the design of each component in the model centers around<br>meeting those requirements. We organize transmitted data as<br>follows:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>Non\u00adguaranteed data is data that is never re\u00adtransmitted if<br>lost.<\/li><li>Guaranteed data is data that must be retransmitted if lost,<br>and delivered to the client in the order it was sent.<\/li><li>Most Recent State data is volatile data of which only the<br>latest version is of interest.<\/li><li>Guaranteed Quickest data is data that needs to be delivered in<br>the quickest possible manner.<br>The networking model is divided into three major components as<br>shown in Figure 1:<\/li><li>A Connection Layer that deals with notification and delivery<br>of packets between client and server. The features of this<br>layer along with a stream class provide the general<br>infrastructure on which the other layers are built.<\/li><li>A Stream Layer which provides packet stream management. This<br>layer employs five stream managers to deal with events, object<br>mirroring, input move management, static data and string<br>compression. Each of the five stream managers provides<br>different data delivery guarantees.<\/li><li>A Simulation Layer which manages all objects in the<br>simulation. A full description of this layer is outside the<br>scope of this article but several items are relevant: the<br>advancement of time, object scoping and client prediction.<br>Though Starsiege TRIBES employs a client\u00adserver connection model,<br>only a few of the stream managers are asymmetrical. Nothing<br>prevents this model from being used in peer\u00adto\u00adpeer or multiserver architectures.<br>Persistent Objects<br>Though polymorphic object persistence is not an integral part of<br>the networking model, it is a feature that is used extensively. A<br>large percentage of data transmission is done using persistent<br>objects and the polymorphic nature of the objects used to hide<br>the type and content of the data from the networking code.<br>Move<br>Manager<br>Stream Manager<br>Simulation Layer<br>Ghost<br>Manager<br>Event Manager<br>Datablock<br>Manager<br>String<br>Manager<br>Connection Manager<br>Platform Packet Module<br>Stream Layer<br>Connection Layer<br>Figure 1<br>Each class that is declared persistent registers a &#8220;class<br>representative&#8221; (ClassRep) object with the Persist manager and is<br>assigned a unique Class ID. The ClassRep is used by the Persist<br>manager to construct objects of the type it represents. By<br>mapping assigned Class IDs to ClassReps the Persist manager can<br>construct any persistent object given its class ID. This<br>construction by Class ID along with virtual read and write<br>methods provides basic polymorphic IO.<br>Writing an object into a stream is a two step process. First its<br>class ID is written, then the object&#8217;s virtual write method is<br>invoked. Reading an object involves reading the Class ID,<br>constructing the object using the Persist manager, and then<br>invoking its virtual read method. In this way both the object&#8217;s<br>class, and the data it reads and writes are hidden from the<br>stream managers.<br>Connection Layer<br>The Connection layer provides transmission of packets between<br>host machines and is divided into two modules, a platform packet<br>module and the Connection manager. The platform packet module<br>provides basic connectionless unreliable packet delivery,<br>currently implemented using standard UDP sockets. The Connection<br>manager provides a virtual connection between two hosts and while<br>it does not provide delivery guarantees, it does provide packet<br>delivery status notifications.<br>This notification guarantee is very important to the<br>architecture; a packet layer that supports only guaranteed or<br>non\u00adguaranteed packets would not be sufficient to support the<br>four basic data delivery modalities outlined above. How each<br>delivery mode is handled is delegated to a higher level \u00ad\u2013 the<br>connection manager only guarantees the correct notification of a<br>sent packet&#8217;s status. I.e., if a packet is notified as dropped it<br>was either dropped or delivered out of order (and subsequently<br>dropped), and if a packet is notified as delivered, it has been<br>delivered.<br>The Connection manager notifies the Stream layer of the status of<br>each packet in the order that they were sent. An overview of this<br>relationship is shown in Figure 2. Dropped packets are never retransmitted by the Connection manager; the Stream layer, and its<br>associated managers, handle all data guarantee mechanisms. Since<br>packets are never retransmitted, they are freed immediately after<br>transmission.<br>The Connection manager employs a sliding window protocol in order<br>to track the delivery of packets. When the window is full,<br>transmission stops until an Acknowledgment is received.<br>Acknowledgment of packets is only used to advance the window and<br>generate notification events. This protocol imposes an average<br>overhead of 3 bytes per packet.<br>Though not part of the protocol, an important feature of the<br>architecture is bit\u00adpacking provided by a custom bit stream<br>class. This class provides bit level read and write functions,<br>including read\/write functions for: a single bit, variable length<br>integers, variable length normalized floats, and Huffman<br>compressed strings. All packet data, including the header and the<br>sliding window protocol, are accessed through this stream. These<br>packing features are used extensively and virtually all data is<br>transmitted using the smallest number of bits possible.<br>Examples of bit packing using the bit stream.<br>Writing into the packet:<br>if (stream\u00ad&gt;writeBool(updateDamage)) { \/\/ Uses 1 bit<br>stream\u00ad&gt;writeInt(mDamageState, 2); \/\/ Uses 2 bits<br>if (mDamageState != Dead)<br>stream\u00ad&gt;writeInt(mDamageLevel,6);<br>if (stream\u00ad&gt;writeBool(mRepairActive))<br>Connection Layer<br>Connection Manager<br>Packet<br>Data<br>Packet Notify Event<br>Delivery Status<br>Stream Manager<br>Platform Packet Module<br>Packet<br>Data<br>Packet<br>Address<br>Data<br>Packet<br>Address<br>Data<br>Figure 2<br>stream\u00ad&gt;writeInt(mRepairRate,4);<br>}<br>The matching read method:<br>if (stream\u00ad&gt;readBool()) {<br>mDamageState = stream\u00ad&gt;readInt(2);<br>if (mDamageState != Dead)<br>mDamageLevel = stream\u00ad&gt;readInt(6);<br>mRepairActive = stream\u00ad&gt;readBool();<br>if (mRepairActive)<br>mRepairRate = stream\u00ad&gt;readInt(4);<br>}<br>Stream Layer<br>The Stream layer is comprised of a Stream manager and the Event,<br>Ghost, Move, Datablock and String managers. The Connection<br>manager deals with the frequency of packet transmission as well<br>as packet size and stream manager ordering. The individual<br>stream managers deal with the packing and delivering of data<br>including the various guarantee mechanisms. The String manager,<br>dealing primarily with the compression of string data, is not<br>covered in this article.<br>The Stream manager allocates and transmits packets to its<br>counterpart on a remote host. To control bandwidth, each Stream<br>manager has a packet update rate and size. These parameters are<br>set by the remote host\u2019s Stream manager and represent the amount<br>of data it\u2019s capable of receiving. Clients connecting to a<br>dedicated server set these values to represent the bandwidth of<br>their Internet connection. If a client is connecting to an ISP<br>with a 28.8 modem then it could set a rate of 10 packets per<br>second with a size of 200 to produce about 2K of data per second.<br>The client may change these parameters on the fly in response to<br>changes in line quality. To control its own out\u00adgoing bandwidth,<br>the server imposes a maximum bandwidth per client based on its<br>own connection quality.<br>Packets are allocated by the Stream manager and filled by the<br>Move, Event, and Ghost managers. Since the opportunity to write<br>into the stream is given to the managers in a fixed order, this<br>order forms a fixed priority among them. Once the requested<br>packet size is exceeded or all the managers are done, the packet<br>is handed off to the Connection manager for transmission. Figure<br>3 shows an example packet.<br>When a packet is delivered to the Stream layer for reading, it<br>simply hands off the packet to each stream manager in the same<br>fixed order and each manager is responsible for reading the data<br>written by its counterpart.<br>The Stream manager also provides a Transmission Record for each<br>packet that it constructs (shown in Figure 4). When a stream<br>manager stores data into a packet, it stores information<br>regarding that data in the Transmission Record. When a<br>Connection manager Notify Event occurs for a packet, the<br>Transmission Record for that packet is processed by the Stream,<br>Event and Ghost managers, and is used to provide delivery<br>guarantees. Since the Connection manager always produces Notify<br>Events for each packet in the order they were sent, the<br>Transmission Records are stored in a simple FIFO.<br>Event Stream Manager<br>The Event Stream manager is responsible for providing guaranteed<br>and non\u00adguaranteed delivery of event objects from one host to<br>another. Guaranteed events are also guaranteed to process in the<br>order they were sent. A sliding window is used to track the<br>status of events. All window management is performed using the<br>Transmission Records.<br>When the Event manager is given a packet to write into, it pops<br>events off its outgoing queue and writes them into the stream<br>until either the packet size is exceeded, the queue is empty, or<br>the event window is full. Events are persistent objects and are<br>written using the methods discussed earlier.<br>Once events are written into a stream, they are linked together<br>and attached to the Stream manager\u2019s Transmission Record. When<br>Packet 1<br>Move<br>Event 1<br>Event 2<br>Ghost State 1<br>Event 3<br>Ghost State 2<br>Event Manager Data<br>Ghost Manager Data<br>Move Manager Data<br>Figure 3: Example Packet<br>Header Stream Manager Data<br>the Stream manager is notified of a packet\u2019s status, the<br>Transmission Record for that packet is passed to the Event<br>manager. If the Connection manager signals the successful<br>delivery of the packet then the events attached to the record are<br>deleted and the sliding window updated. If the Connection manager<br>signals non\u00addelivery then the events associated with the lost<br>packet are simply pushed onto the head of the event queue for retransmission.<br>When the manager is given a packet stream to read, it unpacks and<br>constructs all the events. If the event is marked as guaranteed,<br>it is added to an ordered queue used to provide ordered<br>processing. Processing either happens immediately upon receipt<br>(for non\u00adguaranteed events) or as the ordered queue is advanced.<br>Since neither the transmitting nor the receiving Event managers<br>store delivery status information into the packet stream, this<br>protocol imposes very little overhead above that already imposed<br>by the Connection manager, normally 3 bits per packet and 1 bit<br>per event. If a packet is dropped an additional 7\u00ad14 bits per<br>packet may be written.<br>In packing, delivering and guaranteeing Event objects, the Event<br>manager provides a fundamental service used by many other<br>subsystems in the TRIBES II engine. Since events are packed,<br>unpacked and processed using virtual functions and the Persist<br>manager, the Event manager itself has no knowledge of the type or<br>contents of these events.<br>An example of a simple event:<br>class Signal: public Event {<br>enum {<br>Transmission<br>Record 1<br>Next Record<br>Event List<br>Ghost List<br>TR2<br>TR3<br>Event 1<br>Next Event<br>Event 2<br>Next Event<br>Event 3<br>Next Event<br>Figure 4: Transmission queue including record with<br>events<br>SignalBits = 4,<br>};<br>U8 signal;<br>public:<br>Signal(U8 s) {<br>guaranteed = true;<br>signal = s;<br>}<br>void packData(Bitstream* stream) {<br>stream\u00ad&gt;writeInt(signal,SignalBits);<br>}<br>void unpackData(Bitsream* stream){<br>signal = stream\u00ad&gt;readInt(SignalBits);<br>}<br>void process(NetConnection* ps){<br>printf(&#8220;Recieved signal %d from host %s&#8221;,<br>signal,ps\u00ad&gt;getObjectName());<br>}<br>};<br>Ghost Stream Manager<br>The Ghost manager provides two key functions: the &#8220;ghosting&#8221; of<br>objects from one host to another, and the transferring of state<br>information between the original object and its ghost. A ghost<br>is a copy of an object persisted and transmitted to a remote<br>host. An object may only have one ghost per Ghost manager (and<br>thus per remote host), but may be ghosted by several Ghost<br>managers at once (to different clients, for example). Ghosts are<br>created using a form of guaranteed delivery also used to support<br>partial state updates between an object and its ghost. State<br>data is considered volatile and transferred using a &#8220;Most Recent<br>State&#8221; algorithm.<br>Since ghosting an object involves network overhead, the ghost<br>manager does not ghost all objects in the simulation, but instead<br>has a concept of &#8220;scope.&#8221; Objects may come in and out of scope<br>for a manager for a number of different reasons (this process is<br>managed in the Simulation layer). When an object comes into<br>scope, its ghost is transferred to the remote host; when an<br>object goes out of scope its ghost is deleted. While an object<br>is in scope, state data is transferred between the object and its<br>ghost and is updated at a rate based on its priority and state<br>mask. The manager also supports the notion of &#8220;ghost always&#8221;<br>objects, which are always in scope.<br>When a new object comes into scope, the object is tagged with a<br>Ghost Record containing a Ghost ID and a State Mask. The Ghost ID<br>is assigned from a limited range and is used by the local manager<br>to address ghost objects on the remote host. The remote Ghost<br>manager maintains a dictionary, which translates Ghost IDs into<br>local ghost objects. When the manager transfers information from<br>an object to its ghost, its Ghost ID is embedded in the stream so<br>that the remote manager can properly deliver the data. The Ghost<br>Record\u2019s State Mask represents state data that an object is<br>interested in transferring and is the heart of the &#8220;Most Recent<br>State&#8221; algorithm. Each bit in the State Mask represents a class<br>dependant set of related data, or state, that will be<br>transferred. An object may represent its position and velocity as<br>one state bit, changes in rotation as another, and possibly a<br>change in animation state as a third. (TRIBES simulation objects<br>typically have upwards of 20 state flags.) Each state is tracked<br>and transferred independently of the others, providing the<br>ability to perform partial updates of an object\u2019s total state.<br>When the ghost manager is given a packet to fill by the stream<br>manager, it performs two basic actions. First, it builds an<br>update list which includes every object with a status change or<br>non\u00adzero State Mask. This update list is ordered first by status<br>change, then by object priority. Status changes include the<br>transferring or deleting of ghosts from the remote host; an<br>object&#8217;s priority is a value assigned by the Simulation layer as<br>part of the scoping process. Next, the update list is traversed<br>in order writing the Ghost ID, status and object state<br>information into the packet until the packet is filled or the<br>list is empty. For each object that contains data in the stream,<br>a transmission structure is constructed containing the status<br>change requested and the State Mask. This structure is attached<br>to the Transmission Record for the packet as shown in Figure 5.<br>The State Mask bits in the Transmission structure represent the<br>state data written into the stream by the object.<br>When a ghost manager receives a packet, it reads each set of<br>Ghost ID and status flags in order. If a status indicates a new<br>ghost, the Persist manager is called to construct the object and<br>the new ghost is entered into the Ghost ID dictionary. If the<br>status change is a deletion request, the ghost object is obtained<br>Transmission<br>Record 1<br>Next Record<br>Event List<br>Ghost List<br>TR2<br>TR3<br>Object 1 GTR1<br>Next Record<br>State Mask [111]<br>Next Object TR<br>Object 2 GTR2<br>Next Record<br>State Mask [101]<br>Next Object TR<br>Figure 5: Transmission queue including ghost transmission records for<br>objects 1 &amp; 2<br>Object 1 GTR3<br>Next Record<br>State Mask [010]<br>Next Object TR<br>from the dictionary and deleted. If there is no status change,<br>the packet contains only state data. The ghost object is<br>obtained from the ID dictionary and its unpack method is called<br>with the current packet bit stream. Which state mask bits were<br>used by the source object to pack data is not transmitted to the<br>ghost. The object is responsible for encoding such information<br>into the bit stream.<br>The guarantee of object state information happens as follows;<br>State Mask bits represent state changes, so if an object&#8217;s<br>position changes, it sets its &#8220;position state&#8221; bit in the State<br>Mask. If there are multiple managers ghosting this object, the<br>bit is set in the Ghost Record related to each manager. This set<br>bit now represents state data that the Ghost manager needs to<br>transfer to the object&#8217;s ghost. When the manager is filling a<br>packet, objects with a non\u00adzero State Masks are asked to write<br>into the packet stream given the current mask for that Ghost<br>manager. In the example of the &#8220;position state&#8221;, the object<br>writes its current position. At this point the State Mask<br>represents which states were written into the packet, and the<br>mask is stored in a transmission structure which is linked to the<br>Transmission Record. Once this is done, the State Mask is<br>cleared. If the packet is notified as lost, then the State Mask<br>for each object which had data in that packet is updated to<br>include lost bits. State bits are only considered lost if no<br>subsequently\u00adsent packet contained the same state bit for that<br>object. Since we guarantee only the latest state, if a later<br>packet has already been sent with a later version of that state,<br>then this requirement has been met and no other action takes<br>place.<br>In Figure 5, if the packet for Transmission Record 1 is lost then<br>bits [101] for object 1 and 2 are considered lost; whereas bit<br>[010] for object 1 is not, because GTR 3, a later update, has<br>that bit set. If a lost bit is added to an object&#8217;s State Mask,<br>then that state will get re\u00adtransmitted at the next opportunity.<br>Status changes, such as object construction and deletion, are<br>handled in a similar fashion.<br>Example of state masks:<br>An object&#8217;s position changes and it sets its Position State<br>bit to true. The ghost manager decides to pack the object&#8217;s<br>state and calls the object\u2019s pack method with the state<br>mask. The object sees that the manager wants to transmit<br>its position and writes its current position into the<br>packet bit stream. The manager stores the state mask<br>containing the Position State bit in the notification<br>structure for that packet and the State Mask is then<br>cleared.<br>First possibility: the packet is delivered and the state<br>has been successfully transferred. The object&#8217;s position<br>has not changed since the packet was sent and the State<br>Mask is still 0 so no further action takes place.<br>Second possibility: the packet is not delivered and in the<br>meantime the object&#8217;s position has not changed. The packet<br>is notified as lost. Since no packets have been sent with<br>new Position State data for the object, the Position State<br>bit is set in the object\u2019s State Mask. In this case the<br>same position will get resent the next time the object is<br>packed.<br>Third possibility: the packet is not delivered, but in the<br>meantime, the object&#8217;s position has changed. Before<br>notification of non\u00addelivery, another packet was already<br>sent with the latest position. In this case, the manager<br>knows that Position State data has already been<br>retransmitted and the loss of the state is ignored. This is<br>possible because the Ghost manager only guarantees that the<br>latest version of the state data is transferred and not any<br>particular version or copy of that state. In this example,<br>this is exactly the behavior we want \u00ad\u00ad the position<br>information contained in the lost packet is stale and we<br>don&#8217;t want to retransmit it. Unless the second packet is<br>notified as lost, we also don&#8217;t want to re\u00adtransmit the<br>current position, as it hasn&#8217;t changed since the second<br>packet was sent.<br>Fourth possibility: the packet is not delivered, and in the<br>meantime, the object&#8217;s position has changed. But the<br>position change was recent and no second packet has been<br>sent yet. Since no new packets have been sent with<br>Position State data for the object, the object&#8217;s Position<br>State bit is set by the Ghost manager. The object will<br>have already set the bit itself, so this will not change<br>the object\u2019s behavior. The next time the object is packed<br>it will write its current position, not the position sent<br>in the lost packet. Similar to the third possibility, the<br>position data lost in the dropped packet is never re\u00adsent.<br>Like the Connection manager, Event, and other stream managers, a<br>server will have a Ghost manager for each client connected to it.<br>If there are several Ghost managers ghosting an object, the<br>object will have a Ghost Record for each manager. Each manager<br>will have a different Ghost ID and State Mask for that object<br>representing the state of that object on its remote host. When<br>an object sets one of its state bits, it updates the State Mask<br>for every Ghost manager currently ghosting it and each manager<br>tracks and updates state information independently.<br>This process of tracking state masks and guaranteeing the<br>delivery of state information between object and their ghosts is<br>a key feature of TRIBES networking. Game simulation objects rely<br>almost exclusively on this mechanism to transfer information.<br>Move Stream Manager<br>The Move manager guarantees &#8220;soonest possible&#8221; delivery of client<br>input moves to the server and &#8220;soonest possible&#8221; delivery of<br>control object state data. A sliding window is used to track the<br>delivery of moves and also to synchronize move processing between<br>control objects and their ghosts. This manager is asymmetrical in<br>that moves are only sent from a &#8220;client&#8221; connection to a &#8220;server&#8221;<br>connection and Control Object state data from server object to<br>client ghost.<br>Input moves are used to control simulation objects such as<br>vehicles, cameras, and players. Input is gathered on the client<br>by an Input Manager, which collects a move every 32 milliseconds.<br>Moves consist of x, y and z translations, yaw, pitch and roll<br>rotations as well as an array of trigger states. These moves are<br>delivered to objects by the Move manager and are the sole means of<br>user controlled object movement.<br>The Move manager, similar to the Event and Ghost managers, writes<br>information into every packet stream allocated by the Stream<br>manager. Unlike the other managers, it makes no use of the<br>Transmission Record or the Connection manager&#8217;s notification<br>system. Instead, the Move manager provides &#8220;soonest possible&#8221;<br>delivery of moves to the server by writing moves into every packet<br>sent. Each move is transmitted three times in consecutive<br>packets. Each packet transmitted from the server contains the<br>last move received, which the client uses to advance a sliding<br>window of outstanding moves. If the window becomes full, the<br>client simulation is halted until the server advances the window.<br>If 3 packets are dropped in a row then any moves unique to those<br>packets are lost.<br>In Starsiege TRIBES, every move in the current window was<br>transmitted in every packet guaranteeing 100% delivery of all<br>moves, but this was sometimes the cause of a negative feedback<br>loop. A dropped packet would increase the size of the next packet<br>by widening the sliding window and causing more moves to be<br>transmitted. On low bandwidth Internet connections the increased<br>packet size could exceed the connection&#8217;s bandwidth, causing more<br>packets to be dropped. Though this means moves are no longer<br>guaranteed delivery, the client responds better when large numbers<br>of packets are dropped. Losing moves can cause the client and<br>server control object to get out of sync which may produce a<br>momentary \u201cwarping\u201d for the client.<br>The Control Object for a client is the object that receives the<br>current move being produced. Both the Control Object on the<br>server and its ghost on the client receive every move. Control<br>Objects have two major requirements that differentiate them from<br>other objects. The first is the deterministic processing of<br>moves. Both the original server object and its ghost are given<br>moves generated by the player and both, given the same starting<br>state, must produce the same end state. The second is that the<br>control object must be able to transfer its current state on<br>demand. Though Control Objects are ghosted, and thus can transfer<br>information using the ghost State Mask, the Move manager provides<br>a separate &#8220;Soonest possible&#8221; delivery of a Control Object&#8217;s state<br>to its ghost. This state information is similar but not identical<br>to information transmitted through the Ghost manager. State<br>information sent to a ghost is normally limited to that needed to<br>render the object, or to reasonably predict its motion. Control<br>Object state information must include everything needed to<br>deterministically process input moves. &#8220;Soonest possible&#8221;<br>delivery of this control state information is achieved by having<br>the Control Object write its current state into every packet sent<br>from the server. This combination of state transfer and<br>deterministic move processing is used to keep the Control Object<br>and its ghost synchronized, and to allow the client to predict the<br>motion of the control object ahead of server updates.<br>Delivering moves and Control Object state updates as soon as<br>possible along with client prediction of move processing allows<br>smooth and immediate response to player input, while providing<br>full validation of all player moves by the server.<br>Datablock Stream Manager<br>The Datablock manager provides &#8220;latest state&#8221; delivery of<br>datablocks from the server to the client. Datablocks are objects<br>which contain relatively static data and the manager guarantees<br>delivery only of the latest state at a very low frequency. Like<br>the Ghost manager, the Datablock manager is asymmetric, and<br>datablocks are only transmitted from server to client. The most<br>common use of datablocks is to transmit initialization or<br>reference data for ghosts.<br>Datablocks are copied to the client in a stream of guaranteed<br>events. Since datablocks are considered static they are not<br>updated during the normal course of a session, but only at fixed<br>times such as when first connecting to a server or during a map<br>change. A global \u201clast modified\u201d key is used to track changes to<br>datablocks. Each time a datablock is modified it is assigned the<br>current value of this key, after which the key is incremented.<br>The manager keeps the highest key value transmitted to its remote<br>host. Since the manager only guarantees &#8220;latest state&#8221;, if a<br>Datablock is modified several times between updates, this<br>information is lost to the client. Further, since the changes to<br>datablocks are low in frequency, the Ghost manager\u2019s overhead of<br>partial state updates is not necessary \u2013\u00ad a changed datablock is<br>retransmitted in its entirety.<br>Unlike the other Connection stream managers, the Datablock<br>manager does not write into, or read from the packet bit stream<br>produced by the Stream manager. Updates are invoked directly<br>from the scripting language and involve looping through all the<br>existing datablocks and transmitting every datablock with a<br>modified key greater than manager&#8217;s key. The actual transfer of<br>datablocks is performed by sending guaranteed Datablock Events<br>using the Event manager. A Datablock Event contains the datablock<br>object to transmit, and the datablock is packed as part of the<br>event.<br>As an example, the Vehicle class has an associated VehicleData<br>Datablock class. The Datablock class contains numerous vehicle<br>property attributes such as shape, maximum speed, maximum<br>acceleration, suspension properties for wheels, etc. When a<br>Vehicle object is constructed, it is assigned an instance of the<br>VehicleData Datablock. Each instance of the VehicleData<br>Datablock represents a different set of attributes describing a<br>different type of vehicle such as a tank, scout, transport, etc.<br>The Vehicle class itself represents the instance of that vehicle<br>type in the simulation and provides all the code and dynamic<br>attributes necessary to simulate the vehicle.<br>The advantage of this separation of static attribute data and<br>instance data is that Datablocks are normally only transmitted to<br>the client once at connect time. When a Vehicle object is<br>ghosted to the client (a process that can happen often) it sends<br>only its datablock&#8217;s ID along with other dynamic data relevant to<br>the ghost. This results in huge savings in bandwidth, while still<br>providing the flexibility of defining static attribute data on<br>the server. As shown in Figure 5, the client will mirror the<br>same relationship of Vehicle instance to VehicleData datablock.<br>Since datablocks are only transferred at times when the<br>simulation is effectively stopped, more bandwidth is available<br>and datablocks can contain more information than would normally<br>be possible. This also means the Datablock manager imposes no<br>extra overhead to the bitstream during the normal course of<br>simulation.<br>Server Client<br>VehicleData Datablock<br>Instance with static<br>vehicle attributes<br>VehicleData Datablock<br>Copy of static vehicle<br>attributes<br>Vehicle Class Object<br>Datablock<br>Dynamic vehicle<br>attributes<br>Vehicle Class Ghost<br>Datablock<br>Dynamic vehicle<br>attributes<br>Figure 5: Vehicle Datablock relationship mirrored on client<br>Simulation Layer<br>The Simulation layer performs a number of functions of which only<br>a few are relevant to a discussion of the networking model.<br>These are the advancement of time, scoping for the Ghost manager,<br>and client side prediction.<br>Advancement of simulation time is centered on the processing of<br>moves. Moves are gathered every 32 milliseconds, and the<br>simulation is advanced in 32 millisecond increments with a move<br>for each increment or tick. All objects on the client and server<br>are advanced in the same manner and if a Move manager is not<br>controlling an object, then it is passed NULL moves.<br>Though client objects are advanced in fixed increments, the<br>elapsed time between frames may not fall on even increments. To<br>present a smooth view, especially on a machine with a high frame<br>rate, time is always advanced to the beginning of the next<br>interval, and ghost objects maintain backwards interpolation<br>information which is used to &#8220;tween&#8221; frames between ticks.<br>Objects on the server are never rendered so they do not have to<br>perform this function.<br>Simulation objects must be scoped and prioritized for the ghost<br>manager. Scoping is the process of determining which objects on<br>the server are relevant to a particular client. In the simplest<br>sense, this means all objects that are potentially visible to a<br>client from that client&#8217;s current control object. This form of<br>scoping is performed using a spatial database maintained by the<br>simulation. When an object is determined to be in scope, the<br>object itself determines its own priority based on information<br>such as current ghost state mask, distance from the scoping<br>object, projected radius (using the scoping object\u2019s view<br>frustrum parameters), relative velocity, animation state and<br>interest modifiers (projectiles that are moving towards the<br>client are more interesting than vehicles, vehicles are more<br>interesting than items, etc.).<br>There are two forms of client side prediction in this model.<br>First is the Control Object move prediction performed by the Move<br>manager. This is by far the more complicated and provides the<br>foundation for controlling and predicting objects such as<br>players, vehicles, turrets and cameras. The other is prediction<br>performed by non\u00adcontrolled ghost objects such as projectiles,<br>items, etc. This level of prediction is left entirely to the<br>objects themselves and no supporting structure is provided.<br>An example of a prediction strategy is that provided by the Item<br>class, one of the simpler objects. This object is driven<br>entirely by the physics of the environment and does not process<br>input moves. By simply simulating normally the ghost will<br>correctly predict interaction with all static objects. If a<br>dynamic object is hit on the server, then its ghost state mask is<br>set to transfer its latest position and velocity. When the<br>client ghost receives an update from the server, it interpolates<br>its current position smoothly to the server&#8217;s updated position<br>and continues from there. More complex strategies are used by<br>the vehicle and player classes, which have to deal with<br>predicting movement from other clients.<br>Summary<br>By classifying data according to its delivery requirements and<br>organizing the stream managers to efficiently meet those<br>requirements, the Tribes II networking model attempts to make<br>optimal use of available bandwidth. In combination with dynamic<br>object scoping and prioritization of partial object state<br>updates, the TRIBES II engine allows smooth visualization of<br>large virtual environments over low bandwidth connections, even<br>when a substantial number of objects are visible to a client.<br>Though the model provides a solid foundation for network traffic,<br>it is not sufficient to simply provide a fast reliable connection<br>between hosts; there will always be bandwidth and latency issues<br>to contend with. Not covered in this article are: choices made<br>when deciding which object behavior should be managed by the<br>server and which by the clients, which object state information<br>to transfer, how to organize object partial states, how to<br>prioritize object state changes relative to each other, which<br>compression algorithm to use, and how to perform client side<br>prediction of Control Objects. All of these can have a dramatic<br>effect on perceived network performance.<br>This model was first implemented in Starsiege TRIBES, which<br>shipped in December of \u201898. The updated implementation in TRIBES<br>II has the following improvements:<\/li><li>Reorganization and simplification of the Connection and Stream<br>layers.<\/li><li>Better packet header compression for the Connection manager\u2019s<br>notification protocol.<\/li><li>A reduction in the number of times moves are re\u00adtransmitted to<br>avoid negative feedback loops due to increased packet size.<\/li><li>Better data compression by individual objects.<\/li><li>The addition of the String manager, which provides high order<br>compression for repeatedly transmitted character strings (not<br>discussed in this article).<br>Several modifications to the model have been considered, and<br>though they will not be implemented for TRIBES II, are worth<br>exploring. These include:<\/li><li>Variable production of packets by the Stream manager. Updates<br>are currently produced at a constant rate dictated by the<br>Stream manager\u2019s bandwidth settings. Packets could be<br>produced as soon as data is available for transmission or data<br>priority reaches a given threshold.<\/li><li>Asynchronous Connection processing on the server. All<br>Connection managers are currently processed at the same time<br>on the server in a single thread. The server architecture<br>could be changed to support a primary simulation thread with a<br>separate thread for each client. This would provide the<br>Connection and Stream managers with lower latency<br>communication with their remote host as well as provide better<br>support for multi\u00adprocessor hardware.<\/li><li>The addition of new stream managers, including a multiplexing<br>audio stream manager. TRIBES II will support voice using a<br>simpler approach.<\/li><li>Dynamic allocation of server bandwidth. Bandwidth on the<br>server is currently set at a fixed per client. The server<br>could be modified to adjust its per client bandwidth based on<br>the number of connected clients.<\/li><li>Determine a method of automatically and effectively<br>calculating the available bandwidth on a connection.<br>Bandwidth settings are currently set manually by a client.<br>Acknowledgments<br>Though several of the ideas presented here are unique to TRIBES,<br>others have been assimilated from other sources over countless<br>years of reading and the authors, being too lazy to research<br>their sources, would like to thank everyone in one sentence.<\/li><\/ol>\n","protected":false},"excerpt":{"rendered":"<p>by Mark Frohnmayer and Tim GiftMark.Frohnmayer@Dynamix.com Tim.Gift@Dynamix.com AbstractThis paper discusses the networking model developed to support a&#8220;real\u00adtime&#8221; multi\u00adplayer gaming environment. This model is beingdeveloped for TRIBES II, and was first implemented in StarsiegeTRIBES, a multi\u00adplayer online team game published in December&#8217;98. The three major features of this model are: support formultiple data delivery requirements, partial&#8230;<\/p>\n<p class=\"more-link-wrap\"><a href=\"http:\/\/merserver.com\/index.php\/2022\/10\/20\/the-tribes-engine-networking-modelor-how-to-make-the-internet-rock-for-multiplayer-games\/\" class=\"more-link\">Read More<span class=\"screen-reader-text\"> &ldquo;The TRIBES Engine Networking Model<br \/>or How to Make the Internet Rock for Multi\u00adplayer Games&rdquo;<\/span> &raquo;<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[],"class_list":["post-315","post","type-post","status-publish","format-standard","hentry","category-gamedev"],"_links":{"self":[{"href":"http:\/\/merserver.com\/index.php\/wp-json\/wp\/v2\/posts\/315","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/merserver.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/merserver.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/merserver.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/merserver.com\/index.php\/wp-json\/wp\/v2\/comments?post=315"}],"version-history":[{"count":1,"href":"http:\/\/merserver.com\/index.php\/wp-json\/wp\/v2\/posts\/315\/revisions"}],"predecessor-version":[{"id":316,"href":"http:\/\/merserver.com\/index.php\/wp-json\/wp\/v2\/posts\/315\/revisions\/316"}],"wp:attachment":[{"href":"http:\/\/merserver.com\/index.php\/wp-json\/wp\/v2\/media?parent=315"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/merserver.com\/index.php\/wp-json\/wp\/v2\/categories?post=315"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/merserver.com\/index.php\/wp-json\/wp\/v2\/tags?post=315"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}