netObject.h

Engine/source/sim/netObject.h

More...

Classes:

class

Superclass for ghostable networked objects.

Detailed Description

  1
  2//-----------------------------------------------------------------------------
  3// Copyright (c) 2012 GarageGames, LLC
  4//
  5// Permission is hereby granted, free of charge, to any person obtaining a copy
  6// of this software and associated documentation files (the "Software"), to
  7// deal in the Software without restriction, including without limitation the
  8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  9// sell copies of the Software, and to permit persons to whom the Software is
 10// furnished to do so, subject to the following conditions:
 11//
 12// The above copyright notice and this permission notice shall be included in
 13// all copies or substantial portions of the Software.
 14//
 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 21// IN THE SOFTWARE.
 22//-----------------------------------------------------------------------------
 23
 24//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
 25// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
 26// Copyright (C) 2015 Faust Logic, Inc.
 27//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
 28
 29#ifndef _NETOBJECT_H_
 30#define _NETOBJECT_H_
 31
 32#ifndef _SIMBASE_H_
 33#include "console/simBase.h"
 34#endif
 35#ifndef _MMATH_H_
 36#include "math/mMath.h"
 37#endif
 38
 39
 40//-----------------------------------------------------------------------------
 41class NetConnection;
 42class NetObject;
 43
 44//-----------------------------------------------------------------------------
 45
 46struct CameraScopeQuery
 47{
 48   NetObject *camera;       ///< Pointer to the viewing object.
 49   Point3F pos;             ///< Position in world space
 50   Point3F orientation;     ///< Viewing vector in world space
 51   F32 fov;                 ///< Viewing angle/2
 52   F32 sinFov;              ///< sin(fov/2);
 53   F32 cosFov;              ///< cos(fov/2);
 54   F32 visibleDistance;     ///< Visible distance.
 55};
 56
 57struct GhostInfo;
 58
 59
 60//-----------------------------------------------------------------------------
 61/// Superclass for ghostable networked objects.
 62///
 63/// @section NetObject_intro Introduction To NetObject And Ghosting
 64///
 65/// One of the most powerful aspects of Torque's networking code is its support
 66/// for ghosting and prioritized, most-recent-state network updates. The way
 67/// this works is a bit complex, but it is immensely efficient. Let's run
 68/// through the steps that the server goes through for each client in this part
 69/// of Torque's networking:
 70///      - First, the server determines what objects are in-scope for the client.
 71///        This is done by calling onCameraScopeQuery() on the object which is
 72///        considered the "scope" object. This is usually the player object, but
 73///        it can be something else. (For instance, the current vehicle, or a
 74///        object we're remote controlling.)
 75///      - Second, it ghosts them to the client; this is implemented in netGhost.cc.
 76///      - Finally, it sends updates as needed, by checking the dirty list and packing
 77///        updates.
 78///
 79/// There several significant advantages to using this networking system:
 80///      - Efficient network usage, since we only send data that has changed. In addition,
 81///        since we only care about most-recent data, if a packet is dropped, we don't waste
 82///        effort trying to deliver stale data.
 83///      - Cheating protection; since we don't deliver information about game objects which
 84///        aren't in scope, we dramatically reduce the ability of clients to hack the game and
 85///        gain a meaningful advantage. (For instance, they can't find out about things behind
 86///        them, since objects behind them don't fall in scope.) In addition, since ghost IDs are
 87///        assigned per-client, it's difficult for any sort of co-ordination between cheaters to
 88///        occur.
 89///
 90/// NetConnection contains the Ghost Manager implementation, which deals with transferring data to
 91/// the appropriate clients and keeping state in synch.
 92///
 93/// @section NetObject_Implementation An Example Implementation
 94///
 95/// The basis of the ghost implementation in Torque is NetObject. It tracks the dirty flags for the
 96/// various states that the object trackers, and does some other book-keeping to allow more efficient
 97/// operation of the networking layer.
 98///
 99/// Using a NetObject is very simple; let's go through a simple example implementation:
