diff --git a/doomsday/apps/client/include/dd_main.h b/doomsday/apps/client/include/dd_main.h index b28024abe5..c78f9a674c 100644 --- a/doomsday/apps/client/include/dd_main.h +++ b/doomsday/apps/client/include/dd_main.h @@ -50,11 +50,6 @@ void DD_FinishInitializationAfterWindowReady(); void DD_ConsoleRegister(); -/** - * Returns @c true if shutdown is in progress. - */ -bool DD_IsShuttingDown(); - /** * Print an error message and quit. */ @@ -89,10 +84,7 @@ void DD_UpdateEngineState(); // Game modules ------------------------------------------------------------------- // -/** - * Switch to/activate the specified game. - */ -bool App_ChangeGame(Game &game, bool allowReload = false); +int DD_ActivateGameWorker(void *context); /** * Returns the application's global Games (collection). diff --git a/doomsday/apps/client/src/clientapp.cpp b/doomsday/apps/client/src/clientapp.cpp index 3256729b31..db2acaa3f5 100644 --- a/doomsday/apps/client/src/clientapp.cpp +++ b/doomsday/apps/client/src/clientapp.cpp @@ -787,6 +787,8 @@ void ClientApp::unloadGame(Game const &upcomingGame) R_InitViewWindow(); R_InitSvgs(); + + Map::initDummies(); } void ClientApp::makeGameCurrent(Game &newGame) diff --git a/doomsday/apps/client/src/dd_main.cpp b/doomsday/apps/client/src/dd_main.cpp index adc17bc3fd..c87f5560e4 100644 --- a/doomsday/apps/client/src/dd_main.cpp +++ b/doomsday/apps/client/src/dd_main.cpp @@ -168,7 +168,6 @@ class WadFileType : public de::NativeFileType static dint DD_StartupWorker(void *context); static dint DD_DummyWorker(void *context); -static void DD_AutoLoad(); dint isDedicated; dint verbose; ///< For debug messages (-verbose). @@ -617,27 +616,6 @@ InFineSystem &App_InFineSystem() throw Error("App_InFineSystem", "App not yet initialized"); } -static File1 *tryLoadFile(de::Uri const &path, size_t baseOffset = 0); - -static void parseStartupFilePathsAndAddFiles(char const *pathString) -{ - static char const *ATWSEPS = ",; \t"; - - if(!pathString || !pathString[0]) return; - - size_t len = strlen(pathString); - char *buffer = (char *) M_Malloc(len + 1); - - strcpy(buffer, pathString); - char *token = strtok(buffer, ATWSEPS); - while(token) - { - tryLoadFile(de::Uri(token, RC_NULL)); - token = strtok(nullptr, ATWSEPS); - } - M_Free(buffer); -} - #undef Con_Open void Con_Open(dint yes) { @@ -709,242 +687,9 @@ D_CMD(Tutorial) #endif // __CLIENT__ -/** - * Find all game data file paths in the auto directory with the extensions - * wad, lmp, pk3, zip and deh. - * - * @param found List of paths to be populated. - * - * @return Number of paths added to @a found. - */ -static dint findAllGameDataPaths(FS1::PathList &found) -{ - static String const extensions[] = { - "wad", "lmp", "pk3", "zip", "deh" -#ifdef UNIX - "WAD", "LMP", "PK3", "ZIP", "DEH" // upper case alternatives -#endif - }; - dint const numFoundSoFar = found.count(); - for(String const &ext : extensions) - { - DENG2_ASSERT(!ext.isEmpty()); - String const searchPath = de::Uri(Path("$(App.DataPath)/$(GamePlugin.Name)/auto/*." + ext)).resolved(); - App_FileSystem().findAllPaths(searchPath, 0, found); - } - return found.count() - numFoundSoFar; -} - -/** - * Find and try to load all game data file paths in auto directory. - * - * @return Number of new files that were loaded. - */ -static dint loadFilesFromDataGameAuto() -{ - FS1::PathList found; - findAllGameDataPaths(found); - - dint numLoaded = 0; - DENG2_FOR_EACH_CONST(FS1::PathList, i, found) - { - // Ignore directories. - if(i->attrib & A_SUBDIR) continue; - - if(tryLoadFile(de::Uri(i->path, RC_NULL))) - { - numLoaded += 1; - } - } - return numLoaded; -} - -static void loadResource(ResourceManifest &manifest) -{ - DENG2_ASSERT(manifest.resourceClass() == RC_PACKAGE); - - de::Uri path(manifest.resolvedPath(false/*do not locate resource*/), RC_NULL); - if(path.isEmpty()) return; - - if(File1 *file = tryLoadFile(path)) - { - // Mark this as an original game resource. - file->setCustom(false); - - // Print the 'CRC' number of IWADs, so they can be identified. - if(Wad *wad = file->maybeAs()) - { - LOG_RES_MSG("IWAD identification: %08x") << wad->calculateCRC(); - } - } -} - -struct ddgamechange_params_t -{ - /// @c true iff caller (i.e., App_ChangeGame) initiated busy mode. - dd_bool initiatedBusyMode; -}; - -static dint DD_BeginGameChangeWorker(void *context) -{ - ddgamechange_params_t &parms = *static_cast(context); - - Map::initDummies(); - P_InitMapEntityDefs(); - - if(parms.initiatedBusyMode) - { - Con_SetProgress(200); - } - - return 0; -} - -static dint DD_LoadGameStartupResourcesWorker(void *context) -{ - ddgamechange_params_t &parms = *static_cast(context); - - // Reset file Ids so previously seen files can be processed again. - App_FileSystem().resetFileIds(); - FS_InitVirtualPathMappings(); - App_FileSystem().resetAllSchemes(); - - if(parms.initiatedBusyMode) - { - Con_SetProgress(50); - } - - if(App_GameLoaded()) - { - // Create default Auto mappings in the runtime directory. - - // Data class resources. - App_FileSystem().addPathMapping("auto/", de::Uri("$(App.DataPath)/$(GamePlugin.Name)/auto/", RC_NULL).resolved()); - - // Definition class resources. - App_FileSystem().addPathMapping("auto/", de::Uri("$(App.DefsPath)/$(GamePlugin.Name)/auto/", RC_NULL).resolved()); - } - - /** - * Open all the files, load headers, count lumps, etc, etc... - * @note Duplicate processing of the same file is automatically guarded - * against by the virtual file system layer. - */ - GameManifests const &gameManifests = App_CurrentGame().manifests(); - dint const numPackages = gameManifests.count(RC_PACKAGE); - if(numPackages) - { - LOG_RES_MSG("Loading game resources") << (verbose >= 1? ":" : "..."); - - dint packageIdx = 0; - for(GameManifests::const_iterator i = gameManifests.find(RC_PACKAGE); - i != gameManifests.end() && i.key() == RC_PACKAGE; ++i, ++packageIdx) - { - loadResource(**i); - - // Update our progress. - if(parms.initiatedBusyMode) - { - Con_SetProgress((packageIdx + 1) * (200 - 50) / numPackages - 1); - } - } - } - - if(parms.initiatedBusyMode) - { - Con_SetProgress(200); - } - - return 0; -} - -static dint addListFiles(QStringList const &list, FileType const &ftype) +int DD_ActivateGameWorker(void *context) { - dint numAdded = 0; - foreach(QString const &path, list) - { - if(&ftype != &DD_GuessFileTypeFromFileName(path)) - { - continue; - } - - if(tryLoadFile(de::Uri(path, RC_NULL))) - { - numAdded += 1; - } - } - return numAdded; -} - -static dint DD_LoadAddonResourcesWorker(void *context) -{ - ddgamechange_params_t &parms = *static_cast(context); - - /** - * Add additional game-startup files. - * @note These must take precedence over Auto but not game-resource files. - */ - if(startupFiles && startupFiles[0]) - { - parseStartupFilePathsAndAddFiles(startupFiles); - } - - if(parms.initiatedBusyMode) - { - Con_SetProgress(50); - } - - if(App_GameLoaded()) - { - /** - * Phase 3: Add real files from the Auto directory. - */ - Session::Profile &prof = Session::profile(); - - FS1::PathList found; - findAllGameDataPaths(found); - DENG2_FOR_EACH_CONST(FS1::PathList, i, found) - { - // Ignore directories. - if(i->attrib & A_SUBDIR) continue; - - /// @todo Is expansion of symbolics still necessary here? - prof.resourceFiles << NativePath(i->path).expand().withSeparators('/'); - } - - if(!prof.resourceFiles.isEmpty()) - { - // First ZIPs then WADs (they may contain WAD files). - addListFiles(prof.resourceFiles, DD_FileTypeByName("FT_ZIP")); - addListFiles(prof.resourceFiles, DD_FileTypeByName("FT_WAD")); - } - - // Final autoload round. - DD_AutoLoad(); - } - - if(parms.initiatedBusyMode) - { - Con_SetProgress(180); - } - - FS_InitPathLumpMappings(); - - // Re-initialize the resource locator as there are now new resources to be found - // on existing search paths (probably that is). - App_FileSystem().resetAllSchemes(); - - if(parms.initiatedBusyMode) - { - Con_SetProgress(200); - } - - return 0; -} - -static dint DD_ActivateGameWorker(void *context) -{ - ddgamechange_params_t &parms = *static_cast(context); + DoomsdayApp::GameChangeParameters &parms = *(DoomsdayApp::GameChangeParameters *) context; auto &plugins = DoomsdayApp::plugins(); ResourceSystem &resSys = App_ResourceSystem(); @@ -1113,126 +858,6 @@ Game &App_CurrentGame() return DoomsdayApp::currentGame(); } -/// @todo => DoomsdayApp::changeGame() -bool App_ChangeGame(Game &game, bool allowReload) -{ - // Ignore attempts to reload the current game? - if(&App_CurrentGame() == &game) - { - // We are reloading. - if(!allowReload) - { - if(App_GameLoaded()) - { - LOG_NOTE("%s (%s) is already loaded") << game.title() << game.id(); - } - return true; - } - } - - // The current game will now be unloaded. - DENG2_FOR_EACH_OBSERVER(DoomsdayApp::GameUnloadAudience, i, - DoomsdayApp::app().audienceForGameUnload()) - { - i->aboutToUnloadGame(DoomsdayApp::game()); - } - - DoomsdayApp::app().unloadGame(game); - - // Do the switch. - DoomsdayApp::app().makeGameCurrent(game); - - /* - * If we aren't shutting down then we are either loading a game or switching - * to ringzero (the current game will have already been unloaded). - */ - if(!DD_IsShuttingDown()) - { - /* - * The bulk of this we can do in busy mode unless we are already busy - * (which can happen if a fatal error occurs during game load and we must - * shutdown immediately; Sys_Shutdown will call back to load the special - * "null-game" game). - */ - dint const busyMode = BUSYF_PROGRESS_BAR | (verbose? BUSYF_CONSOLE_OUTPUT : 0); - ddgamechange_params_t p; - BusyTask gameChangeTasks[] = { - // Phase 1: Initialization. - { DD_BeginGameChangeWorker, &p, busyMode, "Loading game...", 200, 0.0f, 0.1f }, - - // Phase 2: Loading "startup" resources. - { DD_LoadGameStartupResourcesWorker, &p, busyMode, nullptr, 200, 0.1f, 0.3f }, - - // Phase 3: Loading "add-on" resources. - { DD_LoadAddonResourcesWorker, &p, busyMode, "Loading add-ons...", 200, 0.3f, 0.7f }, - - // Phase 4: Game activation. - { DD_ActivateGameWorker, &p, busyMode, "Starting game...", 200, 0.7f, 1.0f } - }; - - p.initiatedBusyMode = !BusyMode_Active(); - - if(App_GameLoaded()) - { - // Tell the plugin it is being loaded. - /// @todo Must this be done in the main thread? - auto &plugins = DoomsdayApp::plugins(); - void *loader = plugins.findEntryPoint(App_CurrentGame().pluginId(), "DP_Load"); - LOGDEV_MSG("Calling DP_Load %p") << loader; - plugins.setActivePluginId(App_CurrentGame().pluginId()); - if(loader) ((pluginfunc_t)loader)(); - plugins.setActivePluginId(0); - } - - /// @todo Kludge: Use more appropriate task names when unloading a game. - if(game.isNull()) - { - gameChangeTasks[0].name = "Unloading game..."; - gameChangeTasks[3].name = "Switching to ringzero..."; - } - // kludge end - - BusyMode_RunTasks(gameChangeTasks, sizeof(gameChangeTasks)/sizeof(gameChangeTasks[0])); - - if(App_GameLoaded()) - { - Game::printBanner(App_CurrentGame()); - } - } - - DENG_ASSERT(DoomsdayApp::plugins().activePluginId() == 0); - - // Game change is complete. - DENG2_FOR_EACH_OBSERVER(DoomsdayApp::GameChangeAudience, i, - DoomsdayApp::app().audienceForGameChange()) - { - i->currentGameChanged(DoomsdayApp::game()); - } - - return true; -} - -bool DD_IsShuttingDown() -{ - return Sys_IsShuttingDown(); -} - -/** - * Looks for new files to autoload from the auto-load data directory. - */ -static void DD_AutoLoad() -{ - /** - * Keep loading files if any are found because virtual files may now - * exist in the auto-load directory. - */ - dint numNewFiles; - while((numNewFiles = loadFilesFromDataGameAuto()) > 0) - { - LOG_RES_VERBOSE("Autoload round completed with %i new files") << numNewFiles; - } -} - /** * Attempt to determine which game is to be played. * @@ -1422,7 +1047,7 @@ static void initialize() } // Begin the game session. - App_ChangeGame(*game); + DoomsdayApp::app().changeGame(*game, DD_ActivateGameWorker); } #ifdef __SERVER__ else @@ -1658,7 +1283,7 @@ static dint DD_StartupWorker(void * /*context*/) String foundPath = App_FileSystem().findPath(de::Uri("doomsday.pk3", RC_PACKAGE), RLF_DEFAULT, App_ResourceClass(RC_PACKAGE)); foundPath = App_BasePath() / foundPath; // Ensure the path is absolute. - File1 *loadedFile = tryLoadFile(de::Uri(foundPath, RC_NULL)); + File1 *loadedFile = File1::tryLoad(de::Uri(foundPath, RC_NULL)); DENG2_ASSERT(loadedFile); DENG2_UNUSED(loadedFile); @@ -2277,7 +1902,7 @@ D_CMD(Load) BusyMode_FreezeGameForBusyMode(); - if(!App_ChangeGame(game)) + if(!DoomsdayApp::app().changeGame(game, DD_ActivateGameWorker)) { return false; } @@ -2297,7 +1922,7 @@ D_CMD(Load) RLF_MATCH_EXTENSION, App_ResourceClass(RC_PACKAGE)); foundPath = App_BasePath() / foundPath; // Ensure the path is absolute. - if(tryLoadFile(de::Uri(foundPath, RC_NULL))) + if(File1::tryLoad(de::Uri(foundPath, RC_NULL))) { didLoadResource = true; } @@ -2314,39 +1939,6 @@ D_CMD(Load) return didLoadGame || didLoadResource; } -/** - * Attempt to load the (logical) resource indicated by the @a search term. - * - * @param path Path to the resource to be loaded. Either a "real" file in - * the local file system, or a "virtual" file. - * @param baseOffset Offset from the start of the file in bytes to begin. - * - * @return @c true if the referenced resource was loaded. - */ -static File1 *tryLoadFile(de::Uri const &search, size_t baseOffset) -{ - try - { - FileHandle &hndl = App_FileSystem().openFile(search.path(), "rb", baseOffset, false /* no duplicates */); - - de::Uri foundFileUri = hndl.file().composeUri(); - LOG_VERBOSE("Loading \"%s\"...") << NativePath(foundFileUri.asText()).pretty().toUtf8().constData(); - - App_FileSystem().index(hndl.file()); - - return &hndl.file(); - } - catch(FS1::NotFoundError const&) - { - if(App_FileSystem().accessFile(search)) - { - // Must already be loaded. - LOG_RES_XVERBOSE("\"%s\" already loaded") << NativePath(search.asText()).pretty(); - } - } - return nullptr; -} - /** * Attempt to unload the (logical) resource indicated by the @a search term. * @@ -2395,7 +1987,7 @@ D_CMD(Unload) LOG_MSG("No game is currently loaded."); return true; } - return App_ChangeGame(App_Games().nullGame()); + return DoomsdayApp::app().changeGame(App_Games().nullGame(), DD_ActivateGameWorker); } AutoStr *searchPath = AutoStr_NewStd(); @@ -2420,7 +2012,8 @@ D_CMD(Unload) Game &game = App_Games()[Str_Text(searchPath)]; if(App_GameLoaded()) { - return App_ChangeGame(App_Games().nullGame()); + return DoomsdayApp::app().changeGame(App_Games().nullGame(), + DD_ActivateGameWorker); } LOG_MSG("%s is not currently loaded.") << game.id(); @@ -2475,7 +2068,8 @@ D_CMD(ReloadGame) LOG_MSG("No game is presently loaded."); return true; } - App_ChangeGame(App_CurrentGame(), true/* allow reload */); + DoomsdayApp::app().changeGame(DoomsdayApp::game(), DD_ActivateGameWorker, + DoomsdayApp::AllowReload); return true; } diff --git a/doomsday/apps/client/src/sys_system.cpp b/doomsday/apps/client/src/sys_system.cpp index 70ceb51d05..724069e6f7 100644 --- a/doomsday/apps/client/src/sys_system.cpp +++ b/doomsday/apps/client/src/sys_system.cpp @@ -60,8 +60,6 @@ int novideo; // if true, stay in text mode for debugging -static dd_bool appShutdown = false; ///< Set to true when we should exit (normally). - #ifdef DENG_CATCH_SIGNALS /** * Borrowed from Lee Killough. @@ -116,7 +114,7 @@ void Sys_Init() bool Sys_IsShuttingDown() { - return CPP_BOOL(appShutdown); + return DoomsdayApp::app().isShuttingDown(); } /** @@ -125,7 +123,7 @@ bool Sys_IsShuttingDown() void Sys_Shutdown() { // We are now shutting down. - appShutdown = true; + DoomsdayApp::app().setShuttingDown(true); // Time to unload *everything*. if(App_GameLoaded()) @@ -282,7 +280,7 @@ DENG_EXTERN_C void Sys_Quit(void) return; } - appShutdown = true; + DoomsdayApp::app().setShuttingDown(true); // It's time to stop the main loop. DENG2_APP->stopLoop(DD_GameLoopExitCode()); diff --git a/doomsday/apps/client/src/ui/widgets/mpsessionmenuwidget.cpp b/doomsday/apps/client/src/ui/widgets/mpsessionmenuwidget.cpp index 5532835b5c..9538eb305b 100644 --- a/doomsday/apps/client/src/ui/widgets/mpsessionmenuwidget.cpp +++ b/doomsday/apps/client/src/ui/widgets/mpsessionmenuwidget.cpp @@ -72,7 +72,7 @@ DENG_GUI_PIMPL(MPSessionMenuWidget) ClientApp::serverLink().disconnect(); } - App_ChangeGame(App_Games()[gameId], false /*no reload*/); + DoomsdayApp::app().changeGame(DoomsdayApp::games()[gameId], DD_ActivateGameWorker); Con_Execute(CMDS_DDAY, cmd.toLatin1(), false, false); } }; diff --git a/doomsday/apps/client/src/ui/widgets/savedsessionmenuwidget.cpp b/doomsday/apps/client/src/ui/widgets/savedsessionmenuwidget.cpp index ec5eee60d9..4c728d3249 100644 --- a/doomsday/apps/client/src/ui/widgets/savedsessionmenuwidget.cpp +++ b/doomsday/apps/client/src/ui/widgets/savedsessionmenuwidget.cpp @@ -59,7 +59,7 @@ DENG_GUI_PIMPL(SavedSessionMenuWidget) BusyMode_FreezeGameForBusyMode(); ClientWindow::main().taskBar().close(); - App_ChangeGame(App_Games()[gameId], false /*no reload*/); + DoomsdayApp::app().changeGame(DoomsdayApp::games()[gameId], DD_ActivateGameWorker); Con_Execute(CMDS_DDAY, cmd.toLatin1(), false, false); } }; diff --git a/doomsday/apps/libdoomsday/include/doomsday/busymode.h b/doomsday/apps/libdoomsday/include/doomsday/busymode.h index d51c5a6882..21cf6a3304 100644 --- a/doomsday/apps/libdoomsday/include/doomsday/busymode.h +++ b/doomsday/apps/libdoomsday/include/doomsday/busymode.h @@ -30,7 +30,7 @@ /// Busy mode worker function. typedef int (*busyworkerfunc_t) (void *parm); -/// POD structure for defining a task processable in busy mode. +/// Defines a task processable in busy mode. struct LIBDOOMSDAY_PUBLIC BusyTask { std::function worker; ///< Worker thread that does processing while in busy mode. diff --git a/doomsday/apps/libdoomsday/include/doomsday/doomsdayapp.h b/doomsday/apps/libdoomsday/include/doomsday/doomsdayapp.h index 9e184ae463..4e1a5e5159 100644 --- a/doomsday/apps/libdoomsday/include/doomsday/doomsdayapp.h +++ b/doomsday/apps/libdoomsday/include/doomsday/doomsdayapp.h @@ -26,6 +26,8 @@ #include #include + +#include #include namespace res { class Bundles; } @@ -57,6 +59,12 @@ class LIBDOOMSDAY_PUBLIC DoomsdayApp */ DENG2_DEFINE_AUDIENCE2(ConsoleRegistration, void consoleRegistration()) + struct GameChangeParameters + { + /// @c true iff caller (i.e., App_ChangeGame) initiated busy mode. + bool initiatedBusyMode; + }; + public: DoomsdayApp(Players::Constructor playerConstructor); @@ -69,8 +77,29 @@ class LIBDOOMSDAY_PUBLIC DoomsdayApp void determineGlobalPaths(); + enum Behavior + { + AllowReload = 0x1, + DefaultBehavior = 0, + }; + Q_DECLARE_FLAGS(Behaviors, Behavior) + + /** + * Switch to/activate the specified game. + * + * @param newGame Game to change to. + * @param gameActivationFunc Callback to call after the new game has + * been made current. + * @param behaviors Change behavior flags. + */ + bool changeGame(Game &newGame, std::function gameActivationFunc, + Behaviors behaviors = DefaultBehavior); + bool isUsingUserDir() const; + bool isShuttingDown() const; + void setShuttingDown(bool shuttingDown); + #ifdef WIN32 void *moduleHandle() const; #endif @@ -104,7 +133,9 @@ class LIBDOOMSDAY_PUBLIC DoomsdayApp */ static Game &game(); -//protected: // TODO: after changeGame() is moved here, make protected again + static bool isGameLoaded(); + +protected: /** * Called just before a game change is about to begin. The GameUnload * audience has already been notified. @@ -130,4 +161,6 @@ class LIBDOOMSDAY_PUBLIC DoomsdayApp */ LIBDOOMSDAY_PUBLIC bool App_GameLoaded(); +Q_DECLARE_OPERATORS_FOR_FLAGS(DoomsdayApp::Behaviors) + #endif // LIBDOOMSDAY_DOOMSDAYAPP_H diff --git a/doomsday/apps/libdoomsday/include/doomsday/filesys/file.h b/doomsday/apps/libdoomsday/include/doomsday/filesys/file.h index d028451afd..0f959cf5d9 100644 --- a/doomsday/apps/libdoomsday/include/doomsday/filesys/file.h +++ b/doomsday/apps/libdoomsday/include/doomsday/filesys/file.h @@ -217,6 +217,18 @@ class LIBDOOMSDAY_PUBLIC File1 */ virtual File1 &clearCache(bool *retCleared = 0); +public: + /** + * Attempt to load the (logical) resource indicated by the @a search term. + * + * @param path Path to the resource to be loaded. Either a "real" file in + * the local file system, or a "virtual" file. + * @param baseOffset Offset from the start of the file in bytes to begin. + * + * @return @c true if the referenced resource was loaded. + */ + static File1 *tryLoad(Uri const &search, size_t baseOffset = 0); + protected: /// File stream handle. FileHandle *handle_; diff --git a/doomsday/apps/libdoomsday/src/doomsdayapp.cpp b/doomsday/apps/libdoomsday/src/doomsdayapp.cpp index 4940dcedca..3f58992a24 100644 --- a/doomsday/apps/libdoomsday/src/doomsdayapp.cpp +++ b/doomsday/apps/libdoomsday/src/doomsdayapp.cpp @@ -28,6 +28,7 @@ #include "doomsday/filesys/datafolder.h" #include "doomsday/filesys/virtualmappings.h" #include "doomsday/paths.h" +#include "doomsday/busymode.h" #include "doomsday/world/world.h" #include "doomsday/world/entitydef.h" #include "doomsday/Session" @@ -65,6 +66,7 @@ DENG2_PIMPL_NOREF(DoomsdayApp) std::string ddRuntimePath; bool initialized = false; + bool shuttingDown = false; Plugins plugins; Games games; Game *currentGame = nullptr; @@ -433,6 +435,16 @@ bool DoomsdayApp::isUsingUserDir() const return d->usingUserDir; } +bool DoomsdayApp::isShuttingDown() const +{ + return d->shuttingDown; +} + +void DoomsdayApp::setShuttingDown(bool shuttingDown) +{ + d->shuttingDown = shuttingDown; +} + std::string const &DoomsdayApp::doomsdayBasePath() const { return d->ddBasePath; @@ -477,6 +489,11 @@ Game &DoomsdayApp::game() return *app().d->currentGame; } +bool DoomsdayApp::isGameLoaded() +{ + return App::appExists() && !DoomsdayApp::currentGame().isNull(); +} + void DoomsdayApp::unloadGame(Game const &) { auto &gx = plugins().gameExports(); @@ -560,7 +577,7 @@ void DoomsdayApp::makeGameCurrent(Game &newGame) Library_ReleaseGames(); - //if(!DD_IsShuttingDown()) + if(!isShuttingDown()) { // Re-initialize subsystems needed even when in ringzero. if(!plugins().exchangeGameEntryPoints(newGame.pluginId())) @@ -576,7 +593,109 @@ void DoomsdayApp::makeGameCurrent(Game &newGame) Session::profile().gameId = newGame.id(); } +// from game_init.cpp +extern int beginGameChangeBusyWorker(void *context); +extern int loadGameStartupResourcesBusyWorker(void *context); +extern int loadAddonResourcesBusyWorker(void *context); + +bool DoomsdayApp::changeGame(Game &newGame, + std::function gameActivationFunc, + Behaviors behaviors) +{ + // Ignore attempts to reload the current game? + if(game().id() == newGame.id()) + { + // We are reloading. + if(!behaviors.testFlag(AllowReload)) + { + if(isGameLoaded()) + { + LOG_NOTE("%s (%s) is already loaded") << newGame.title() << newGame.id(); + } + return true; + } + } + + // The current game will now be unloaded. + DENG2_FOR_AUDIENCE2(GameUnload, i) + { + i->aboutToUnloadGame(game()); + } + + unloadGame(newGame); + + // Do the switch. + makeGameCurrent(newGame); + + /* + * If we aren't shutting down then we are either loading a game or switching + * to ringzero (the current game will have already been unloaded). + */ + if(!isShuttingDown()) + { + /* + * The bulk of this we can do in busy mode unless we are already busy + * (which can happen if a fatal error occurs during game load and we must + * shutdown immediately; Sys_Shutdown will call back to load the special + * "null-game" game). + */ + dint const busyMode = BUSYF_PROGRESS_BAR; // | (verbose? BUSYF_CONSOLE_OUTPUT : 0); + GameChangeParameters p; + BusyTask gameChangeTasks[] = + { + // Phase 1: Initialization. + { beginGameChangeBusyWorker, &p, busyMode, "Loading game...", 200, 0.0f, 0.1f }, + + // Phase 2: Loading "startup" resources. + { loadGameStartupResourcesBusyWorker, &p, busyMode, nullptr, 200, 0.1f, 0.3f }, + + // Phase 3: Loading "add-on" resources. + { loadAddonResourcesBusyWorker, &p, busyMode, "Loading add-ons...", 200, 0.3f, 0.7f }, + + // Phase 4: Game activation. + { gameActivationFunc, &p, busyMode, "Starting game...", 200, 0.7f, 1.0f } + }; + + p.initiatedBusyMode = !BusyMode_Active(); + + if(isGameLoaded()) + { + // Tell the plugin it is being loaded. + /// @todo Must this be done in the main thread? + void *loader = plugins().findEntryPoint(game().pluginId(), "DP_Load"); + LOGDEV_MSG("Calling DP_Load %p") << loader; + plugins().setActivePluginId(game().pluginId()); + if(loader) ((pluginfunc_t)loader)(); + plugins().setActivePluginId(0); + } + + /// @todo Kludge: Use more appropriate task names when unloading a game. + if(newGame.isNull()) + { + gameChangeTasks[0].name = "Unloading game..."; + gameChangeTasks[3].name = "Switching to ringzero..."; + } + // kludge end + + BusyMode_RunTasks(gameChangeTasks, sizeof(gameChangeTasks)/sizeof(gameChangeTasks[0])); + + if(isGameLoaded()) + { + Game::printBanner(game()); + } + } + + DENG_ASSERT(plugins().activePluginId() == 0); + + // Game change is complete. + DENG2_FOR_AUDIENCE2(GameChange, i) + { + i->currentGameChanged(game()); + } + return true; +} + bool App_GameLoaded() { - return App::appExists() && !DoomsdayApp::currentGame().isNull(); + return DoomsdayApp::isGameLoaded(); } diff --git a/doomsday/apps/libdoomsday/src/filesys/virtualmappings.cpp b/doomsday/apps/libdoomsday/src/filesys/virtualmappings.cpp index 968f45e6ef..f0bf599a3d 100644 --- a/doomsday/apps/libdoomsday/src/filesys/virtualmappings.cpp +++ b/doomsday/apps/libdoomsday/src/filesys/virtualmappings.cpp @@ -23,6 +23,7 @@ #include "doomsday/filesys/fs_util.h" #include "doomsday/filesys/lumpindex.h" #include "doomsday/filesys/virtualmappings.h" +#include "doomsday/doomsdayapp.h" #include #include @@ -33,7 +34,7 @@ void FS_InitVirtualPathMappings() { App_FileSystem().clearPathMappings(); - //if(DD_IsShuttingDown()) return; + if(DoomsdayApp::app().isShuttingDown()) return; // Create virtual directory mappings by processing all -vdmap options. dint argC = CommandLine_Count(); @@ -142,7 +143,7 @@ void FS_InitPathLumpMappings() // Free old paths, if any. App_FileSystem().clearPathLumpMappings(); - //if(DD_IsShuttingDown()) return; + if(DoomsdayApp::app().isShuttingDown()) return; size_t bufSize = 0; uint8_t *buf = 0; diff --git a/doomsday/apps/libdoomsday/src/game_init.cpp b/doomsday/apps/libdoomsday/src/game_init.cpp new file mode 100644 index 0000000000..c077194020 --- /dev/null +++ b/doomsday/apps/libdoomsday/src/game_init.cpp @@ -0,0 +1,334 @@ +/** @file game_init.cpp Routines for initializing a game. + * + * @authors Copyright (c) 2003-2016 Jaakko Keränen + * @authors Copyright (c) 2005-2015 Daniel Swanson + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. This program is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. You should have received a copy of the GNU + * General Public License along with this program; if not, see: + * http://www.gnu.org/licenses + */ + +#include "doomsday/doomsdayapp.h" +#include "doomsday/games.h" +#include "doomsday/busymode.h" +#include "doomsday/session.h" +#include "doomsday/console/var.h" +#include "doomsday/filesys/fs_main.h" +#include "doomsday/filesys/virtualmappings.h" +#include "doomsday/filesys/wad.h" +#include "doomsday/resource/manifest.h" +#include "doomsday/world/entitydef.h" + +#include +#include + +using namespace de; + +static void updateProgress(int progress) +{ + DENG2_FOR_EACH_OBSERVER(Games::ProgressAudience, i, + DoomsdayApp::games().audienceForProgress()) + { + i->gameWorkerProgress(progress); + } +} + +int beginGameChangeBusyWorker(void *context) +{ + DoomsdayApp::GameChangeParameters &parms = *(DoomsdayApp::GameChangeParameters *) context; + + P_InitMapEntityDefs(); + if(parms.initiatedBusyMode) + { + updateProgress(200); + } + return 0; +} + +static File1 *tryLoadFile(de::Uri const &search, size_t baseOffset = 0) +{ + auto &fs1 = App_FileSystem(); + try + { + FileHandle &hndl = fs1.openFile(search.path(), "rb", baseOffset, false /* no duplicates */); + + de::Uri foundFileUri = hndl.file().composeUri(); + LOG_VERBOSE("Loading \"%s\"...") << NativePath(foundFileUri.asText()).pretty().toUtf8().constData(); + + fs1.index(hndl.file()); + + return &hndl.file(); + } + catch(FS1::NotFoundError const&) + { + if(fs1.accessFile(search)) + { + // Must already be loaded. + LOG_RES_XVERBOSE("\"%s\" already loaded") << NativePath(search.asText()).pretty(); + } + } + return nullptr; +} + +File1 *de::File1::tryLoad(Uri const &search, size_t baseOffset) +{ + return tryLoadFile(search, baseOffset); +} + +static void loadResource(ResourceManifest &manifest) +{ + DENG2_ASSERT(manifest.resourceClass() == RC_PACKAGE); + + de::Uri path(manifest.resolvedPath(false/*do not locate resource*/), RC_NULL); + if(path.isEmpty()) return; + + if(File1 *file = tryLoadFile(path)) + { + // Mark this as an original game resource. + file->setCustom(false); + + // Print the 'CRC' number of IWADs, so they can be identified. + if(Wad *wad = file->maybeAs()) + { + LOG_RES_MSG("IWAD identification: %08x") << wad->calculateCRC(); + } + } +} + +static void parseStartupFilePathsAndAddFiles(char const *pathString) +{ + static char const *ATWSEPS = ",; \t"; + + if(!pathString || !pathString[0]) return; + + size_t len = strlen(pathString); + char *buffer = (char *) M_Malloc(len + 1); + + strcpy(buffer, pathString); + char *token = strtok(buffer, ATWSEPS); + while(token) + { + tryLoadFile(de::Uri(token, RC_NULL)); + token = strtok(nullptr, ATWSEPS); + } + M_Free(buffer); +} + +static dint addListFiles(QStringList const &list, FileType const &ftype) +{ + dint numAdded = 0; + foreach(QString const &path, list) + { + if(&ftype != &DD_GuessFileTypeFromFileName(path)) + { + continue; + } + + if(tryLoadFile(de::Uri(path, RC_NULL))) + { + numAdded += 1; + } + } + return numAdded; +} + +int loadGameStartupResourcesBusyWorker(void *context) +{ + DoomsdayApp::GameChangeParameters &parms = *(DoomsdayApp::GameChangeParameters *) context; + + // Reset file Ids so previously seen files can be processed again. + App_FileSystem().resetFileIds(); + FS_InitVirtualPathMappings(); + App_FileSystem().resetAllSchemes(); + + if(parms.initiatedBusyMode) + { + updateProgress(50); + } + + if(App_GameLoaded()) + { + // Create default Auto mappings in the runtime directory. + + // Data class resources. + App_FileSystem().addPathMapping("auto/", de::Uri("$(App.DataPath)/$(GamePlugin.Name)/auto/", RC_NULL).resolved()); + + // Definition class resources. + App_FileSystem().addPathMapping("auto/", de::Uri("$(App.DefsPath)/$(GamePlugin.Name)/auto/", RC_NULL).resolved()); + } + + /** + * Open all the files, load headers, count lumps, etc, etc... + * @note Duplicate processing of the same file is automatically guarded + * against by the virtual file system layer. + */ + GameManifests const &gameManifests = DoomsdayApp::game().manifests(); + int const numPackages = gameManifests.count(RC_PACKAGE); + if(numPackages) + { + LOG_RES_MSG("Loading game resources..."); + + int packageIdx = 0; + for(GameManifests::const_iterator i = gameManifests.find(RC_PACKAGE); + i != gameManifests.end() && i.key() == RC_PACKAGE; ++i, ++packageIdx) + { + loadResource(**i); + + // Update our progress. + if(parms.initiatedBusyMode) + { + updateProgress((packageIdx + 1) * (200 - 50) / numPackages - 1); + } + } + } + + if(parms.initiatedBusyMode) + { + updateProgress(200); + } + + return 0; +} + +/** + * Find all game data file paths in the auto directory with the extensions + * wad, lmp, pk3, zip and deh. + * + * @param found List of paths to be populated. + * + * @return Number of paths added to @a found. + */ +static dint findAllGameDataPaths(FS1::PathList &found) +{ + static String const extensions[] = { + "wad", "lmp", "pk3", "zip", "deh" +#ifdef UNIX + "WAD", "LMP", "PK3", "ZIP", "DEH" // upper case alternatives +#endif + }; + dint const numFoundSoFar = found.count(); + for(String const &ext : extensions) + { + DENG2_ASSERT(!ext.isEmpty()); + String const searchPath = de::Uri(Path("$(App.DataPath)/$(GamePlugin.Name)/auto/*." + ext)).resolved(); + App_FileSystem().findAllPaths(searchPath, 0, found); + } + return found.count() - numFoundSoFar; +} + +/** + * Find and try to load all game data file paths in auto directory. + * + * @return Number of new files that were loaded. + */ +static dint loadFilesFromDataGameAuto() +{ + FS1::PathList found; + findAllGameDataPaths(found); + + dint numLoaded = 0; + DENG2_FOR_EACH_CONST(FS1::PathList, i, found) + { + // Ignore directories. + if(i->attrib & A_SUBDIR) continue; + + if(tryLoadFile(de::Uri(i->path, RC_NULL))) + { + numLoaded += 1; + } + } + return numLoaded; +} + +/** + * Looks for new files to autoload from the auto-load data directory. + */ +static void autoLoadFiles() +{ + /** + * Keep loading files if any are found because virtual files may now + * exist in the auto-load directory. + */ + dint numNewFiles; + while((numNewFiles = loadFilesFromDataGameAuto()) > 0) + { + LOG_RES_VERBOSE("Autoload round completed with %i new files") << numNewFiles; + } +} + +int loadAddonResourcesBusyWorker(void *context) +{ + DoomsdayApp::GameChangeParameters &parms = *(DoomsdayApp::GameChangeParameters *) context; + + char const *startupFiles = CVar_String(Con_FindVariable("file-startup")); + + /** + * Add additional game-startup files. + * @note These must take precedence over Auto but not game-resource files. + */ + if(startupFiles && startupFiles[0]) + { + parseStartupFilePathsAndAddFiles(startupFiles); + } + + if(parms.initiatedBusyMode) + { + updateProgress(50); + } + + if(App_GameLoaded()) + { + /** + * Phase 3: Add real files from the Auto directory. + */ + Session::Profile &prof = Session::profile(); + + FS1::PathList found; + findAllGameDataPaths(found); + DENG2_FOR_EACH_CONST(FS1::PathList, i, found) + { + // Ignore directories. + if(i->attrib & A_SUBDIR) continue; + + /// @todo Is expansion of symbolics still necessary here? + prof.resourceFiles << NativePath(i->path).expand().withSeparators('/'); + } + + if(!prof.resourceFiles.isEmpty()) + { + // First ZIPs then WADs (they may contain WAD files). + addListFiles(prof.resourceFiles, DD_FileTypeByName("FT_ZIP")); + addListFiles(prof.resourceFiles, DD_FileTypeByName("FT_WAD")); + } + + // Final autoload round. + autoLoadFiles(); + } + + if(parms.initiatedBusyMode) + { + updateProgress(180); + } + + FS_InitPathLumpMappings(); + + // Re-initialize the resource locator as there are now new resources to be found + // on existing search paths (probably that is). + App_FileSystem().resetAllSchemes(); + + if(parms.initiatedBusyMode) + { + updateProgress(200); + } + + return 0; +} diff --git a/doomsday/apps/server/src/serverapp.cpp b/doomsday/apps/server/src/serverapp.cpp index 475b93181e..9681bf6c59 100644 --- a/doomsday/apps/server/src/serverapp.cpp +++ b/doomsday/apps/server/src/serverapp.cpp @@ -37,6 +37,7 @@ #include "def_main.h" #include "con_config.h" #include "network/net_main.h" +#include "world/map.h" #if WIN32 # include "dd_winit.h" @@ -224,6 +225,8 @@ void ServerApp::initialize() void ServerApp::unloadGame(Game const &upcomingGame) { DoomsdayApp::unloadGame(upcomingGame); + + Map::initDummies(); } ServerApp &ServerApp::app()