//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _SIMSET_H_
#define _SIMSET_H_
#ifndef _SIMOBJECT_H_
#include "console/simObject.h"
#endif
#ifndef _SIMOBJECTLIST_H_
#include "console/simObjectList.h"
#endif
#ifndef _SIMDICTIONARY_H_
#include "console/simDictionary.h"
#endif
#ifndef _TSIGNAL_H_
#include "core/util/tSignal.h"
#endif
#include "persistence/taml/tamlChildren.h"
//---------------------------------------------------------------------------
/// 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 always 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(*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, public TamlChildren
{
public:
typedef SimObject Parent;
typedef SimObject Children;
enum SetModification
{
SetCleared,
SetObjectAdded,
SetObjectRemoved
};
/// Signal for letting observers know when objects are added to or removed from
/// the set.
///
/// @param modification In what way the set has been modified.
/// @param set The set that has been modified.
/// @param object If #modification is #SetObjectAdded or #SetObjectRemoved, this is
/// the object that has been added or removed. Otherwise NULL.
typedef Signal< void( SetModification modification, SimSet* set, SimObject* object ) > SetModificationSignal;
protected:
SimObjectList mObjectList;
void *mMutex;
/// Signal that is triggered when objects are added or removed from the set.
SetModificationSignal mSetModificationSignal;
/// @name Callbacks
/// @{
DECLARE_CALLBACK( void, onObjectAdded, ( SimObject* object ) );
DECLARE_CALLBACK( void, onObjectRemoved, ( SimObject* object ) );
/// @}
public:
SimSet();
~SimSet();
/// Return the signal that is triggered when an object is added to or removed
/// from the set.
const SetModificationSignal& getSetModificationSignal() const { return mSetModificationSignal; }
SetModificationSignal& getSetModificationSignal() { return mSetModificationSignal; }
/// @name STL Interface
/// @{
///
typedef SimObjectList::iterator iterator;
typedef SimObjectList::value_type value;
SimObject* front() { return mObjectList.front(); }
SimObject* first() { return mObjectList.first(); }
SimObject* last() { return mObjectList.last(); }
bool empty() const { return mObjectList.empty(); }
S32 size() const { return mObjectList.size(); }
iterator begin() { return mObjectList.begin(); }
iterator end() { return mObjectList.end(); }
value operator[] (S32 index) { return mObjectList[U32(index)]; }
inline iterator find( iterator first, iterator last, SimObject *obj)
{ return T3D::find(first, last, obj); }
inline iterator find(SimObject *obj)
{ return T3D::find(begin(), end(), obj); }
/// Reorder the position of "obj" to either be the last object in the list or, if
/// "target" is given, to come before "target" in the list of children.
virtual bool reOrder( SimObject *obj, SimObject *target=0 );
/// Return the object at the given index.
SimObject* at(S32 index) const { return mObjectList.at(index); }
/// Remove all objects from this set.
virtual void clear();
/// @}
/// @name Set Management
/// @{
/// Add the given object to the set.
/// @param object Object to add to the set.
virtual void addObject( SimObject* object );
/// Remove the given object from the set.
/// @param object Object to remove from the set.
virtual void removeObject( SimObject* object );
/// Add the given object to the end of the object list of this set.
/// @param object Object to add to the set.
virtual void pushObject( SimObject* object );
/// Return true if this set accepts the given object as a child.
/// This method should be overridden for set classes that restrict membership.
virtual bool acceptsAsChild( SimObject* object ) const { return true; }
/// Deletes all the objects in the set.
void deleteAllObjects();
/// Remove an object from the end of the list.
virtual void popObject();
void bringObjectToFront(SimObject* obj) { reOrder(obj, front()); }
void pushObjectToBack(SimObject* obj) { reOrder(obj, NULL); }
/// Performs a sort of the objects in the set using a script
/// callback function to do the comparison.
///
/// An example script sort callback:
///
/// @code
/// function sortByName( %object1, %object2 )
/// {
/// return strcmp( %object1.getName(), %object2.getName() );
/// }
/// @endcode
///
/// Note: You should never modify the SimSet itself while in
/// the sort callback function as it can cause a deadlock.
///
void scriptSort( const String &scriptCallbackFn );
/// @}
void callOnChildren( const String &method, S32 argc, ConsoleValue argv[], bool executeOnChildGroups = true );
/// Return the number of objects in this set as well as all sets that are contained
/// in this set and its children.
///
/// @note The child sets themselves count towards the total too.
U32 sizeRecursive();
virtual SimObject* findObjectByInternalName(StringTableEntry internalName, bool searchChildren = false);
SimObject* findObjectByLineNumber(const char* fileName, S32 declarationLine, bool searchChildren = false);
/// Find the given object in this set. Returns NULL if the object
/// is not part of this set.
SimObject* findObject( SimObject* object );
/// Add all child objects ( including children of children ) to the foundObjects
/// Vector which are of type T.
template< class T >
void findObjectByType( Vector &foundObjects );
/// Add all child objects ( including children of children ) to the foundObjects
/// Vector which are of type T and for which DecideAddObjectCallback return true;
template< class T >
void findObjectByCallback( bool ( *fn )( T* ), Vector& foundObjects );
SimObject* getRandom();
inline void lock()
{
#ifdef TORQUE_MULTITHREAD
Mutex::lockMutex(mMutex);
#endif
}
void unlock()
{
#ifdef TORQUE_MULTITHREAD
Mutex::unlockMutex(mMutex);
#endif
}
#ifdef TORQUE_DEBUG_GUARD
inline void _setVectorAssoc( const char *file, const U32 line )
{
mObjectList.setFileAssociation( file, line );
}
#endif
// SimObject.
DECLARE_CONOBJECT( SimSet );
virtual void onRemove();
virtual void onDeleteNotify(SimObject *object);
virtual SimObject* findObject( const char* name );
virtual void write(Stream &stream, U32 tabStop, U32 flags = 0);
virtual bool writeObject(Stream *stream);
virtual bool readObject(Stream *stream);
virtual SimSet* clone();
// TamlChildren
virtual U32 getTamlChildCount(void) const
{
return (U32)size();
}
virtual SimObject* getTamlChild(const U32 childIndex) const
{
// Sanity!
AssertFatal(childIndex < (U32)size(), "SimSet::getTamlChild() - Child index is out of range.");
// For when the assert is not used.
if (childIndex >= (U32)size())
return NULL;
return at(childIndex);
}
virtual void addTamlChild(SimObject* pSimObject)
{
// Sanity!
AssertFatal(pSimObject != NULL, "SimSet::addTamlChild() - Cannot add a NULL child object.");
addObject(pSimObject);
}
};
#ifdef TORQUE_DEBUG_GUARD
# define SIMSET_SET_ASSOCIATION( x ) x._setVectorAssoc( __FILE__, __LINE__ )
#else
# define SIMSET_SET_ASSOCIATION( x )
#endif
template< class T >
void SimSet::findObjectByType( Vector &foundObjects )
{
T *curObj;
SimSet *curSet;
lock();
// Loop through our child objects.
SimObjectList::iterator itr = mObjectList.begin();
for ( ; itr != mObjectList.end(); itr++ )
{
curObj = dynamic_cast( *itr );
curSet = dynamic_cast( *itr );
// If child object is a set, call recursively into it.
if ( curSet && curSet->size() != 0)
curSet->findObjectByType( foundObjects );
// Add this child object if appropriate.
if ( curObj )
foundObjects.push_back( curObj );
}
// Add this object if appropriate.
curObj = dynamic_cast(this);
if ( curObj )
foundObjects.push_back( curObj );
unlock();
}
template< class T >
void SimSet::findObjectByCallback( bool ( *fn )( T* ), Vector &foundObjects )
{
T *curObj;
SimSet *curSet;
lock();
// Loop through our child objects.
SimObjectList::iterator itr = mObjectList.begin();
for ( ; itr != mObjectList.end(); itr++ )
{
curObj = dynamic_cast( *itr );
curSet = dynamic_cast( *itr );
// If child object is a set, call recursively into it.
if ( curSet )
curSet->findObjectByCallback( fn, foundObjects );
// Add this child object if appropriate.
if ( curObj && ( fn == NULL || fn( curObj ) ) )
foundObjects.push_back( curObj );
}
// Add this object if appropriate.
curObj = dynamic_cast(this);
if ( curObj && ( fn == NULL || fn( curObj ) ) )
foundObjects.push_back( curObj );
unlock();
}
/// An iterator that recursively and exhaustively traverses the contents
/// of a SimSet.
///
/// @see SimSet
class SimSetIterator
{
protected:
struct Entry {
SimSet* set;
SimSet::iterator itr;
};
class Stack: public Vector {
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(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
{
public:
typedef SimSet Parent;
friend class SimManager;
friend class SimObject;
private:
SimNameDictionary mNameDictionary;
void _addObject( SimObject* object, bool forcePushBack = false );
void _removeObjectNoLock( SimObject* );
public:
~SimGroup();
void addObject( SimObject* object, SimObjectId id);
void addObject( SimObject* object, const char* name );
// SimSet.
virtual void addObject( SimObject* object );
virtual void removeObject( SimObject* object );
virtual void pushObject( SimObject* object );
virtual void popObject();
virtual void clear();
virtual SimGroup* clone();
virtual SimGroup* deepClone();
virtual SimObject* findObject(const char* name);
virtual void onRemove();
virtual bool processArguments( S32 argc, ConsoleValue *argv );
virtual SimObject* getObject(const S32& index);
DECLARE_CONOBJECT( SimGroup );
};
inline void SimGroup::addObject(SimObject* obj, SimObjectId id)
{
obj->mId = id;
dSprintf( obj->mIdString, sizeof( obj->mIdString ), "%u", obj->mId );
addObject( obj );
}
inline void SimGroup::addObject(SimObject *obj, const char *name)
{
addObject( obj );
obj->assignName(name);
}
/// An iterator that recursively and exhaustively traverses all objects
/// in an SimGroup object tree.
class SimGroupIterator: public SimSetIterator
{
public:
SimGroupIterator(SimGroup* grp): SimSetIterator(grp) {}
SimObject* operator++();
};
#endif // _SIMSET_H_