100///
101/// @code
102/// class SimpleNetObject : public NetObject
103/// {
104/// public:
105///   typedef NetObject Parent;
106///   DECLARE_CONOBJECT(SimpleNetObject);
107/// @endcode
108///
109/// Above is the standard boilerplate code for a Torque class. You can find out more about this in SimObject.
110///
111/// @code
112///    char message1[256];
113///    char message2[256];
114///    enum States {
115///       Message1Mask = BIT(0),
116///       Message2Mask = BIT(1),
117///    };
118/// @endcode
119///
120/// For our example, we're having two "states" that we keep track of, message1 and message2. In a real
121/// object, we might map our states to health and position, or some other set of fields. You have 32
122/// bits to work with, so it's possible to be very specific when defining states. In general, you
123/// should try to use as few states as possible (you never know when you'll need to expand your object's
124/// functionality!), and in fact, most of your fields will end up changing all at once, so it's not worth
125/// it to be too fine-grained. (As an example, position and velocity on Player are controlled by the same
126/// bit, as one rarely changes without the other changing, too.)
127///
128/// @code
129///    SimpleNetObject()
130///    {
131///       // in order for an object to be considered by the network system,
132///       // the Ghostable net flag must be set.
133///       // the ScopeAlways flag indicates that the object is always scoped
134///       // on all active connections.
135///       mNetFlags.set(ScopeAlways | Ghostable);
136///       dStrcpy(message1, "Hello World 1!", bufLen);
137///       dStrcpy(message2, "Hello World 2!", bufLen);
138///    }
139/// @endcode
140///
141/// Here is the constructor. Here, you see that we initialize our net flags to show that
142/// we should always be scoped, and that we're to be taken into consideration for ghosting. We
143/// also provide some initial values for the message fields.
144///
145/// @code
146///    U32 packUpdate(NetConnection *, U32 mask, BitStream *stream)
147///    {
148///       // check which states need to be updated, and update them
149///       if(stream->writeFlag(mask & Message1Mask))
150///          stream->writeString(message1);
151///       if(stream->writeFlag(mask & Message2Mask))
152///          stream->writeString(message2);
153///
154///       // the return value from packUpdate can set which states still
155///       // need to be updated for this object.
156///       return 0;
157///    }
158/// @endcode
159///
160/// Here's half of the meat of the networking code, the packUpdate() function. (The other half, unpackUpdate(),
161/// we'll get to in a second.) The comments in the code pretty much explain everything, however, notice that the
162/// code follows a pattern of if(writeFlag(mask & StateMask)) { ... write data ... }. The packUpdate()/unpackUpdate()
163/// functions are responsible for reading and writing the dirty bits to the bitstream by themselves.
164///
165/// @code
166///    void unpackUpdate(NetConnection *, BitStream *stream)
167///    {
168///       // the unpackUpdate function must be symmetrical to packUpdate
169///       if(stream->readFlag())
170///       {
171///          stream->readString(message1);
172///          Con::printf("Got message1: %s", message1);
173///       }
174///       if(stream->readFlag())
175///       {
176///          stream->readString(message2);
177///          Con::printf("Got message2: %s", message2);
178///       }
179///    }
180/// @endcode
181///
182/// The other half of the networking code in any NetObject, unpackUpdate(). In our simple example, all that
183/// the code does is print the new messages to the console; however, in a more advanced object, you might
184/// trigger animations, update complex object properties, or even spawn new objects, based on what packet
185/// data you unpack.
186///
187/// @code
188///    void setMessage1(const char *msg)
189///    {
190///       setMaskBits(Message1Mask);
191///       dStrcpy(message1, msg, bufLen);
192///    }
193///    void setMessage2(const char *msg)
194///    {
195///       setMaskBits(Message2Mask);
196///       dStrcpy(message2, msg, bufLen);
197///    }
198/// @endcode
199///
200/// Here are the accessors for the two properties. It is good to encapsulate your state
201/// variables, so that you don't have to remember to make a call to setMaskBits every time you change
202/// anything; the accessors can do it for you. In a more complex object, you might need to set
203/// multiple mask bits when you change something; this can be done using the | operator, for instance,
204/// setMaskBits( Message1Mask | Message2Mask ); if you changed both messages.
205///
206/// @code
207/// IMPLEMENT_CO_NETOBJECT_V1(SimpleNetObject);
208///
209/// ConsoleMethod(SimpleNetObject, setMessage1, void, 3, 3, "(string msg) Set message 1.")
210/// {
211///    object->setMessage1(argv[2]);
212/// }
213///
214/// ConsoleMethod(SimpleNetObject, setMessage2, void, 3, 3, "(string msg) Set message 2.")
215/// {
216///    object->setMessage2(argv[2]);
217/// }
218/// @endcode
219///
220/// Finally, we use the NetObject implementation macro, IMPLEMENT_CO_NETOBJECT_V1(), to implement our
221/// NetObject. It is important that we use this, as it makes Torque perform certain initialization tasks
222/// that allow us to send the object over the network. IMPLEMENT_CONOBJECT() doesn't perform these tasks, see
223/// the documentation on AbstractClassRep for more details.
224///
225/// @nosubgrouping
226class NetObject : public SimGroup
227{
228   // The Ghost Manager needs read/write access
229   friend class  NetConnection;
230   friend struct GhostInfo;
231   friend class  ProcessList;
232
233   // Not the best way to do this, but the event needs access to mNetFlags
234   friend class GhostAlwaysObjectEvent;
235
236private:
237   typedef SimGroup Parent;
238
239   /// Mask indicating which states are dirty and need to be retransmitted on this
240   /// object.
241   U32 mDirtyMaskBits;
242
243   /// @name Dirty List
244   ///
245   /// Whenever a NetObject becomes "dirty", we add it to the dirty list.
246   /// We also remove ourselves on the destructor.
247   ///
248   /// This is done so that when we want to send updates (in NetConnection),
249   /// it's very fast to find the objects that need to be updated.
250   /// @{
251
252   /// Static pointer to the head of the dirty NetObject list.
253   static NetObject *mDirtyList;
254
255   /// Next item in the dirty list...
256   NetObject *mPrevDirtyList;
257
258   /// Previous item in the dirty list...
259   NetObject *mNextDirtyList;
260
261   /// @}
262protected:
263
264   /// Pointer to the server object on a local connection.
265   /// @see getServerObject
266   SimObjectPtr<NetObject> mServerObject;
267
268   /// Pointer to the client object on a local connection.
269   /// @see getClientObject
270   SimObjectPtr<NetObject> mClientObject;
271
272   enum NetFlags
273   {
274      IsGhost           =  BIT(1),   ///< This is a ghost.
275      ScopeAlways       =  BIT(6),  ///< Object always ghosts to clients.
276      ScopeLocal        =  BIT(7),  ///< Ghost only to local client.
277      Ghostable         =  BIT(8),  ///< Set if this object CAN ghost.
278
279      MaxNetFlagBit     =  15
280   };
281
282   BitSet32 mNetFlags;              ///< Flag values from NetFlags
283   U32 mNetIndex;                   ///< The index of this ghost in the GhostManager on the server.
284
285   GhostInfo *mFirstObjectRef;      ///< Head of a linked list storing GhostInfos referencing this NetObject.
286
287public:
288   NetObject();
289   ~NetObject();
290
291   virtual String describeSelf() const;
292
293   /// @name Miscellaneous
294   /// @{
295   DECLARE_CONOBJECT(NetObject);
296   static void initPersistFields();
297   bool onAdd();
298   void onRemove();
299   /// @}
300
301   static void collapseDirtyList();
302
303   /// Used to mark a bit as dirty; ie, that its corresponding set of fields need to be transmitted next update.
304   ///
305   /// @param   orMask   Bit(s) to set
306   virtual void setMaskBits(U32 orMask);
307
308   /// Clear the specified bits from the dirty mask.
309   ///
310   /// @param   orMask   Bits to clear
311   virtual void clearMaskBits(U32 orMask);
312   virtual U32 filterMaskBits(U32 mask, NetConnection * connection) { return mask; }
313
314   ///  Scope the object to all connections.
315   ///
316   ///  The object is marked as ScopeAlways and is immediately ghosted to
317   ///  all active connections.  This function has no effect if the object
318   ///  is not marked as Ghostable.
319   void setScopeAlways();
320
321   /// Stop scoping the object to all connections.
322   ///
323   /// The object's ScopeAlways flag is cleared and the object is removed from
324   /// all current active connections.
325   void clearScopeAlways();
326
327   /// This returns a value which is used to prioritize which objects need to be updated.
328   ///
329   /// In NetObject, our returned priority is 0.1 * updateSkips, so that less recently
330   /// updated objects are more likely to be updated.
331   ///
332   /// In subclasses, this can be adjusted. For instance, ShapeBase provides priority
333   /// based on proximity to the camera.
334   ///
335   /// @param  focusObject    Information from a previous call to onCameraScopeQuery.
336   /// @param  updateMask     Current update mask.
337   /// @param  updateSkips    Number of ticks we haven't been updated for.
338   /// @returns A floating point value indicating priority. These are typically < 5.0.
339   virtual F32 getUpdatePriority(CameraScopeQuery *focusObject, U32 updateMask, S32 updateSkips);
340
341   /// Instructs this object to pack its state for transfer over the network.
342   ///
343   /// @param   conn    Net connection being used
344   /// @param   mask    Mask indicating fields to transmit.
345   /// @param   stream  Bitstream to pack data to
346   ///
347   /// @returns Any bits which were not dealt with. The value is stored by the networking
348   ///          system. Don't set bits you weren't passed.
349   virtual U32  packUpdate(NetConnection * conn, U32 mask, BitStream *stream);
350
351   /// Instructs this object to read state data previously packed with packUpdate.
352   ///
353   /// @param   conn    Net connection being used
354   /// @param   stream  stream to read from
355   virtual void unpackUpdate(NetConnection * conn, BitStream *stream);
356
357   /// Queries the object about information used to determine scope.
358   ///
359   /// Something that is 'in scope' is somehow interesting to the client.
360   ///
361   /// If we are a NetConnection's scope object, it calls this method to determine
362   /// how things should be scoped; basically, we tell it our field of view with camInfo,
363   /// and have the opportunity to manually mark items as "in scope" as we see fit.
364   ///
365   /// By default, we just mark all ghostable objects as in scope.
366   ///
367   /// @param   cr         Net connection requesting scope information.
368   /// @param   camInfo    Information about what this object can see.
369   virtual void onCameraScopeQuery(NetConnection *cr, CameraScopeQuery *camInfo);
370
371   /// Get the ghost index of this object.
372   U32 getNetIndex() { return mNetIndex; }
373
374   bool isServerObject() const;  ///< Is this a server object?
375   bool isClientObject() const;  ///< Is this a client object?
376
377   bool isGhost() const;         ///< Is this is a ghost?
378   bool isScopeLocal() const;    ///< Should this object only be visible to the client which created it?
379   bool isScopeable() const;     ///< Is this object subject to scoping?
380   bool isGhostable() const;     ///< Is this object ghostable?
381   bool isGhostAlways() const;   ///< Should this object always be ghosted?
382
383
384   /// @name Short-Circuited Networking
385   ///
386   /// When we are running with client and server on the same system (which can happen be either
387   /// when we are doing a single player game, or if we're hosting a multiplayer game and having
388   /// someone playing on the same instance), we can do some short circuited code to enhance
389   /// performance.
390   ///
391   /// These variables are used to make it simpler; if we are running in short-circuited mode, 
392   /// the ghosted client gets the server object while the server gets the client object.
393   ///
394   /// @note "Premature optimization is the root of all evil" - Donald Knuth. The current codebase
395   ///       uses this feature in three small places, mostly for non-speed-related purposes.
396   ///
397   /// @{
398   
399   /// Returns a pointer to the server object when on a local connection.
400   NetObject* getServerObject() const { return mServerObject; }
401
402   /// Returns a pointer to the client object when on a local connection.
403   NetObject* getClientObject() const { return mClientObject; }
404   
405   /// Template form for the callers convenience.
406   template < class T >
407   static T* getServerObject( T *netObj ) { return static_cast<T*>( netObj->getServerObject() ); }   
408
409   /// Template form for the callers convenience.
410   template < class T >
411   static T* getClientObject( T *netObj ) { return static_cast<T*>( netObj->getClientObject() ); }
412
413   /// @}
414protected:
415   U16           mScope_id;
416   U16           mScope_refs;
417   bool          mScope_registered;
418   virtual void  onScopeIdChange() { }
419public:
420   enum { SCOPE_ID_BITS = 14 };
421   U16           getScopeId() const { return mScope_id; }
422   U16           addScopeRef();
423   void          removeScopeRef();
424   void          setScopeRegistered(bool flag) { mScope_registered = flag; }
425   bool          getScopeRegistered() const { return mScope_registered; }
426
427protected:
428   /// Add a networked field
429   ///
430   /// A networked field is a regular field but with a bitmask flag associated to it.
431   /// When the field is set, it automatically triggers a call to setMaskBits with the mask associated to the field
432   /// in order to streamline simple networking code
433   /// Register a complex field.
434   ///
435   /// @param  in_pFieldname     Name of the field.
436   /// @param  in_fieldType      Type of the field. @see ConsoleDynamicTypes
437   /// @param  in_fieldOffset    Offset to  the field from the start of the class; calculated using the Offset() macro.
438   /// @param  in_elementCount   Number of elements in this field. Arrays of elements are assumed to be contiguous in memory.
439   /// @param  in_pFieldDocs     Usage string for this field. @see console_autodoc
440   static void addNetworkedField(const char*   in_pFieldname,
441      const U32     in_fieldType,
442      const dsize_t in_fieldOffset,
443      const U32     in_elementCount = 1,
444      const char*   in_pFieldDocs = NULL,
445      U32 flags = 0,
446      U32 networkMask = 0);
447
448   static void addNetworkedField(const char*   in_pFieldname,
449      const U32     in_fieldType,
450      const dsize_t in_fieldOffset,
451      AbstractClassRep::WriteDataNotify in_writeDataFn,
452      const U32     in_elementCount = 1,
453      const char*   in_pFieldDocs = NULL,
454      U32 flags = 0,
455      U32 networkMask = 0);
456
457   /// Register a simple field.
458   ///
459   /// @param  in_pFieldname  Name of the field.
460   /// @param  in_fieldType   Type of the field. @see ConsoleDynamicTypes
461   /// @param  in_fieldOffset Offset to  the field from the start of the class; calculated using the Offset() macro.
462   /// @param  in_pFieldDocs  Usage string for this field. @see console_autodoc
463   static void addNetworkedField(const char*   in_pFieldname,
464      const U32     in_fieldType,
465      const dsize_t in_fieldOffset,
466      const char*   in_pFieldDocs,
467      U32 flags = 0,
468      U32 networkMask = 0);
469
470   static void addNetworkedField(const char*   in_pFieldname,
471      const U32     in_fieldType,
472      const dsize_t in_fieldOffset,
473      AbstractClassRep::WriteDataNotify in_writeDataFn,
474      const char*   in_pFieldDocs,
475      U32 flags = 0,
476      U32 networkMask = 0);
477};
478
479//-----------------------------------------------------------------------------
480
481inline bool NetObject::isGhost() const
482{
483   return mNetFlags.test(IsGhost);
484}
485
486inline bool NetObject::isClientObject() const
487{
488   return mNetFlags.test(IsGhost);
489}
490
491inline bool NetObject::isServerObject() const
492{
493   return !mNetFlags.test(IsGhost);
494}
495
496inline bool NetObject::isScopeLocal() const
497{
498   return mNetFlags.test(ScopeLocal);
499}
500
501inline bool NetObject::isScopeable() const
502{
503   return mNetFlags.test(Ghostable) && !mNetFlags.test(ScopeAlways);
504}
505
506inline bool NetObject::isGhostable() const
507{
508   return mNetFlags.test(Ghostable);
509}
510
511inline bool NetObject::isGhostAlways() const
512{
513   AssertFatal(mNetFlags.test(Ghostable) || mNetFlags.test(ScopeAlways) == false,
514               "That's strange, a ScopeAlways non-ghostable object?  Something wrong here");
515   return mNetFlags.test(Ghostable) && mNetFlags.test(ScopeAlways);
516}
517
518#endif
519