diff --git a/Engine/source/module/moduleDefinition.cpp b/Engine/source/module/moduleDefinition.cpp index a280e48f1..4e624c1e4 100644 --- a/Engine/source/module/moduleDefinition.cpp +++ b/Engine/source/module/moduleDefinition.cpp @@ -68,7 +68,8 @@ mModuleId(StringTable->EmptyString()), mLoadCount( 0 ), mScopeSet( 0 ), mLocked( false ), - mpModuleManager( NULL ) + mpModuleManager( NULL ), + mPriority(0.0f) { // Set Vector Associations. VECTOR_SET_ASSOCIATION( mDependencies ); @@ -111,6 +112,8 @@ void ModuleDefinition::initPersistFields() /// Misc. addProtectedField( "Signature", TypeString, 0, &defaultProtectedNotSetFn, &getSignature, &defaultProtectedNotWriteFn, "A unique signature of the module definition based upon its Id, version and build. This is read-only and is available only after the module has been registered by a module manager." ); + addProtectedField( "Priority", TypeF32, 0, &setPriority, &defaultProtectedGetFn, &defaultProtectedNotWriteFn, "A numeric value indicating execution priority for certain callback commands. 0 has the highest priority and is then sorted from there ascending in value. This is read-only and is available only after the module has been registered by a module manager."); + } //----------------------------------------------------------------------------- diff --git a/Engine/source/module/moduleDefinition.h b/Engine/source/module/moduleDefinition.h index 0fb12f7b0..0025582eb 100644 --- a/Engine/source/module/moduleDefinition.h +++ b/Engine/source/module/moduleDefinition.h @@ -115,6 +115,7 @@ private: SimObjectId mScopeSet; bool mLocked; ModuleManager* mpModuleManager; + F32 mPriority; private: inline bool checkUnlocked( void ) const { if ( mLocked ) { Con::warnf("Ignoring changes for locked module definition."); } return !mLocked; } @@ -195,6 +196,9 @@ public: inline bool getModuleLocked( void ) const { return mLocked; } inline ModuleManager* getModuleManager( void ) const { return mpModuleManager; } + inline void setPriority(const F32 pPriority) { if (checkUnlocked()) { mPriority = pPriority; } } + inline F32 getPriority(void) const { return mPriority; } + using Parent::save; bool save( void ); @@ -332,6 +336,8 @@ protected: } static bool writeDependencies( void* obj, StringTableEntry pFieldName ) { return static_cast(obj)->getDependencies().size() > 0; } static const char* getSignature(void* obj, const char* data) { return static_cast(obj)->getSignature(); } + + static bool setPriority(void* obj, const char* index, const char* data) { static_cast(obj)->setPriority((F32)dAtof(data)); return false; } }; #endif // _MODULE_DEFINITION_H diff --git a/Engine/source/module/moduleManager_ScriptBinding.h b/Engine/source/module/moduleManager_ScriptBinding.h index b0f67548b..71a7186a8 100644 --- a/Engine/source/module/moduleManager_ScriptBinding.h +++ b/Engine/source/module/moduleManager_ScriptBinding.h @@ -150,8 +150,14 @@ DefineEngineMethod(ModuleManager, findModuleByFilePath, String, (const char* fil } //----------------------------------------------------------------------------- +static S32 QSORT_CALLBACK _findModulesSortByPriority(ModuleDefinition* const* a, ModuleDefinition* const* b) +{ + F32 diff = (*a)->getPriority() - (*b)->getPriority(); + return diff > 0 ? 1 : diff < 0 ? -1 : 0; +} -DefineEngineMethod(ModuleManager, findModules, String, (bool loadedOnly), (true), + +DefineEngineMethod(ModuleManager, findModules, String, (bool loadedOnly, bool sortByPriority, const char* moduleGroup), (true, false, ""), "Find all the modules registered with the specified loaded state.\n" "@param loadedOnly Whether to return only modules that are loaded or not.\n" "@return A list of space - separated module definition object Ids.\n") @@ -174,12 +180,23 @@ DefineEngineMethod(ModuleManager, findModules, String, (bool loadedOnly), (true) char* pReturnBuffer = Con::getReturnBuffer( bufferSize ); char* pBufferWrite = pReturnBuffer; + if (sortByPriority) + moduleDefinitions.sort(_findModulesSortByPriority); + + StringTableEntry moduleGroupStr = StringTable->insert(moduleGroup); + // Iterate module definitions. for ( ModuleManager::typeConstModuleDefinitionVector::const_iterator moduleDefinitionItr = moduleDefinitions.begin(); moduleDefinitionItr != moduleDefinitions.end(); ++moduleDefinitionItr ) { // Fetch module definition. const ModuleDefinition* pModuleDefinition = *moduleDefinitionItr; + if(moduleGroupStr != StringTable->EmptyString()) + { + if (pModuleDefinition->getModuleGroup() != moduleGroupStr) + continue; + } + // Format module definition. const U32 offset = dSprintf( pBufferWrite, bufferSize, "%d ", pModuleDefinition->getId() ); pBufferWrite += offset; diff --git a/Templates/BaseGame/game/core/clientServer/Core_ClientServer.tscript b/Templates/BaseGame/game/core/clientServer/Core_ClientServer.tscript index 1bc3b9d6f..fa0e3a12f 100644 --- a/Templates/BaseGame/game/core/clientServer/Core_ClientServer.tscript +++ b/Templates/BaseGame/game/core/clientServer/Core_ClientServer.tscript @@ -27,10 +27,10 @@ function Core_ClientServer::finishMapLoad(%this) Core_ClientServer.GetEventManager().postEvent( "mapLoadComplete" ); } -function Core_ClientServer::FailMapLoad(%this, %moduleName, %isFine) +function Core_ClientServer::FailMapLoad(%this, %moduleName, %canContinueOnFail) { Core_ClientServer.failedModuleName = %moduleName; - Core_ClientServer.GetEventManager().postEvent( "mapLoadFail", %isFine ); + Core_ClientServer.GetEventManager().postEvent( "mapLoadFail", %canContinueOnFail ); } function Core_ClientServerListener::onMapLoadComplete(%this) @@ -50,9 +50,9 @@ function Core_ClientServerListener::onMapLoadComplete(%this) } } -function Core_ClientServerListener::onmapLoadFail(%this, %isFine) +function Core_ClientServerListener::onMapLoadFail(%this, %canContinueOnFail) { - if (%isFine) + if (%canContinueOnFail) { %this.onMapLoadComplete(); return; diff --git a/Templates/BaseGame/game/core/clientServer/scripts/server/connectionToClient.tscript b/Templates/BaseGame/game/core/clientServer/scripts/server/connectionToClient.tscript index 10d47f8c1..8c68494f8 100644 --- a/Templates/BaseGame/game/core/clientServer/scripts/server/connectionToClient.tscript +++ b/Templates/BaseGame/game/core/clientServer/scripts/server/connectionToClient.tscript @@ -81,24 +81,64 @@ function GameConnection::onConnect( %this, %clientData ) %this.connectData = %clientData; + //Signal and listener logic for the spawn config/processing here + %this.GetEventManager().registerEvent("setSpawnObjectTypeComplete"); + %this.GetEventManager().registerEvent("setSpawnObjectTypeFailed"); + %this.GetEventManager().registerEvent("setSpawnPointComplete"); + %this.GetEventManager().registerEvent("setSpawnPointFailed"); + %this.GetEventManager().registerEvent("postSpawnComplete"); + + %this.listener = new ScriptMsgListener() { + class = GameConnectionListener; + }; + %this.GetEventManager().subscribe( %this.listener, "setSpawnObjectTypeComplete" ); + %this.GetEventManager().subscribe( %this.listener, "setSpawnObjectTypeFailed" ); + %this.GetEventManager().subscribe( %this.listener, "setSpawnPointComplete" ); + %this.GetEventManager().subscribe( %this.listener, "setSpawnPointFailed" ); + %this.GetEventManager().subscribe( %this.listener, "postSpawnComplete" ); + callGamemodeFunction("onClientConnect", %this); $Server::PlayerCount++; } +function GameConnection::GetEventManager(%this) +{ + if( !isObject( %this.eventManager ) ) + %this.eventManager = new EventManager() { + queue = "GameConnectionEventManager"; + }; + + return %this.eventManager; +} + function GameConnection::spawnControlObject( %this ) { //baseline controlObject spawn type with extention points %this.spawnClass = "Camera"; %this.spawnDBType = "CameraData"; %this.spawnDataBlock = "Observer"; - callOnModules("setSpawnObjectType", "Game", %this); - callGamemodeFunction("setSpawnObjectType", %this); + + %this.numModsNeedingLoaded = 0; + %this.moduleLoadedDone = 0; + %modulesIDList = getModulesAndGameModesList(true, "Game"); + + %this.numModsNeedingLoaded = getNumCanCallOnObjectList("setSpawnObjectType", %modulesIDList); + + if (%this.numModsNeedingLoaded) + callOnObjectList("setSpawnObjectType", %modulesIdList, %this); + else + %this.GetEventManager().onSetSpawnObjectTypeComplete(); //just jump to progress } -function GameConnection::onSetSpawnObjectTypeComplete( %this ) +function GameConnectionListener::onSetSpawnObjectTypeComplete( %this, %client ) { - if (isObject(%this.player)) + %client.moduleLoadedDone++; + + if (%client.moduleLoadedDone < %client.numModsNeedingLoaded) + return; //continue to wait + + if (isObject(%client.player)) { // The client should not already have a player. Assigning // a new one could result in an uncontrolled player object. @@ -106,36 +146,41 @@ function GameConnection::onSetSpawnObjectTypeComplete( %this ) } // Spawn with the engine's Sim::spawnObject() function - %this.player = spawnObject(%this.spawnClass, %this.spawnDataBlock); + %client.player = spawnObject(%client.spawnClass, %client.spawnDataBlock); - if (!%this.player.isMemberOfClass(%this.spawnClass)) - warn("Trying to spawn a class that does not derive from "@ %this.spawnClass); + if (!%client.player.isMemberOfClass(%client.spawnClass)) + warn("Trying to spawn a class that does not derive from "@ %client.spawnClass); // Add the player object to MissionCleanup so that it // won't get saved into the level files and will get // cleaned up properly - MissionCleanup.add(%this.player); + MissionCleanup.add(%client.player); // Store the client object on the player object for // future reference - %this.player.client = %this; + %client.player.client = %client; - %this.setSpawnPoint(); + %client.setSpawnPoint(); // Give the client control of the camera if in the editor if( $startWorldEditor ) { - %control = %this.camera; + %control = %client.camera; %control.mode = "Fly"; EditorGui.syncCameraGui(); } else - %control = %this.player; + %control = %client.player; // Allow the player/camera to receive move data from the GameConnection. Without this // the user is unable to control the player/camera. if (!isDefined("%noControl")) - %this.setControlObject(%control); + %client.setControlObject(%control); +} + +function GameConnectionListener::onSetSpawnObjectTypeFailed( %this, %client, %canContinueOnFail ) +{ + errorf("Failed to properly set Spawn Object Type for client: " @ %client); } function GameConnection::setSpawnPoint( %this ) @@ -144,14 +189,27 @@ function GameConnection::setSpawnPoint( %this ) %this.playerSpawnGroups = "PlayerSpawnPoints PlayerDropPoints"; %this.spawnPoint = ""; %this.spawnLocation = "0 0 0"; - callOnModules("setSpawnPoint", "Game", %this); - callGamemodeFunction("setSpawnPoint", %this); + + %this.numModsNeedingLoaded = 0; + %this.moduleLoadedDone = 0; + %modulesIDList = getModulesAndGameModesList(true, "Game"); + + %this.numModsNeedingLoaded = getNumCanCallOnObjectList("setSpawnPoint", %modulesIDList); + + if (%this.numModsNeedingLoaded) + callOnObjectList("setSpawnPoint", %modulesIdList, %this); + else + %this.GetEventManager().onSetSpawnPointComplete(); } -function GameConnection::onSetSpawnPointComplete( %this ) +function GameConnectionListener::onSetSpawnPointComplete( %this, %client ) { - if (isObject(%this.player)) - %this.player.setTransform(%this.spawnLocation); + %client.moduleLoadedDone++; + if (%client.moduleLoadedDone < %client.numModsNeedingLoaded) + return; //continue to wait + + if (isObject(%client.player)) + %client.player.setTransform(%client.spawnLocation); else { // If we weren't able to create the player object then warn the user @@ -160,26 +218,47 @@ function GameConnection::onSetSpawnPointComplete( %this ) if (isDefined("%this.spawnDataBlock")) { MessageBoxOK("Spawn Failed", - "Unable to create a player with class " @ %this.spawnClass @ - " and datablock " @ %this.spawnDataBlock @ ".\n\nStarting as an Observer instead.", + "Unable to create a player with class " @ %client.spawnClass @ + " and datablock " @ %client.spawnDataBlock @ ".\n\nStarting as an Observer instead.", ""); } else { MessageBoxOK("Spawn Failed", - "Unable to create a player with class " @ %this.spawnClass @ + "Unable to create a player with class " @ %client.spawnClass @ ".\n\nStarting as an Observer instead.", ""); } } - %this.onPostSpawn(); + %client.onPostSpawn(); +} + +function GameConnectionListener::onSetSpawnPointFailed( %this, %client, %canContinueOnFail ) +{ + errorf("Failed to properly set Spawn Object Type for client: " @ %client); } function GameConnection::onPostSpawn( %this ) { - //post controlObject create extention points - callOnModules("onPostSpawn", "Game", %this); - callGamemodeFunction("onPostSpawn", %this); + %this.numModsNeedingLoaded = 0; + %this.moduleLoadedDone = 0; + %modulesIDList = getModulesAndGameModesList(true, "Game"); + + %this.numModsNeedingLoaded = getNumCanCallOnObjectList("onPostSpawn", %modulesIDList); + + if (%this.numModsNeedingLoaded) + callOnObjectList("onPostSpawn", %modulesIdList, %this); + else + %this.GetEventManager().onPostSpawnComplete(); +} + +function GameConnectionListener::onPostSpawnComplete(%this, %client) +{ + %client.moduleLoadedDone++; + if (%client.moduleLoadedDone < %client.numModsNeedingLoaded) + return; //continue to wait + + //Continue on. Room for special handling here if needbe but not expressly required } //----------------------------------------------------------------------------- diff --git a/Templates/BaseGame/game/core/utility/scripts/helperFunctions.tscript b/Templates/BaseGame/game/core/utility/scripts/helperFunctions.tscript index 05438e600..e81d5cb76 100644 --- a/Templates/BaseGame/game/core/utility/scripts/helperFunctions.tscript +++ b/Templates/BaseGame/game/core/utility/scripts/helperFunctions.tscript @@ -698,4 +698,88 @@ function playSoundAsset(%soundAssetId,%pos) } AssetDatabase.releaseAsset(%soundAssetId); return %handle; +} + +//------------------------------------------------------------------------------ +function getModulesAndGameModesList(%usePriority, %group) +{ + %modulesList = ModuleDatabase.findModules(true, %usePriority, %group); + %modulesIDList = ""; + + for(%i=0; %i < getWordCount(%modulesList); %i++) + { + %module = getWord(%modulesList, %i); + + %modulesIDList = %modulesIDList SPC %module.ModuleId; + } + + %gamemodeList = getGameModesList(); + %gameModeCount = %gamemodeList.count(); + for(%i=0; %i < %gameModeCount; %i++) + { + %gameModeObj = %gamemodeList.getKey(%i); + %active = %gamemodeList.getValue(%i); + + if(!isObject(%gameModeObj) || !%active) + continue; + + %modulesIDList = %modulesIDList SPC %gameModeObj; + } + + %modulesIDList = strreplace(%modulesIDList, " "," "); + %modulesIDList = trim(%modulesIDList); + return %modulesIDList; +} + +function callOnObjectList(%functionName, %objectsList, %var0, %var1, %var2, %var3, %var4, %var5, %var6) +{ + //Get our modules so we can exec any specific client-side loading/handling + %echoList = "Called List:"; + for(%i=0; %i < getWordCount(%objectsList); %i++) + { + %obj = getWord(%objectsList, %i); + %objName = %obj.getName(); + + if(!isObject(%obj)) + { + //could be a moduleID we're trying to call against, so try a lookup + %module = ModuleDatabase.findModule(%obj); + if(isObject(%module)) + { + %obj = %module.scopeSet; + %objName = %module.ModuleId; + } + } + + %echoList = %echoList SPC %objName; + + // match this to i/o signature + if(isObject(%obj) && %obj.isMethod(%functionName)) + %obj.call(%functionName, %var0, %var1, %var2, %var3, %var4, %var5, %var6); + } + + if ($reportModuleOrder) + warn(%echoList); +} + +function getNumCanCallOnObjectList(%functionName, %objectsList) +{ + %numberWithFunction = 0; + for(%i=0; %i < getWordCount(%objectsList); %i++) + { + %obj = getWord(%objectsList, %i); + if(!isObject(%obj)) + { + //could be a moduleID we're trying to call against, so try a lookup + %module = ModuleDatabase.findModule(%obj); + if(isObject(%module)) + %obj = %module.scopeSet; + } + + // match this to i/o signature + if(isObject(%obj) && %obj.isMethod(%functionName)) + %numberWithFunction++; + } + + return %numberWithFunction; } \ No newline at end of file