Torque3D/Engine/source/console/simManager.cpp
2018-03-12 03:58:19 -05:00

618 lines
16 KiB
C++

//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "platform/threads/mutex.h"
#include "console/simBase.h"
#include "console/simPersistID.h"
#include "core/stringTable.h"
#include "console/console.h"
#include "core/stream/fileStream.h"
#include "core/fileObject.h"
#include "console/consoleInternal.h"
#include "console/engineAPI.h"
#include "core/idGenerator.h"
#include "core/util/safeDelete.h"
#include "platform/platformIntrinsics.h"
#include "platform/profiler.h"
#include "math/mMathFn.h"
extern ExprEvalState gEvalState;
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
// We comment out the implementation of the Con namespace when doxygenizing because
// otherwise Doxygen decides to ignore our docs in console.h
#ifndef DOXYGENIZING
namespace Sim
{
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
// event queue variables:
SimTime gCurrentTime;
SimTime gTargetTime;
void *gEventQueueMutex;
SimEvent *gEventQueue;
U32 gEventSequence;
//---------------------------------------------------------------------------
// event queue init/shutdown
static void initEventQueue()
{
gCurrentTime = 0;
gTargetTime = 0;
gEventSequence = 1;
gEventQueue = NULL;
gEventQueueMutex = Mutex::createMutex();
}
static void shutdownEventQueue()
{
// Delete all pending events
Mutex::lockMutex(gEventQueueMutex);
SimEvent *walk = gEventQueue;
while(walk)
{
SimEvent *temp = walk->nextEvent;
delete walk;
walk = temp;
}
Mutex::unlockMutex(gEventQueueMutex);
Mutex::destroyMutex(gEventQueueMutex);
}
//---------------------------------------------------------------------------
// event post
U32 postEvent(SimObject *destObject, SimEvent* event,U32 time)
{
AssertFatal(time == -1 || time >= getCurrentTime(),
"Sim::postEvent() - Event time must be greater than or equal to the current time." );
AssertFatal(destObject, "Sim::postEvent() - Destination object for event doesn't exist.");
Mutex::lockMutex(gEventQueueMutex);
if( time == -1 ) // FIXME: a smart compiler will remove this check. - see http://garagegames.com/community/resources/view/19785 for a fix
time = gCurrentTime;
event->time = time;
event->startTime = gCurrentTime;
event->destObject = destObject;
if(!destObject)
{
delete event;
Mutex::unlockMutex(gEventQueueMutex);
return InvalidEventId;
}
event->sequenceCount = gEventSequence++;
SimEvent **walk = &gEventQueue;
SimEvent *current;
while((current = *walk) != NULL && (current->time < event->time))
walk = &(current->nextEvent);
// [tom, 6/24/2005] This ensures that SimEvents are dispatched in the same order that they are posted.
// This is needed to ensure Con::threadSafeExecute() executes script code in the correct order.
while((current = *walk) != NULL && (current->time == event->time))
walk = &(current->nextEvent);
event->nextEvent = current;
*walk = event;
U32 seqCount = event->sequenceCount;
Mutex::unlockMutex(gEventQueueMutex);
return seqCount;
}
//---------------------------------------------------------------------------
// event cancellation
void cancelEvent(U32 eventSequence)
{
Mutex::lockMutex(gEventQueueMutex);
SimEvent **walk = &gEventQueue;
SimEvent *current;
while((current = *walk) != NULL)
{
if(current->sequenceCount == eventSequence)
{
*walk = current->nextEvent;
delete current;
Mutex::unlockMutex(gEventQueueMutex);
return;
}
else
walk = &(current->nextEvent);
}
Mutex::unlockMutex(gEventQueueMutex);
}
void cancelPendingEvents(SimObject *obj)
{
Mutex::lockMutex(gEventQueueMutex);
SimEvent **walk = &gEventQueue;
SimEvent *current;
while((current = *walk) != NULL)
{
if(current->destObject == obj)
{
*walk = current->nextEvent;
delete current;
}
else
walk = &(current->nextEvent);
}
Mutex::unlockMutex(gEventQueueMutex);
}
//---------------------------------------------------------------------------
// event pending test
bool isEventPending(U32 eventSequence)
{
Mutex::lockMutex(gEventQueueMutex);
for(SimEvent *walk = gEventQueue; walk; walk = walk->nextEvent)
if(walk->sequenceCount == eventSequence)
{
Mutex::unlockMutex(gEventQueueMutex);
return true;
}
Mutex::unlockMutex(gEventQueueMutex);
return false;
}
U32 getEventTimeLeft(U32 eventSequence)
{
Mutex::lockMutex(gEventQueueMutex);
for(SimEvent *walk = gEventQueue; walk; walk = walk->nextEvent)
if(walk->sequenceCount == eventSequence)
{
SimTime t = walk->time - getCurrentTime();
Mutex::unlockMutex(gEventQueueMutex);
return t;
}
Mutex::unlockMutex(gEventQueueMutex);
return 0;
}
U32 getScheduleDuration(U32 eventSequence)
{
for(SimEvent *walk = gEventQueue; walk; walk = walk->nextEvent)
if(walk->sequenceCount == eventSequence)
return (walk->time-walk->startTime);
return 0;
}
U32 getTimeSinceStart(U32 eventSequence)
{
for(SimEvent *walk = gEventQueue; walk; walk = walk->nextEvent)
if(walk->sequenceCount == eventSequence)
return (getCurrentTime()-walk->startTime);
return 0;
}
//---------------------------------------------------------------------------
// event timing
void advanceToTime(SimTime targetTime)
{
AssertFatal(targetTime >= getCurrentTime(),
"Sim::advanceToTime() - Target time is less than the current time." );
Mutex::lockMutex(gEventQueueMutex);
gTargetTime = targetTime;
while(gEventQueue && gEventQueue->time <= targetTime)
{
SimEvent *event = gEventQueue;
gEventQueue = gEventQueue->nextEvent;
AssertFatal(event->time >= gCurrentTime,
"Sim::advanceToTime() - Event time is less than current time.");
gCurrentTime = event->time;
SimObject *obj = event->destObject;
if(!obj->isDeleted())
event->process(obj);
delete event;
}
gCurrentTime = targetTime;
Mutex::unlockMutex(gEventQueueMutex);
}
void advanceTime(SimTime delta)
{
advanceToTime(getCurrentTime() + delta);
}
U32 getCurrentTime()
{
return dAtomicRead( gCurrentTime);
}
U32 getTargetTime()
{
return dAtomicRead( gTargetTime );
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
SimGroup *gRootGroup = NULL;
SimManagerNameDictionary *gNameDictionary;
SimIdDictionary *gIdDictionary;
U32 gNextObjectId;
static void initRoot()
{
gIdDictionary = new SimIdDictionary;
gNameDictionary = new SimManagerNameDictionary;
gRootGroup = new SimGroup();
gRootGroup->incRefCount();
gRootGroup->setId(RootGroupId);
gRootGroup->assignName("RootGroup");
gRootGroup->registerObject();
gNextObjectId = DynamicObjectIdFirst;
}
static void shutdownRoot()
{
gRootGroup->decRefCount();
if( engineAPI::gUseConsoleInterop )
gRootGroup->deleteObject();
gRootGroup = NULL;
SAFE_DELETE(gNameDictionary);
SAFE_DELETE(gIdDictionary);
}
//---------------------------------------------------------------------------
SimObject* findObject(const char* fileName, S32 declarationLine)
{
PROFILE_SCOPE(SimFindObjectByLine);
if (!fileName)
return NULL;
if (declarationLine < 0)
return NULL;
if (!gRootGroup)
return NULL;
return gRootGroup->findObjectByLineNumber(fileName, declarationLine, true);
}
SimObject* findObject(ConsoleValueRef &ref)
{
return findObject((const char*)ref);
}
SimObject* findObject(const char* name)
{
PROFILE_SCOPE(SimFindObject);
// Play nice with bad code - JDD
if( !name )
return NULL;
SimObject *obj;
char c = *name;
if (c == '%')
{
if (gEvalState.getStackDepth())
{
Dictionary::Entry* ent = gEvalState.getCurrentFrame().lookup(StringTable->insert(name));
if (ent)
return Sim::findObject(ent->getIntValue());
}
}
if(c == '/')
return gRootGroup->findObject(name + 1 );
if(c >= '0' && c <= '9')
{
// it's an id group
const char* temp = name + 1;
for(;;)
{
c = *temp++;
if(!c)
return findObject(dAtoi(name));
else if(c == '/')
{
obj = findObject(dAtoi(name));
if(!obj)
return NULL;
return obj->findObject(temp);
}
else if (c < '0' || c > '9')
return NULL;
}
}
S32 len;
for(len = 0; name[len] != 0 && name[len] != '/'; len++)
;
StringTableEntry stName = StringTable->lookupn(name, len);
if(!stName)
return NULL;
obj = gNameDictionary->find(stName);
if(!name[len])
return obj;
if(!obj)
return NULL;
return obj->findObject(name + len + 1);
}
SimObject* findObject(SimObjectId id)
{
return gIdDictionary->find(id);
}
SimObject *spawnObject(String spawnClass, String spawnDataBlock, String spawnName,
String spawnProperties, String spawnScript)
{
if (spawnClass.isEmpty())
{
Con::errorf("Unable to spawn an object without a spawnClass");
return NULL;
}
String spawnString;
spawnString += "$SpawnObject = new " + spawnClass + "(" + spawnName + ") { ";
if (spawnDataBlock.isNotEmpty() && !spawnDataBlock.equal( "None", String::NoCase ) )
spawnString += "datablock = " + spawnDataBlock + "; ";
if (spawnProperties.isNotEmpty())
spawnString += spawnProperties + " ";
spawnString += "};";
// Evaluate our spawn string
Con::evaluate(spawnString.c_str());
// Get our spawnObject id
const char* spawnObjectId = Con::getVariable("$SpawnObject");
// Get the actual spawnObject
SimObject* spawnObject = findObject(spawnObjectId);
// If we have a spawn script go ahead and execute it last
if (spawnScript.isNotEmpty())
Con::evaluate(spawnScript.c_str(), true);
return spawnObject;
}
SimGroup *getRootGroup()
{
return gRootGroup;
}
String getUniqueName( const char *inName )
{
String outName( inName );
if ( outName.isEmpty() )
return String::EmptyString;
SimObject *dummy;
if ( !Sim::findObject( outName, dummy ) )
return outName;
S32 suffixNumb = -1;
String nameStr( String::GetTrailingNumber( outName, suffixNumb ) );
suffixNumb = mAbs( suffixNumb ) + 1;
#define MAX_TRIES 100
for ( U32 i = 0; i < MAX_TRIES; i++ )
{
outName = String::ToString( "%s%d", nameStr.c_str(), suffixNumb );
if ( !Sim::findObject( outName, dummy ) )
return outName;
suffixNumb++;
}
Con::errorf( "Sim::getUniqueName( %s ) - failed after %d attempts", inName, MAX_TRIES );
return String::EmptyString;
}
String getUniqueInternalName( const char *inName, SimSet *inSet, bool searchChildren )
{
// Since SimSet::findObjectByInternalName operates with StringTableEntry(s)
// we have to muck up the StringTable with our attempts.
// But then again, so does everywhere else.
StringTableEntry outName = StringTable->insert( inName );
if ( !outName || !outName[0] )
return String::EmptyString;
if ( !inSet->findObjectByInternalName( outName, searchChildren ) )
return String(outName);
S32 suffixNumb = -1;
String nameStr( String::GetTrailingNumber( outName, suffixNumb ) );
suffixNumb++;
static char tempStr[512];
#define MAX_TRIES 100
for ( U32 i = 0; i < MAX_TRIES; i++ )
{
dSprintf( tempStr, 512, "%s%d", nameStr.c_str(), suffixNumb );
outName = StringTable->insert( tempStr );
if ( !inSet->findObjectByInternalName( outName, searchChildren ) )
return String(outName);
suffixNumb++;
}
Con::errorf( "Sim::getUniqueInternalName( %s ) - failed after %d attempts", inName, MAX_TRIES );
return String::EmptyString;
}
bool isValidObjectName( const char* name )
{
if( !name || !name[ 0 ] )
return true; // Anonymous object.
if( !dIsalpha( name[ 0 ] ) && name[ 0 ] != '_' )
return false;
for( U32 i = 1; name[ i ]; ++ i )
if( !dIsalnum( name[ i ] ) && name[ i ] != '_' )
return false;
return true;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
#define InstantiateNamedSet(set) g##set = new SimSet; g##set->registerObject(#set); g##set->setNameChangeAllowed(false); gRootGroup->addObject(g##set); SIMSET_SET_ASSOCIATION((*g##set))
#define InstantiateNamedGroup(set) g##set = new SimGroup; g##set->registerObject(#set); g##set->setNameChangeAllowed(false); gRootGroup->addObject(g##set); SIMSET_SET_ASSOCIATION((*g##set))
static bool sgIsShuttingDown;
SimDataBlockGroup *gDataBlockGroup;
SimDataBlockGroup *getDataBlockGroup()
{
return gDataBlockGroup;
}
void init()
{
initEventQueue();
initRoot();
InstantiateNamedSet(ActiveActionMapSet);
InstantiateNamedSet(GhostAlwaysSet);
InstantiateNamedSet(WayPointSet);
InstantiateNamedSet(fxReplicatorSet);
InstantiateNamedSet(fxFoliageSet);
InstantiateNamedSet(MaterialSet);
InstantiateNamedSet(SFXSourceSet);
InstantiateNamedSet(SFXDescriptionSet);
InstantiateNamedSet(SFXTrackSet);
InstantiateNamedSet(SFXEnvironmentSet);
InstantiateNamedSet(SFXStateSet);
InstantiateNamedSet(SFXAmbienceSet);
InstantiateNamedSet(TerrainMaterialSet);
InstantiateNamedSet(DataBlockSet);
InstantiateNamedGroup(ActionMapGroup);
InstantiateNamedGroup(ClientGroup);
InstantiateNamedGroup(GuiGroup);
InstantiateNamedGroup(GuiDataGroup);
InstantiateNamedGroup(TCPGroup);
InstantiateNamedGroup(ClientConnectionGroup);
InstantiateNamedGroup(SFXParameterGroup);
InstantiateNamedSet(BehaviorSet);
InstantiateNamedSet(sgMissionLightingFilterSet);
gDataBlockGroup = new SimDataBlockGroup();
gDataBlockGroup->registerObject("DataBlockGroup");
gRootGroup->addObject(gDataBlockGroup);
SimPersistID::init();
}
void shutdown()
{
sgIsShuttingDown = true;
shutdownRoot();
shutdownEventQueue();
SimPersistID::shutdown();
}
bool isShuttingDown()
{
return sgIsShuttingDown;
}
}
#endif // DOXYGENIZING.
SimDataBlockGroup::SimDataBlockGroup()
{
mLastModifiedKey = 0;
}
S32 QSORT_CALLBACK SimDataBlockGroup::compareModifiedKey(const void* a,const void* b)
{
const SimDataBlock* dba = *((const SimDataBlock**)a);
const SimDataBlock* dbb = *((const SimDataBlock**)b);
return dba->getModifiedKey() - dbb->getModifiedKey();
}
void SimDataBlockGroup::sort()
{
if(mLastModifiedKey != SimDataBlock::getNextModifiedKey())
{
mLastModifiedKey = SimDataBlock::getNextModifiedKey();
dQsort(mObjectList.address(), mObjectList.size(),sizeof(SimObject *),compareModifiedKey);
}
}