tge/engine/console/simBase.h
2025-02-17 23:17:30 -06:00

1369 lines
49 KiB
C++
Executable File

//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _SIMBASE_H_
#define _SIMBASE_H_
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
#ifndef _TALGORITHM_H_
#include "core/tAlgorithm.h"
#endif
#ifndef _BITSET_H_
#include "core/bitSet.h"
#endif
#ifndef _CONSOLEOBJECT_H_
#include "console/consoleObject.h"
#endif
#ifndef _SIMDICTIONARY_H_
#include "console/simDictionary.h"
#endif
#ifndef _PLATFORMMUTEX_H_
#include "platform/platformMutex.h"
#endif
#ifndef _PLATFORMSEMAPHORE_H_
#include "platform/platformSemaphore.h"
#endif
//---------------------------------------------------------------------------
/// Definition of some basic Sim system constants.
///
/// These constants define the range of ids assigned to datablocks
/// (DataBlockObjectIdFirst - DataBlockObjectIdLast), and the number
/// of bits used to store datablock IDs.
///
/// Normal Sim objects are given the range of IDs starting at
/// DynamicObjectIdFirst and going to infinity. Sim objects use
/// a SimObjectId to represent their ID; this is currently a U32.
///
/// The RootGroupId is assigned to gRootGroup, in which most SimObjects
/// are addded as child members. See simManager.cc for details, particularly
/// Sim::initRoot() and following.
enum SimObjectsConstants
{
DataBlockObjectIdFirst = 3,
DataBlockObjectIdBitSize = 10,
DataBlockObjectIdLast = DataBlockObjectIdFirst + (1 << DataBlockObjectIdBitSize) - 1,
DynamicObjectIdFirst = DataBlockObjectIdLast + 1,
InvalidEventId = 0,
RootGroupId = 0xFFFFFFFF,
};
class SimEvent;
class SimObject;
class SimGroup;
class SimManager;
class Namespace;
class BitStream;
class Stream;
class LightManager;
typedef U32 SimTime;
typedef U32 SimObjectId;
/// A vector of SimObjects.
///
/// As this inherits from VectorPtr, it has the full range of vector methods.
class SimObjectList: public VectorPtr<SimObject*>
{
static S32 QSORT_CALLBACK compareId(const void* a,const void* b);
public:
void pushBack(SimObject*); ///< Add the SimObject* to the end of the list, unless it's already in the list.
void pushBackForce(SimObject*); ///< Add the SimObject* to the end of the list, moving it there if it's already present in the list.
void pushFront(SimObject*); ///< Add the SimObject* to the start of the list.
void remove(SimObject*); ///< Remove the SimObject* from the list; may disrupt order of the list.
/// Remove the SimObject* from the list; guaranteed to preserve list order.
void removeStable(SimObject* pObject);
void sortId(); ///< Sort the list by object ID.
};
//---------------------------------------------------------------------------
/// Represents a queued event in the sim.
///
/// Sim provides an event queue for your convenience, which
/// can be used to schedule events. A few things which use
/// this event queue:
///
/// - The scene lighting system. In order to keep the game
/// responsive while scene lighting occurs, the lighting
/// process is divided into little chunks. In implementation
/// terms, there is a subclass of SimEvent called
/// SceneLightingProcessEvent. The process method of this
/// subclass calls into the lighting code, telling it to
/// perform the next chunk of lighting calculations.
/// - The schedule() console function uses a subclass of
/// SimEvent called SimConsoleEvent to keep track of
/// scheduled events.
class SimEvent
{
public:
SimEvent *nextEvent; ///< Linked list details - pointer to next item in the list.
SimTime startTime; ///< When the event was posted.
SimTime time; ///< When the event is scheduled to occur.
U32 sequenceCount; ///< Unique ID. These are assigned sequentially based on order
/// of addition to the list.
SimObject *destObject; ///< Object on which this event will be applied.
SimEvent() { destObject = NULL; }
virtual ~SimEvent() {} ///< Destructor
///
/// A dummy virtual destructor is required
/// so that subclasses can be deleted properly
/// Function called when event occurs.
///
/// This is where the meat of your event's implementation goes.
///
/// See any of the subclasses for ideas of what goes in here.
///
/// The event is deleted immediately after processing. If the
/// object referenced in destObject is deleted, then the event
/// is not called. The even will be executed unconditionally if
/// the object referenced is NULL.
///
/// @param object Object stored in destObject.
virtual void process(SimObject *object)=0;
};
/// Implementation of schedule() function.
///
/// This allows you to set a console function to be
/// called at some point in the future.
class SimConsoleEvent : public SimEvent
{
protected:
S32 mArgc;
char **mArgv;
bool mOnObject;
public:
/// Constructor
///
/// Pass the arguments of a function call, optionally on an object.
///
/// The object for the call to be executed on is specified by setting
/// onObject and storing a reference to the object in destObject. If
/// onObject is false, you don't need to store anything into destObject.
///
/// The parameters here are passed unmodified to Con::execute() at the
/// time of the event.
///
/// @see Con::execute(S32 argc, const char *argv[])
/// @see Con::execute(SimObject *object, S32 argc, const char *argv[])
SimConsoleEvent(S32 argc, const char **argv, bool onObject);
~SimConsoleEvent();
virtual void process(SimObject *object);
};
/// Used by Con::threadSafeExecute()
struct SimConsoleThreadExecCallback
{
void *sem;
const char *retVal;
SimConsoleThreadExecCallback();
~SimConsoleThreadExecCallback();
void handleCallback(const char *ret);
const char *waitForResult();
};
class SimConsoleThreadExecEvent : public SimConsoleEvent
{
SimConsoleThreadExecCallback *cb;
public:
SimConsoleThreadExecEvent(S32 argc, const char **argv, bool onObject, SimConsoleThreadExecCallback *callback);
virtual void process(SimObject *object);
};
//---------------------------------------------------------------------------
/// Dictionary to keep track of dynamic fields on SimObject.
class SimFieldDictionary
{
friend class SimFieldDictionaryIterator;
public:
struct Entry
{
StringTableEntry slotName;
char *value;
Entry *next;
};
private:
enum
{
HashTableSize = 19
};
Entry *mHashTable[HashTableSize];
static Entry *mFreeList;
static void freeEntry(Entry *entry);
static Entry *allocEntry();
/// In order to efficiently detect when a dynamic field has been
/// added or deleted, we increment this every time we add or
/// remove a field.
U32 mVersion;
public:
const U32 getVersion() const { return mVersion; }
SimFieldDictionary();
~SimFieldDictionary();
void setFieldValue(StringTableEntry slotName, const char *value);
const char *getFieldValue(StringTableEntry slotName);
void writeFields(SimObject *obj, Stream &strem, U32 tabStop);
void printFields(SimObject *obj);
void assignFrom(SimFieldDictionary *dict);
};
class SimFieldDictionaryIterator
{
SimFieldDictionary * mDictionary;
S32 mHashIndex;
SimFieldDictionary::Entry * mEntry;
public:
SimFieldDictionaryIterator(SimFieldDictionary*);
SimFieldDictionary::Entry* operator++();
SimFieldDictionary::Entry* operator*();
};
//---------------------------------------------------------------------------
/// Base class for objects involved in the simulation.
///
/// @section simobject_intro Introduction
///
/// SimObject is a base class for most of the classes you'll encounter
/// working in Torque. It provides fundamental services allowing "smart"
/// object referencing, creation, destruction, organization, and location.
/// Along with SimEvent, it gives you a flexible event-scheduling system,
/// as well as laying the foundation for the in-game editors, GUI system,
/// and other vital subsystems.
///
/// @section simobject_subclassing Subclassing
///
/// You will spend a lot of your time in Torque subclassing, or working
/// with subclasses of, SimObject. SimObject is designed to be easy to
/// subclass.
///
/// You should not need to override anything in a subclass except:
/// - The constructor/destructor.
/// - processArguments()
/// - onAdd()/onRemove()
/// - onGroupAdd()/onGroupRemove()
/// - onNameChange()
/// - onStaticModified()
/// - onDeleteNotify()
/// - onEditorEnable()/onEditorDisable()
/// - inspectPreApply()/inspectPostApply()
/// - things from ConsoleObject (see ConsoleObject docs for specifics)
///
/// Of course, if you know what you're doing, go nuts! But in most cases, you
/// shouldn't need to touch things not on that list.
///
/// When you subclass, you should define a typedef in the class, called Parent,
/// that references the class you're inheriting from.
///
/// @code
/// class mySubClass : public SimObject {
/// typedef SimObject Parent;
/// ...
/// @endcode
///
/// Then, when you override a method, put in:
///
/// @code
/// bool mySubClass::onAdd()
/// {
/// if(!Parent::onAdd())
/// return false;
///
/// // ... do other things ...
/// }
/// @endcode
///
/// Of course, you want to replace onAdd with the appropriate method call.
///
/// @section simobject_lifecycle A SimObject's Life Cycle
///
/// SimObjects do not live apart. One of the primary benefits of using a
/// SimObject is that you can uniquely identify it and easily find it (using
/// its ID). Torque does this by keeping a global hierarchy of SimGroups -
/// a tree - containing every registered SimObject. You can then query
/// for a given object using Sim::findObject() (or SimSet::findObject() if
/// you want to search only a specific set).
///
/// @code
/// // Three examples of registering an object.
///
/// // Method 1:
/// AIClient *aiPlayer = new AIClient();
/// aiPlayer->registerObject();
///
/// // Method 2:
/// ActionMap* globalMap = new ActionMap;
/// globalMap->registerObject("GlobalActionMap");
///
/// // Method 3:
/// bool reg = mObj->registerObject(id);
/// @endcode
///
/// Registering a SimObject performs these tasks:
/// - Marks the object as not cleared and not removed.
/// - Assigns the object a unique SimObjectID if it does not have one already.
/// - Adds the object to the global name and ID dictionaries so it can be found
/// again.
/// - Calls the object's onAdd() method. <b>Note:</b> SimObject::onAdd() performs
/// some important initialization steps. See @ref simobject_subclassing "here
/// for details" on how to properly subclass SimObject.
/// - If onAdd() fails (returns false), it calls unregisterObject().
/// - Checks to make sure that the SimObject was properly initialized (and asserts
/// if not).
///
/// Calling registerObject() and passing an ID or a name will cause the object to be
/// assigned that name and/or ID before it is registered.
///
/// Congratulations, you have now registered your object! What now?
///
/// Well, hopefully, the SimObject will have a long, useful life. But eventually,
/// it must die.
///
/// There are a two ways a SimObject can die.
/// - First, the game can be shut down. This causes the root SimGroup
/// to be unregistered and deleted. When a SimGroup is unregistered,
/// it unregisters all of its member SimObjects; this results in everything
/// that has been registered with Sim being unregistered, as everything
/// registered with Sim is in the root group.
/// - Second, you can manually kill it off, either by calling unregisterObject()
/// or by calling deleteObject().
///
/// When you unregister a SimObject, the following tasks are performed:
/// - The object is flagged as removed.
/// - Notifications are cleaned up.
/// - If the object is in a group, then it removes itself from the group.
/// - Delete notifications are sent out.
/// - Finally, the object removes itself from the Sim globals, and tells
/// Sim to get rid of any pending events for it.
///
/// If you call deleteObject(), all of the above tasks are performed, in addition
/// to some sanity checking to make sure the object was previously added properly,
/// and isn't in the process of being deleted. After the object is unregistered, it
/// deallocates itself.
///
/// @section simobject_editor Torque Editors
///
/// SimObjects are one of the building blocks for the in-game editors. They
/// provide a basic interface for the editor to be able to list the fields
/// of the object, update them safely and reliably, and inform the object
/// things have changed.
///
/// This interface is implemented in the following areas:
/// - onNameChange() is called when the object is renamed.
/// - onStaticModified() is called whenever a static field is modified.
/// - inspectPreApply() is called before the object's fields are updated,
/// when changes are being applied.
/// - inspectPostApply() is called after the object's fields are updated.
/// - onEditorEnable() is called whenever an editor is enabled (for instance,
/// when you hit F11 to bring up the world editor).
/// - onEditorDisable() is called whenever the editor is disabled (for instance,
/// when you hit F11 again to close the world editor).
///
/// (Note: you can check the variable gEditingMission to see if the mission editor
/// is running; if so, you may want to render special indicators. For instance, the
/// fxFoliageReplicator renders inner and outer radii when the mission editor is
/// runnning.)
///
/// @section simobject_console The Console
///
/// SimObject extends ConsoleObject by allowing you to
/// to set arbitrary dynamic fields on the object, as well as
/// statically defined fields. This is done through two methods,
/// setDataField and getDataField, which deal with the complexities of
/// allowing access to two different types of object fields.
///
/// Static fields take priority over dynamic fields. This is to be
/// expected, as the role of dynamic fields is to allow data to be
/// stored in addition to the predefined fields.
///
/// The fields in a SimObject are like properties (or fields) in a class.
///
/// Some fields may be arrays, which is what the array parameter is for; if it's non-null,
/// then it is parsed with dAtoI and used as an index into the array. If you access something
/// as an array which isn't, then you get an empty string.
///
/// <b>You don't need to read any further than this.</b> Right now,
/// set/getDataField are called a total of 6 times through the entire
/// Torque codebase. Therefore, you probably don't need to be familiar
/// with the details of accessing them. You may want to look at Con::setData
/// instead. Most of the time you will probably be accessing fields directly,
/// or using the scripting language, which in either case means you don't
/// need to do anything special.
///
/// The functions to get/set these fields are very straightforward:
///
/// @code
/// setDataField(StringTable->insert("locked", false), NULL, b ? "true" : "false" );
/// curObject->setDataField(curField, curFieldArray, STR.getStringValue());
/// setDataField(slotName, array, value);
/// @endcode
///
/// <i>For advanced users:</i> There are two flags which control the behavior
/// of these functions. The first is ModStaticFields, which controls whether
/// or not the DataField functions look through the static fields (defined
/// with addField; see ConsoleObject for details) of the class. The second
/// is ModDynamicFields, which controls dynamically defined fields. They are
/// set automatically by the console constructor code.
///
/// @nosubgrouping
class SimObject: public ConsoleObject
{
typedef ConsoleObject Parent;
friend class SimManager;
friend class SimGroup;
friend class SimNameDictionary;
friend class SimManagerNameDictionary;
friend class SimIdDictionary;
//-------------------------------------- Structures and enumerations
private:
/// Flags for use in mFlags
enum {
Deleted = BIT(0), ///< This object is marked for deletion.
Removed = BIT(1), ///< This object has been unregistered from the object system.
Added = BIT(3), ///< This object has been registered with the object system.
Selected = BIT(4), ///< This object has been marked as selected. (in editor)
Expanded = BIT(5), ///< This object has been marked as expanded. (in editor)
ModStaticFields = BIT(6), ///< The object allows you to read/modify static fields
ModDynamicFields = BIT(7) ///< The object allows you to read/modify dynamic fields
};
public:
/// @name Notification
/// @{
struct Notify {
enum Type {
ClearNotify, ///< Notified when the object is cleared.
DeleteNotify, ///< Notified when the object is deleted.
ObjectRef, ///< Cleverness to allow tracking of references.
Invalid ///< Mark this notification as unused (used in freeNotify).
} type;
void *ptr; ///< Data (typically referencing or interested object).
Notify *next; ///< Next notification in the linked list.
};
/// @}
enum WriteFlags {
SelectedOnly = BIT(0) ///< Passed to SimObject::write to indicate that only objects
/// marked as selected should be outputted. Used in SimSet.
};
private:
// dictionary information stored on the object
StringTableEntry objectName;
SimObject* nextNameObject;
SimObject* nextManagerNameObject;
SimObject* nextIdObject;
SimGroup* mGroup; ///< SimGroup we're contained in, if any.
BitSet32 mFlags;
/// @name Notification
/// @{
Notify* mNotifyList;
/// @}
protected:
SimObjectId mId; ///< Id number for this object.
Namespace* mNameSpace;
U32 mTypeMask;
protected:
/// @name Notification
/// Helper functions for notification code.
/// @{
static SimObject::Notify *mNotifyFreeList;
static SimObject::Notify *allocNotify(); ///< Get a free Notify structure.
static void freeNotify(SimObject::Notify*); ///< Mark a Notify structure as free.
/// @}
private:
SimFieldDictionary *mFieldDictionary; ///< Storage for dynamic fields.
public:
/// @name Accessors
/// @{
/// Get the value of a field on the object.
///
/// See @ref simobject_console "here" for a detailed discussion of what this
/// function does.
///
/// @param slotName Field to access.
/// @param array String containing index into array
/// (if field is an array); if NULL, it is ignored.
const char *getDataField(StringTableEntry slotName, const char *array);
/// Set the value of a field on the object.
///
/// See @ref simobject_console "here" for a detailed discussion of what this
/// function does.
///
/// @param slotName Field to access.
/// @param array String containing index into array; if NULL, it is ignored.
/// @param value Value to store.
void setDataField(StringTableEntry slotName, const char *array, const char *value);
/// Get reference to the dictionary containing dynamic fields.
///
/// See @ref simobject_console "here" for a detailed discussion of what this
/// function does.
///
/// This dictionary can be iterated over using a SimFieldDictionaryIterator.
SimFieldDictionary * getFieldDictionary() {return(mFieldDictionary);}
/// @}
/// @name Initialization
/// @{
///
SimObject();
virtual ~SimObject();
virtual bool processArguments(S32 argc, const char **argv); ///< Process constructor options. (ie, new SimObject(1,2,3))
/// @}
/// @name Events
/// @{
virtual bool onAdd(); ///< Called when the object is added to the sim.
virtual void onRemove(); ///< Called when the object is removed from the sim.
virtual void onGroupAdd(); ///< Called when the object is added to a SimGroup.
virtual void onGroupRemove(); ///< Called when the object is removed from a SimGroup.
virtual void onNameChange(const char *name); ///< Called when the object's name is changed.
virtual void onStaticModified(const char* slotName); ///< Called when a static field is modified.
///
/// Specifically, this is called by setDataField
/// when a static field is modified, see
/// @ref simobject_console "the console details".
/// Called before any property of the object is changed in the world editor.
///
/// The calling order here is:
/// - inspectPreApply()
/// - ...
/// - calls to setDataField()
/// - ...
/// - inspectPostApply()
virtual void inspectPreApply();
/// Called after any property of the object is changed in the world editor.
///
/// @see inspectPreApply
virtual void inspectPostApply();
/// Called when a SimObject is deleted.
///
/// When you are on the notification list for another object
/// and it is deleted, this method is called.
virtual void onDeleteNotify(SimObject *object);
/// Called when the editor is activated.
virtual void onEditorEnable(){};
/// Called when the editor is deactivated.
virtual void onEditorDisable(){};
/// @}
/// Find a named sub-object of this object.
///
/// This is subclassed in the SimGroup and SimSet classes.
///
/// For a single object, it just returns NULL, as normal objects cannot have children.
virtual SimObject *findObject(const char *name);
/// @name Notification
/// @{
Notify *removeNotify(void *ptr, Notify::Type); ///< Remove a notification from the list.
void deleteNotify(SimObject* obj); ///< Notify an object when we are deleted.
void clearNotify(SimObject* obj); ///< Notify an object when we are cleared.
void clearAllNotifications(); ///< Remove all notifications for this object.
void processDeleteNotifies(); ///< Send out deletion notifications.
/// Register a reference to this object.
///
/// You pass a pointer to your reference to this object.
///
/// When the object is deleted, it will null your
/// pointer, ensuring you don't access old memory.
///
/// @param obj Pointer to your reference to the object.
void registerReference(SimObject **obj);
/// Unregister a reference to this object.
///
/// Remove a reference from the list, so that it won't
/// get nulled inappropriately.
///
/// Call this when you're done with your reference to
/// the object, especially if you're going to free the
/// memory. Otherwise, you may erroneously get something
/// overwritten.
///
/// @see registerReference
void unregisterReference(SimObject **obj);
/// @}
/// @name Registration
///
/// SimObjects must be registered with the object system.
/// @{
/// Register an object with the object system.
///
/// This must be called if you want to keep the object around.
/// In the rare case that you will delete the object immediately, or
/// don't want to be able to use Sim::findObject to locate it, then
/// you don't need to register it.
///
/// registerObject adds the object to the global ID and name dictionaries,
/// after first assigning it a new ID number. It calls onAdd(). If onAdd fails,
/// it unregisters the object and returns false.
///
/// If a subclass's onAdd doesn't eventually call SimObject::onAdd(), it will
/// cause an assertion.
bool registerObject();
/// Register the object, forcing the id.
///
/// @see registerObject()
/// @param id ID to assign to the object.
bool registerObject(U32 id);
/// Register the object, assigning the name.
///
/// @see registerObject()
/// @param name Name to assign to the object.
bool registerObject(const char *name);
/// Register the object, assigning a name and ID.
///
/// @see registerObject()
/// @param name Name to assign to the object.
/// @param id ID to assign to the object.
bool registerObject(const char *name, U32 id);
/// Unregister the object from Sim.
///
/// This performs several operations:
/// - Sets the removed flag.
/// - Call onRemove()
/// - Clear out notifications.
/// - Remove the object from...
/// - its group, if any. (via getGroup)
/// - Sim::gNameDictionary
/// - Sim::gIDDictionary
/// - Finally, cancel any pending events for this object (as it can't receive them now).
void unregisterObject();
void deleteObject(); ///< Unregister, mark as deleted, and free the object.
///
/// This helper function can be used when you're done with the object
/// and don't want to be bothered with the details of cleaning it up.
/// @}
/// @name Accessors
/// @{
SimObjectId getId() const { return mId; }
const char* getIdString();
U32 getType() const { return mTypeMask; }
const char* getName() const { return objectName; };
void setId(SimObjectId id);
void assignName(const char* name);
SimGroup* getGroup() const { return mGroup; }
bool isProperlyAdded() const { return mFlags.test(Added); }
bool isDeleted() const { return mFlags.test(Deleted); }
bool isRemoved() const { return mFlags.test(Deleted | Removed); }
bool isLocked();
void setLocked( bool b );
bool isHidden();
void setHidden(bool b);
/// @}
/// @name Sets
///
/// The object must be properly registered before you can add/remove it to/from a set.
///
/// All these functions accept either a name or ID to identify the set you wish
/// to operate on. Then they call addObject or removeObject on the set, which
/// sets up appropriate notifications.
///
/// An object may be in multiple sets at a time.
/// @{
bool addToSet(SimObjectId);
bool addToSet(const char *);
bool removeFromSet(SimObjectId);
bool removeFromSet(const char *);
/// @}
/// @name Serialization
/// @{
/// Output the TorqueScript to recreate this object.
///
/// This calls writeFields internally.
/// @param stream Stream to output to.
/// @param tabStop Indentation level for this object.
/// @param flags If SelectedOnly is passed here, then
/// only objects marked as selected (using setSelected)
/// will output themselves.
virtual void write(Stream &stream, U32 tabStop, U32 flags = 0);
/// Write the fields of this object in TorqueScript.
///
/// @param stream Stream for output.
/// @param tabStop Indentation level for the fields.
void writeFields(Stream &stream, U32 tabStop);
/// Copy fields from another object onto this one.
///
/// Objects must be of same type. Everything from obj
/// will overwrite what's in this object; extra fields
/// in this object will remain.
///
/// @param obj Object to copy from.
void assignFieldsFrom(SimObject *obj);
/// @}
/// Return the object's namespace.
Namespace* getNamespace() { return mNameSpace; }
/// Get next matching item in namespace.
///
/// This wraps a call to Namespace::tabComplete; it gets the
/// next thing in the namespace, given a starting value
/// and a base length of the string. See
/// Namespace::tabComplete for details.
const char *tabComplete(const char *prevText, S32 baseLen, bool);
/// @name Accessors
/// @{
bool isSelected() const { return mFlags.test(Selected); }
bool isExpanded() const { return mFlags.test(Expanded); }
void setSelected(bool sel) { if(sel) mFlags.set(Selected); else mFlags.clear(Selected); }
void setExpanded(bool exp) { if(exp) mFlags.set(Expanded); else mFlags.clear(Expanded); }
void setModDynamicFields(bool dyn) { if(dyn) mFlags.set(ModDynamicFields); else mFlags.clear(ModDynamicFields); }
void setModStaticFields(bool sta) { if(sta) mFlags.set(ModStaticFields); else mFlags.clear(ModStaticFields); }
/// @}
/// @name Initialization
/// @{
/// Called to register the object's lights, if any, with the LightManager.
///
/// @param lm LightManager to put lights into.
/// @param lightingScene True if we're currently calculating lighting information.
virtual void registerLights(LightManager * lm, bool lightingScene) {};
/// @}
static void initPersistFields();
DECLARE_CONOBJECT(SimObject);
};
//---------------------------------------------------------------------------
/// Smart SimObject pointer.
///
/// This class keeps track of the book-keeping necessary
/// to keep a registered reference to a SimObject or subclass
/// thereof.
///
/// Normally, if you want the SimObject to be aware that you
/// have a reference to it, you must call SimObject::registerReference()
/// when you create the reference, and SimObject::unregisterReference() when
/// you're done. If you change the reference, you must also register/unregister
/// it. This is a big headache, so this class exists to automatically
/// keep track of things for you.
///
/// @code
/// // Assign an object to the
/// SimObjectPtr<GameBase> mOrbitObject = Sim::findObject("anObject");
///
/// // Use it as a GameBase*.
/// mOrbitObject->getWorldBox().getCenter(&mPosition);
///
/// // And reassign it - it will automatically update the references.
/// mOrbitObject = Sim::findObject("anotherObject");
/// @endcode
template <class T> class SimObjectPtr
{
private:
SimObject *mObj;
public:
SimObjectPtr() { mObj = 0; }
SimObjectPtr(T* ptr)
{
mObj = ptr;
if(mObj)
mObj->registerReference(&mObj);
}
SimObjectPtr(const SimObjectPtr<T>& rhs)
{
mObj = const_cast<T*>(static_cast<const T*>(rhs));
if(mObj)
mObj->registerReference(&mObj);
}
SimObjectPtr<T>& operator=(const SimObjectPtr<T>& rhs)
{
if(this == &rhs)
return(*this);
if(mObj)
mObj->unregisterReference(&mObj);
mObj = const_cast<T*>(static_cast<const T*>(rhs));
if(mObj)
mObj->registerReference(&mObj);
return(*this);
}
~SimObjectPtr()
{
if(mObj)
mObj->unregisterReference(&mObj);
}
SimObjectPtr<T>& operator= (T *ptr)
{
if(mObj != (SimObject *) ptr)
{
if(mObj)
mObj->unregisterReference(&mObj);
mObj = (SimObject *) ptr;
if (mObj)
mObj->registerReference(&mObj);
}
return *this;
}
#if defined(__MWERKS__) && (__MWERKS__ < 0x2400)
// CW 5.3 seems to get confused comparing SimObjectPtrs...
bool operator == (const SimObject *ptr) { return mObj == ptr; }
bool operator != (const SimObject *ptr) { return mObj != ptr; }
#endif
bool isNull() const { return mObj == 0; }
T* operator->() const { return static_cast<T*>(mObj); }
T& operator*() const { return *static_cast<T*>(mObj); }
operator T*() const { return static_cast<T*>(mObj)? static_cast<T*>(mObj) : 0; }
};
//---------------------------------------------------------------------------
/// Root DataBlock class.
///
/// @section SimDataBlock_intro Introduction
///
/// Another powerful aspect of Torque's networking is the datablock. Datablocks
/// are used to provide relatively static information about entities; for instance,
/// what model a weapon should use to display itself, or how heavy a player class is.
///
/// This gives significant gains in network efficiency, because it means that all
/// the datablocks on a server can be transferred over the network at client
/// connect time, instead of being intertwined with the update code for NetObjects.
///
/// This makes the network code much simpler overall, as one-time initialization
/// code is segregated from the standard object update code, as well as providing
/// several powerful features, which we will discuss momentarily.
///
/// @section SimDataBlock_preload preload() and File Downloading
///
/// Because datablocks are sent over the wire, using SimDataBlockEvent, before
/// gameplay starts in earnest, we gain in several areas. First, we don't have to
/// try to keep up with the game state while working with incomplete information.
/// Second, we can provide the user with a nice loading screen, instead of the more
/// traditional "Connecting..." message. Finally, and most usefully, we can request
/// missing files from the server as we become aware of them, since we are under
/// no obligation to render anything for the user.
///
/// The mechanism for this is fairly basic. After a datablock is unpacked, the
/// preload() method is called. If it returns false and sets an error, then the
/// network code checks to see if a file (or files) failed to be located by the
/// ResManager; if so, then it requests those files from the server. If preload
/// returns true, then the datablock is considered loaded. If preload returns
/// false and sets no error, then the connection is aborted.
///
/// Once the file(s) is downloaded, the datablock's preload() method is called again.
/// If it fails with the same error, the connection is aborted. If a new error is
/// returned, then the download-retry process is repeated until the preload works.
///
/// @section SimDataBlock_guide Guide To Datablock Code
///
/// To make a datablock subclass, you need to extend three functions:
/// - preload()
/// - packData()
/// - unpackData()
///
/// packData() and unpackData() simply read or write data to a network stream. If you
/// add any fields, you need to add appropriate calls to read or write. Make sure that
/// the order of reads and writes is the same in both functions. Make sure to call
/// the Parent's version of these methods in every subclass.
///
/// preload() is a bit more complex; it is responsible for taking the raw data read by
/// unpackData() and processing it into a form useful by the datablock's owning object. For
/// instance, the Player class' datablock, PlayerData, gets handles to commonly used
/// nodes in the player model, as well as resolving handles to textures and other
/// resources. <b>Any</b> code which loads files or performs other actions beyond simply
/// reading the data from the packet, such as validation, must reside in preload().
///
/// To write your own preload() methods, see any of the existing methods in the codebase; for instance,
/// PlayerData::preload() is an excellent example of error-reporting, data validation, and so forth.
///
/// @note A useful trick, which is used in several places in the engine, is that of temporarily
/// storing SimObjectIds in the variable which will eventually hold the "real" handle. ShapeImage
/// uses this trick in several pllaces; so do the vehicle classes. See GameBaseData for more on
/// using this trick.
///
/// @see GameBaseData for some more information on the datablocks used throughout
/// most of the engine.
/// @see http://hosted.tribalwar.com/t2faq/datablocks.shtml for an excellent
/// explanation of the basics of datablocks from script. Note that these comments
/// mostly apply to GameBaseData and its children.
/// @nosubgrouping
class SimDataBlock: public SimObject
{
typedef SimObject Parent;
public:
SimDataBlock();
DECLARE_CONOBJECT(SimDataBlock);
/// @name Datablock Internals
/// @{
protected:
S32 modifiedKey;
public:
static SimObjectId sNextObjectId;
static S32 sNextModifiedKey;
/// Assign a new modified key.
///
/// Datablocks are assigned a modified key which is updated every time
/// a static field of the datablock is changed. These are gotten from
/// a global store.
static S32 getNextModifiedKey() { return sNextModifiedKey; }
/// Get the modified key for this particular datablock.
S32 getModifiedKey() const { return modifiedKey; }
bool onAdd();
void onStaticModified(const char* slotName);
//void setLastError(const char*);
void assignId();
/// @}
/// @name Datablock Interface
/// @{
///
virtual void packData(BitStream* stream);
virtual void unpackData(BitStream* stream);
/// Called to prepare the datablock for use, after it has been unpacked.
///
/// @param server Set if we're running on the server (and therefore don't need to load
/// things like textures or sounds).
/// @param errorBuffer If an error occurs in loading, this is set to a short string describing
/// the error.
/// @returns True if all went well; false if something failed.
///
/// @see @ref SimDataBlock_preload
virtual bool preload(bool server, char errorBuffer[256]);
/// @}
};
//---------------------------------------------------------------------------
/// A set of SimObjects.
///
/// It is often necessary to keep track of an arbitrary set of SimObjects.
/// For instance, Torque's networking code needs to not only keep track of
/// the set of objects which need to be ghosted, but also the set of objects
/// which must <i>always</i> be ghosted. It does this by working with two
/// sets. The first of these is the RootGroup (which is actually a SimGroup)
/// and the second is the GhostAlwaysSet, which contains objects which must
/// always be ghosted to the client.
///
/// Some general notes on SimSets:
/// - Membership is not exclusive. A SimObject may be a member of multiple
/// SimSets.
/// - A SimSet does not destroy subobjects when it is destroyed.
/// - A SimSet may hold an arbitrary number of objects.
///
/// Using SimSets, the code to work with these two sets becomes
/// relatively straightforward:
///
/// @code
/// // (Example from netObject.cc)
/// // To iterate over all the objects in the Sim:
/// for (SimSetIterator obj(Sim::getRootGroup()); *obj; ++obj)
/// {
/// NetObject* nobj = dynamic_cast<NetObject*>(*obj);
///
/// if (nobj)
/// {
/// // ... do things ...
/// }
/// }
///
/// // (Example from netGhost.cc)
/// // To iterate over the ghostAlways set.
/// SimSet* ghostAlwaysSet = Sim::getGhostAlwaysSet();
/// SimSet::iterator i;
///
/// U32 sz = ghostAlwaysSet->size();
/// S32 j;
///
/// for(i = ghostAlwaysSet->begin(); i != ghostAlwaysSet->end(); i++)
/// {
/// NetObject *obj = (NetObject *)(*i);
///
/// /// ... do things with obj...
/// }
/// @endcode
///
class SimSet: public SimObject
{
typedef SimObject Parent;
protected:
SimObjectList objectList;
void *mMutex;
public:
SimSet() {
VECTOR_SET_ASSOCIATION(objectList);
mMutex = Mutex::createMutex();
}
~SimSet()
{
lock();
unlock();
Mutex::destroyMutex(mMutex);
mMutex = NULL;
}
/// @name STL Interface
/// @{
///
typedef SimObjectList::iterator iterator;
typedef SimObjectList::value_type value;
SimObject* front() { return objectList.front(); }
SimObject* first() { return objectList.first(); }
SimObject* last() { return objectList.last(); }
bool empty() { return objectList.empty(); }
S32 size() { return objectList.size(); }
iterator begin() { return objectList.begin(); }
iterator end() { return objectList.end(); }
value operator[] (S32 index) { return objectList[U32(index)]; }
iterator find( iterator first, iterator last, SimObject *obj)
{ return ::find(first, last, obj); }
bool reOrder( SimObject *obj, SimObject *target=0 );
/// @}
virtual void onRemove();
virtual void onDeleteNotify(SimObject *object);
/// @name Set Management
/// @{
virtual void addObject(SimObject*); ///< Add an object to the set.
virtual void removeObject(SimObject*); ///< Remove an object from the set.
virtual void pushObject(SimObject*); ///< Add object to end of list.
///
/// It will force the object to the end of the list if it already exists
/// in the list.
virtual void popObject(); ///< Remove an object from the end of the list.
void bringObjectToFront(SimObject* obj) { reOrder(obj, front()); }
void pushObjectToBack(SimObject* obj) { reOrder(obj, NULL); }
/// @}
void write(Stream &stream, U32 tabStop, U32 flags = 0);
virtual SimObject *findObject(const char *name);
inline void lock()
{
#ifdef TORQUE_MULTITHREAD
Mutex::lockMutex(mMutex);
#endif
}
inline void unlock()
{
#ifdef TORQUE_MULTITHREAD
Mutex::unlockMutex(mMutex);
#endif
}
DECLARE_CONOBJECT(SimSet);
};
/// Iterator for use with SimSets
///
/// @see SimSet
class SimSetIterator
{
protected:
struct Entry {
SimSet* set;
SimSet::iterator itr;
};
class Stack: public Vector<Entry> {
public:
void push_back(SimSet*);
};
Stack stack;
public:
SimSetIterator(SimSet*);
SimObject* operator++();
SimObject* operator*() {
return stack.empty()? 0: *stack.last().itr;
}
};
//---------------------------------------------------------------------------
/// A group of SimObjects.
///
/// A SimGroup is a stricter form of SimSet. SimObjects may only be a member
/// of a single SimGroup at a time.
///
/// The SimGroup will automatically enforce the single-group-membership rule.
///
/// @code
/// // From engine/sim/simPath.cc - getting a pointer to a SimGroup
/// SimGroup* pMissionGroup = dynamic_cast<SimGroup*>(Sim::findObject("MissionGroup"));
///
/// // From game/trigger.cc:46 - iterating over a SimObject's group.
/// SimObject* trigger = ...;
/// SimGroup* pGroup = trigger->getGroup();
/// for (SimGroup::iterator itr = pGroup->begin(); itr != pGroup->end(); itr++)
/// {
/// // do something with *itr
/// }
/// @endcode
class SimGroup: public SimSet
{
private:
friend class SimManager;
friend class SimObject;
typedef SimSet Parent;
SimNameDictionary nameDictionary;
public:
~SimGroup();
/// Add an object to the group.
void addObject(SimObject*);
void addObject(SimObject*, SimObjectId);
void addObject(SimObject*, const char *name);
/// Remove an object from the group.
void removeObject(SimObject*);
void onRemove();
/// Find an object in the group.
virtual SimObject* findObject(const char* name);
bool processArguments(S32 argc, const char **argv);
DECLARE_CONOBJECT(SimGroup);
};
inline void SimGroup::addObject(SimObject* obj, SimObjectId id)
{
obj->mId = id;
addObject( obj );
}
inline void SimGroup::addObject(SimObject *obj, const char *name)
{
addObject( obj );
obj->assignName(name);
}
class SimGroupIterator: public SimSetIterator
{
public:
SimGroupIterator(SimGroup* grp): SimSetIterator(grp) {}
SimObject* operator++();
};
//---------------------------------------------------------------------------
class SimDataBlockGroup : public SimGroup
{
private:
S32 mLastModifiedKey;
public:
static S32 QSORT_CALLBACK compareModifiedKey(const void* a,const void* b);
void sort();
SimDataBlockGroup();
};
//---------------------------------------------------------------------------
/// @defgroup simbase_helpermacros Helper Macros
///
/// These are used for named sets and groups in the manager.
/// @{
#define DeclareNamedSet(set) extern SimSet *g##set;inline SimSet *get##set() { return g##set; }
#define DeclareNamedGroup(set) extern SimGroup *g##set;inline SimGroup *get##set() { return g##set; }
#define ImplementNamedSet(set) SimSet *g##set;
#define ImplementNamedGroup(set) SimGroup *g##set;
/// @}
//---------------------------------------------------------------------------
namespace Sim
{
DeclareNamedSet(ActiveActionMapSet)
DeclareNamedSet(GhostAlwaysSet)
DeclareNamedSet(LightSet)
DeclareNamedSet(WayPointSet)
DeclareNamedSet(fxReplicatorSet)
DeclareNamedSet(fxFoliageSet)
DeclareNamedGroup(ActionMapGroup)
DeclareNamedGroup(ClientGroup)
DeclareNamedGroup(GuiGroup)
DeclareNamedGroup(GuiDataGroup)
DeclareNamedGroup(TCPGroup)
DeclareNamedGroup(ClientConnectionGroup)
DeclareNamedGroup(ChunkFileGroup);
void init();
void shutdown();
SimDataBlockGroup *getDataBlockGroup();
SimGroup* getRootGroup();
SimObject* findObject(SimObjectId);
SimObject* findObject(const char* name);
template<class T> inline bool findObject(SimObjectId id,T*&t)
{
t = dynamic_cast<T*>(findObject(id));
return t != NULL;
}
template<class T> inline bool findObject(const char *objectName,T*&t)
{
t = dynamic_cast<T*>(findObject(objectName));
return t != NULL;
}
void advanceToTime(SimTime time);
void advanceTime(SimTime delta);
SimTime getCurrentTime();
SimTime getTargetTime();
/// a target time of 0 on an event means current event
U32 postEvent(SimObject*, SimEvent*, U32 targetTime);
inline U32 postEvent(SimObjectId id,SimEvent*evt, U32 targetTime)
{
return postEvent(findObject(id), evt, targetTime);
}
inline U32 postEvent(const char *objectName,SimEvent*evt, U32 targetTime)
{
return postEvent(findObject(objectName), evt, targetTime);
}
inline U32 postCurrentEvent(SimObject*obj, SimEvent*evt)
{
return postEvent(obj,evt,getCurrentTime());
}
inline U32 postCurrentEvent(SimObjectId obj,SimEvent*evt)
{
return postEvent(obj,evt,getCurrentTime());
}
inline U32 postCurrentEvent(const char *obj,SimEvent*evt)
{
return postEvent(obj,evt,getCurrentTime());
}
void cancelEvent(U32 eventId);
bool isEventPending(U32 eventId);
U32 getEventTimeLeft(U32 eventId);
U32 getTimeSinceStart(U32 eventId);
U32 getScheduleDuration(U32 eventId);
}
//----------------------------------------------------------------------------
#define DECLARE_CONSOLETYPE(T) \
DefineConsoleType( Type##T##Ptr )
#define IMPLEMENT_CONSOLETYPE(T) \
DatablockConsoleType( T##Ptr, Type##T##Ptr, sizeof(T*), T )
#define IMPLEMENT_SETDATATYPE(T) \
ConsoleSetType( Type##T##Ptr ) \
{ \
volatile SimDataBlock* pConstraint = static_cast<SimDataBlock*>((T*)NULL); \
\
if (argc == 1) { \
*reinterpret_cast<T**>(dptr) = NULL; \
if (argv[0] && argv[0][0] && !Sim::findObject(argv[0],*reinterpret_cast<T**>(dptr))) \
Con::printf("Object '%s' is not a member of the '%s' data block class", argv[0], #T); \
} \
else \
Con::printf("Cannot set multiple args to a single pointer."); \
}
#define IMPLEMENT_GETDATATYPE(T) \
ConsoleGetType( Type##T##Ptr ) \
{ \
volatile SimDataBlock* pConstraint = static_cast<SimDataBlock*>((T*)NULL); \
T** obj = reinterpret_cast<T**>(dptr); \
return *obj ? (*obj)->getName() : ""; \
}
//---------------------------------------------------------------------------
#endif