123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517 |
- //-----------------------------------------------------------------------------
- // Copyright (c) 2012 GarageGames, LLC
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to
- // deal in the Software without restriction, including without limitation the
- // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- // sell copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- // IN THE SOFTWARE.
- //-----------------------------------------------------------------------------
- //~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
- // Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
- // Copyright (C) 2015 Faust Logic, Inc.
- //~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
- #ifndef _NETOBJECT_H_
- #define _NETOBJECT_H_
- #ifndef _SIMBASE_H_
- #include "console/simBase.h"
- #endif
- #ifndef _MMATH_H_
- #include "math/mMath.h"
- #endif
- //-----------------------------------------------------------------------------
- class NetConnection;
- class NetObject;
- //-----------------------------------------------------------------------------
- struct CameraScopeQuery
- {
- NetObject *camera; ///< Pointer to the viewing object.
- Point3F pos; ///< Position in world space
- Point3F orientation; ///< Viewing vector in world space
- F32 fov; ///< Viewing angle/2
- F32 sinFov; ///< sin(fov/2);
- F32 cosFov; ///< cos(fov/2);
- F32 visibleDistance; ///< Visible distance.
- };
- struct GhostInfo;
- //-----------------------------------------------------------------------------
- /// Superclass for ghostable networked objects.
- ///
- /// @section NetObject_intro Introduction To NetObject And Ghosting
- ///
- /// One of the most powerful aspects of Torque's networking code is its support
- /// for ghosting and prioritized, most-recent-state network updates. The way
- /// this works is a bit complex, but it is immensely efficient. Let's run
- /// through the steps that the server goes through for each client in this part
- /// of Torque's networking:
- /// - First, the server determines what objects are in-scope for the client.
- /// This is done by calling onCameraScopeQuery() on the object which is
- /// considered the "scope" object. This is usually the player object, but
- /// it can be something else. (For instance, the current vehicle, or a
- /// object we're remote controlling.)
- /// - Second, it ghosts them to the client; this is implemented in netGhost.cc.
- /// - Finally, it sends updates as needed, by checking the dirty list and packing
- /// updates.
- ///
- /// There several significant advantages to using this networking system:
- /// - Efficient network usage, since we only send data that has changed. In addition,
- /// since we only care about most-recent data, if a packet is dropped, we don't waste
- /// effort trying to deliver stale data.
- /// - Cheating protection; since we don't deliver information about game objects which
- /// aren't in scope, we dramatically reduce the ability of clients to hack the game and
- /// gain a meaningful advantage. (For instance, they can't find out about things behind
- /// them, since objects behind them don't fall in scope.) In addition, since ghost IDs are
- /// assigned per-client, it's difficult for any sort of co-ordination between cheaters to
- /// occur.
- ///
- /// NetConnection contains the Ghost Manager implementation, which deals with transferring data to
- /// the appropriate clients and keeping state in synch.
- ///
- /// @section NetObject_Implementation An Example Implementation
- ///
- /// The basis of the ghost implementation in Torque is NetObject. It tracks the dirty flags for the
- /// various states that the object trackers, and does some other book-keeping to allow more efficient
- /// operation of the networking layer.
- ///
- /// Using a NetObject is very simple; let's go through a simple example implementation:
- ///
- /// @code
- /// class SimpleNetObject : public NetObject
- /// {
- /// public:
- /// typedef NetObject Parent;
- /// DECLARE_CONOBJECT(SimpleNetObject);
- /// @endcode
- ///
- /// Above is the standard boilerplate code for a Torque class. You can find out more about this in SimObject.
- ///
- /// @code
- /// char message1[256];
- /// char message2[256];
- /// enum States {
- /// Message1Mask = BIT(0),
- /// Message2Mask = BIT(1),
- /// };
- /// @endcode
- ///
- /// For our example, we're having two "states" that we keep track of, message1 and message2. In a real
- /// object, we might map our states to health and position, or some other set of fields. You have 32
- /// bits to work with, so it's possible to be very specific when defining states. In general, you
- /// should try to use as few states as possible (you never know when you'll need to expand your object's
- /// functionality!), and in fact, most of your fields will end up changing all at once, so it's not worth
- /// it to be too fine-grained. (As an example, position and velocity on Player are controlled by the same
- /// bit, as one rarely changes without the other changing, too.)
- ///
- /// @code
- /// 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!", bufLen);
- /// dStrcpy(message2, "Hello World 2!", bufLen);
- /// }
- /// @endcode
- ///
- /// Here is the constructor. Here, you see that we initialize our net flags to show that
- /// we should always be scoped, and that we're to be taken into consideration for ghosting. We
- /// also provide some initial values for the message fields.
- ///
- /// @code
- /// 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;
- /// }
- /// @endcode
- ///
- /// Here's half of the meat of the networking code, the packUpdate() function. (The other half, unpackUpdate(),
- /// we'll get to in a second.) The comments in the code pretty much explain everything, however, notice that the
- /// code follows a pattern of if(writeFlag(mask & StateMask)) { ... write data ... }. The packUpdate()/unpackUpdate()
- /// functions are responsible for reading and writing the dirty bits to the bitstream by themselves.
- ///
- /// @code
- /// 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);
- /// }
- /// }
- /// @endcode
- ///
- /// The other half of the networking code in any NetObject, unpackUpdate(). In our simple example, all that
- /// the code does is print the new messages to the console; however, in a more advanced object, you might
- /// trigger animations, update complex object properties, or even spawn new objects, based on what packet
- /// data you unpack.
- ///
- /// @code
- /// void setMessage1(const char *msg)
- /// {
- /// setMaskBits(Message1Mask);
- /// dStrcpy(message1, msg, bufLen);
- /// }
- /// void setMessage2(const char *msg)
- /// {
- /// setMaskBits(Message2Mask);
- /// dStrcpy(message2, msg, bufLen);
- /// }
- /// @endcode
- ///
- /// Here are the accessors for the two properties. It is good to encapsulate your state
- /// variables, so that you don't have to remember to make a call to setMaskBits every time you change
- /// anything; the accessors can do it for you. In a more complex object, you might need to set
- /// multiple mask bits when you change something; this can be done using the | operator, for instance,
- /// setMaskBits( Message1Mask | Message2Mask ); if you changed both messages.
- ///
- /// @code
- /// IMPLEMENT_CO_NETOBJECT_V1(SimpleNetObject);
- ///
- /// ConsoleMethod(SimpleNetObject, setMessage1, void, 3, 3, "(string msg) Set message 1.")
- /// {
- /// object->setMessage1(argv[2]);
- /// }
- ///
- /// ConsoleMethod(SimpleNetObject, setMessage2, void, 3, 3, "(string msg) Set message 2.")
- /// {
- /// object->setMessage2(argv[2]);
- /// }
- /// @endcode
- ///
- /// Finally, we use the NetObject implementation macro, IMPLEMENT_CO_NETOBJECT_V1(), to implement our
- /// NetObject. It is important that we use this, as it makes Torque perform certain initialization tasks
- /// that allow us to send the object over the network. IMPLEMENT_CONOBJECT() doesn't perform these tasks, see
- /// the documentation on AbstractClassRep for more details.
- ///
- /// @nosubgrouping
- class NetObject : public SimGroup
- {
- // The Ghost Manager needs read/write access
- friend class NetConnection;
- friend struct GhostInfo;
- friend class ProcessList;
- // Not the best way to do this, but the event needs access to mNetFlags
- friend class GhostAlwaysObjectEvent;
- private:
- typedef SimGroup Parent;
- /// Mask indicating which states are dirty and need to be retransmitted on this
- /// object.
- U32 mDirtyMaskBits;
- /// @name Dirty List
- ///
- /// Whenever a NetObject becomes "dirty", we add it to the dirty list.
- /// We also remove ourselves on the destructor.
- ///
- /// This is done so that when we want to send updates (in NetConnection),
- /// it's very fast to find the objects that need to be updated.
- /// @{
- /// Static pointer to the head of the dirty NetObject list.
- static NetObject *mDirtyList;
- /// Next item in the dirty list...
- NetObject *mPrevDirtyList;
- /// Previous item in the dirty list...
- NetObject *mNextDirtyList;
- /// @}
- protected:
- /// Pointer to the server object on a local connection.
- /// @see getServerObject
- SimObjectPtr<NetObject> mServerObject;
- /// Pointer to the client object on a local connection.
- /// @see getClientObject
- SimObjectPtr<NetObject> mClientObject;
- enum NetFlags
- {
- IsGhost = BIT(1), ///< This is a ghost.
- ScopeAlways = BIT(6), ///< Object always ghosts to clients.
- ScopeLocal = BIT(7), ///< Ghost only to local client.
- Ghostable = BIT(8), ///< Set if this object CAN ghost.
- MaxNetFlagBit = 15
- };
- BitSet32 mNetFlags; ///< Flag values from NetFlags
- U32 mNetIndex; ///< The index of this ghost in the GhostManager on the server.
- GhostInfo *mFirstObjectRef; ///< Head of a linked list storing GhostInfos referencing this NetObject.
- public:
- NetObject();
- ~NetObject();
- virtual String describeSelf() const;
- /// @name Miscellaneous
- /// @{
- DECLARE_CONOBJECT(NetObject);
- static void initPersistFields();
- bool onAdd();
- void onRemove();
- /// @}
- static void collapseDirtyList();
- /// Used to mark a bit as dirty; ie, that its corresponding set of fields need to be transmitted next update.
- ///
- /// @param orMask Bit(s) to set
- virtual void setMaskBits(U32 orMask);
- /// Clear the specified bits from the dirty mask.
- ///
- /// @param orMask Bits to clear
- virtual void clearMaskBits(U32 orMask);
- virtual U32 filterMaskBits(U32 mask, NetConnection * connection) { return mask; }
- /// Scope the object to all connections.
- ///
- /// The object is marked as ScopeAlways and is immediately ghosted to
- /// all active connections. This function has no effect if the object
- /// is not marked as Ghostable.
- void setScopeAlways();
- /// Stop scoping the object to all connections.
- ///
- /// The object's ScopeAlways flag is cleared and the object is removed from
- /// all current active connections.
- void clearScopeAlways();
- /// This returns a value which is used to prioritize which objects need to be updated.
- ///
- /// In NetObject, our returned priority is 0.1 * updateSkips, so that less recently
- /// updated objects are more likely to be updated.
- ///
- /// In subclasses, this can be adjusted. For instance, ShapeBase provides priority
- /// based on proximity to the camera.
- ///
- /// @param focusObject Information from a previous call to onCameraScopeQuery.
- /// @param updateMask Current update mask.
- /// @param updateSkips Number of ticks we haven't been updated for.
- /// @returns A floating point value indicating priority. These are typically < 5.0.
- virtual F32 getUpdatePriority(CameraScopeQuery *focusObject, U32 updateMask, S32 updateSkips);
- /// Instructs this object to pack its state for transfer over the network.
- ///
- /// @param conn Net connection being used
- /// @param mask Mask indicating fields to transmit.
- /// @param stream Bitstream to pack data to
- ///
- /// @returns Any bits which were not dealt with. The value is stored by the networking
- /// system. Don't set bits you weren't passed.
- virtual U32 packUpdate(NetConnection * conn, U32 mask, BitStream *stream);
- /// Instructs this object to read state data previously packed with packUpdate.
- ///
- /// @param conn Net connection being used
- /// @param stream stream to read from
- virtual void unpackUpdate(NetConnection * conn, BitStream *stream);
- /// Queries the object about information used to determine scope.
- ///
- /// Something that is 'in scope' is somehow interesting to the client.
- ///
- /// If we are a NetConnection's scope object, it calls this method to determine
- /// how things should be scoped; basically, we tell it our field of view with camInfo,
- /// and have the opportunity to manually mark items as "in scope" as we see fit.
- ///
- /// By default, we just mark all ghostable objects as in scope.
- ///
- /// @param cr Net connection requesting scope information.
- /// @param camInfo Information about what this object can see.
- virtual void onCameraScopeQuery(NetConnection *cr, CameraScopeQuery *camInfo);
- /// Get the ghost index of this object.
- U32 getNetIndex() { return mNetIndex; }
- bool isServerObject() const; ///< Is this a server object?
- bool isClientObject() const; ///< Is this a client object?
- bool isGhost() const; ///< Is this is a ghost?
- bool isScopeLocal() const; ///< Should this object only be visible to the client which created it?
- bool isScopeable() const; ///< Is this object subject to scoping?
- bool isGhostable() const; ///< Is this object ghostable?
- bool isGhostAlways() const; ///< Should this object always be ghosted?
- /// @name Short-Circuited Networking
- ///
- /// When we are running with client and server on the same system (which can happen be either
- /// when we are doing a single player game, or if we're hosting a multiplayer game and having
- /// someone playing on the same instance), we can do some short circuited code to enhance
- /// performance.
- ///
- /// These variables are used to make it simpler; if we are running in short-circuited mode,
- /// the ghosted client gets the server object while the server gets the client object.
- ///
- /// @note "Premature optimization is the root of all evil" - Donald Knuth. The current codebase
- /// uses this feature in three small places, mostly for non-speed-related purposes.
- ///
- /// @{
-
- /// Returns a pointer to the server object when on a local connection.
- NetObject* getServerObject() const { return mServerObject; }
- /// Returns a pointer to the client object when on a local connection.
- NetObject* getClientObject() const { return mClientObject; }
-
- /// Template form for the callers convenience.
- template < class T >
- static T* getServerObject( T *netObj ) { return static_cast<T*>( netObj->getServerObject() ); }
- /// Template form for the callers convenience.
- template < class T >
- static T* getClientObject( T *netObj ) { return static_cast<T*>( netObj->getClientObject() ); }
- /// @}
- protected:
- U16 mScope_id;
- U16 mScope_refs;
- bool mScope_registered;
- virtual void onScopeIdChange() { }
- public:
- enum { SCOPE_ID_BITS = 14 };
- U16 getScopeId() const { return mScope_id; }
- U16 addScopeRef();
- void removeScopeRef();
- void setScopeRegistered(bool flag) { mScope_registered = flag; }
- bool getScopeRegistered() const { return mScope_registered; }
- protected:
- /// Add a networked field
- ///
- /// A networked field is a regular field but with a bitmask flag associated to it.
- /// When the field is set, it automatically triggers a call to setMaskBits with the mask associated to the field
- /// in order to streamline simple networking code
- /// Register a complex field.
- ///
- /// @param in_pFieldname Name of the field.
- /// @param in_fieldType Type of the field. @see ConsoleDynamicTypes
- /// @param in_fieldOffset Offset to the field from the start of the class; calculated using the Offset() macro.
- /// @param in_elementCount Number of elements in this field. Arrays of elements are assumed to be contiguous in memory.
- /// @param in_pFieldDocs Usage string for this field. @see console_autodoc
- static void addNetworkedField(const char* in_pFieldname,
- const U32 in_fieldType,
- const dsize_t in_fieldOffset,
- const U32 in_elementCount = 1,
- const char* in_pFieldDocs = NULL,
- U32 flags = 0,
- U32 networkMask = 0);
- static void addNetworkedField(const char* in_pFieldname,
- const U32 in_fieldType,
- const dsize_t in_fieldOffset,
- AbstractClassRep::WriteDataNotify in_writeDataFn,
- const U32 in_elementCount = 1,
- const char* in_pFieldDocs = NULL,
- U32 flags = 0,
- U32 networkMask = 0);
- /// Register a simple field.
- ///
- /// @param in_pFieldname Name of the field.
- /// @param in_fieldType Type of the field. @see ConsoleDynamicTypes
- /// @param in_fieldOffset Offset to the field from the start of the class; calculated using the Offset() macro.
- /// @param in_pFieldDocs Usage string for this field. @see console_autodoc
- static void addNetworkedField(const char* in_pFieldname,
- const U32 in_fieldType,
- const dsize_t in_fieldOffset,
- const char* in_pFieldDocs,
- U32 flags = 0,
- U32 networkMask = 0);
- static void addNetworkedField(const char* in_pFieldname,
- const U32 in_fieldType,
- const dsize_t in_fieldOffset,
- AbstractClassRep::WriteDataNotify in_writeDataFn,
- const char* in_pFieldDocs,
- U32 flags = 0,
- U32 networkMask = 0);
- };
- //-----------------------------------------------------------------------------
- inline bool NetObject::isGhost() const
- {
- return mNetFlags.test(IsGhost);
- }
- inline bool NetObject::isClientObject() const
- {
- return mNetFlags.test(IsGhost);
- }
- inline bool NetObject::isServerObject() const
- {
- return !mNetFlags.test(IsGhost);
- }
- inline bool NetObject::isScopeLocal() const
- {
- return mNetFlags.test(ScopeLocal);
- }
- inline bool NetObject::isScopeable() const
- {
- return mNetFlags.test(Ghostable) && !mNetFlags.test(ScopeAlways);
- }
- inline bool NetObject::isGhostable() const
- {
- return mNetFlags.test(Ghostable);
- }
- inline bool NetObject::isGhostAlways() const
- {
- AssertFatal(mNetFlags.test(Ghostable) || mNetFlags.test(ScopeAlways) == false,
- "That's strange, a ScopeAlways non-ghostable object? Something wrong here");
- return mNetFlags.test(Ghostable) && mNetFlags.test(ScopeAlways);
- }
- #endif
|