//----------------------------------------------------------------------------- // 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(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(const ConsoleValue &val) { if (val.getType() == ConsoleValueType::cvInteger) return findObject((SimObjectId)val.getFastInt()); return findObject(val.getString()); } SimObject* findObject(ConsoleValue* val) { if (val->getType() == ConsoleValueType::cvInteger) return findObject((SimObjectId)val->getFastInt()); return findObject(val->getString()); } 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); InstantiateNamedSet(ForestBrushSet); InstantiateNamedSet(ForestItemDataSet); 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); } }