diff --git a/src/Game.cpp b/src/Game.cpp index 03f6f49..c2688f0 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -44,15 +44,11 @@ namespace ZL std::cout << "Resources.zip loaded successfully--0" << std::endl; if (s_instance) { s_instance->resourcesDownloadProgress = 1.0f; - /*s_instance->mainThreadHandler.EnqueueMainThreadTask([&]() { - s_instance->setupPart2(); - });*/ - std::cout << "s_instance setup part 2" << std::endl; + // Builds the loadSteps queue (cheap, no GL work); the actual loading + // happens one step per frame in Game::update() so the browser tab + // keeps repainting/responding instead of freezing for the whole load. s_instance->setupPart2(); - } - std::cout << "s_instance end setup" << std::endl; - } void Game::onResourcesZipError(unsigned /*handle*/, void* /*userData*/, int /*httpStatus*/) { @@ -107,12 +103,11 @@ namespace ZL #endif loadingTexture = renderer.textureManager.LoadFromPng("resources/loading.png", ""); -#ifdef EMSCRIPTEN loadingProgressBarFrameTexture = renderer.textureManager.LoadFromPng( "resources/loadingProgressBarFrame.png", "", true); loadingProgressBarTexture = renderer.textureManager.LoadFromPng( "resources/loadingProgressBar.png", "", true); -#endif + float minDimension; float width = Environment::projectionWidth; float height = Environment::projectionHeight; @@ -137,196 +132,200 @@ namespace ZL emscripten_async_wget2("resources.zip", "resources.zip", "GET", nullptr, nullptr, onResourcesZipLoaded, onResourcesZipError, onResourcesZipProgress); #else - mainThreadHandler.EnqueueMainThreadTask([this]() { - std::cout << "Load resurces step 2" << std::endl; - this->setupPart2(); - std::cout << "Load resurces step 3" << std::endl; - }); + // Builds the loadSteps queue (cheap, no GL work); the actual loading + // happens one step per frame in Game::update() so the window keeps + // pumping events / repainting instead of freezing for the whole load. + setupPart2(); #endif } void Game::setupPart2() { - std::cout << "Load resurces step 12x1" << std::endl; + // Builds an ordered queue of small closures instead of doing the work here. + // Game::update() drains one closure per frame, so the event loop / browser + // paint gets a turn between steps instead of the window freezing for the + // whole duration of loading. Building the queue itself is cheap (no GL calls). - // Must happen before createLocations() below: some location scripts + // Must happen before the location step below: some location scripts // (e.g. resources/start_dorm.lua) start a dialogue as soon as they're // loaded, so the language needs to be known before that first load. - ZL::loadLanguageSetting(); - + loadSteps.push([this]() { + ZL::loadLanguageSetting(); + }); + loadSteps.push([this]() { #ifdef EMSCRIPTEN - renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/env_sky.vertex", "resources/shaders/env_sky_web.fragment", CONST_ZIP_FILE); - std::cout << "Load resurces step 12x2" << std::endl; - - renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "resources/shaders/defaultAtmosphere.vertex", "resources/shaders/defaultAtmosphere_web.fragment", CONST_ZIP_FILE); - std::cout << "Load resurces step 12x3" << std::endl; - renderer.shaderManager.AddShaderFromFiles("planetBake", "resources/shaders/planet_bake.vertex", "resources/shaders/planet_bake_web.fragment", CONST_ZIP_FILE); - std::cout << "Load resurces step 12x4" << std::endl; - renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/planet_stone.vertex", "resources/shaders/planet_stone_web.fragment", CONST_ZIP_FILE); - std::cout << "Load resurces step 12x5" << std::endl; - renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/planet_land.vertex", "resources/shaders/planet_land_web.fragment", CONST_ZIP_FILE); - std::cout << "Load resurces step 12x6" << std::endl; - renderer.shaderManager.AddShaderFromFiles("spark", "resources/shaders/spark.vertex", "resources/shaders/spark_web.fragment", CONST_ZIP_FILE); - std::cout << "Load resurces step 12x7" << std::endl; - renderer.shaderManager.AddShaderFromFiles("skinning", "resources/shaders/skinning.vertex", "resources/shaders/default_web.fragment", CONST_ZIP_FILE); - std::cout << "Load resurces step 12x8" << std::endl; - renderer.shaderManager.AddShaderFromFiles("fog", "resources/shaders/fog.vertex", "resources/shaders/fog_web.fragment", CONST_ZIP_FILE); - std::cout << "Load resurces step 12x9" << std::endl; - renderer.shaderManager.AddShaderFromFiles("fog_skinning", "resources/shaders/fog_skinning.vertex", "resources/shaders/fog_web.fragment", CONST_ZIP_FILE); - std::cout << "Load resurces step 12x10" << std::endl; - renderer.shaderManager.AddShaderFromFiles("darklands_fog", "resources/shaders/darklands_fog.vertex", "resources/shaders/darklands_fog_web.fragment", CONST_ZIP_FILE); - std::cout << "Load resurces step 12x11" << std::endl; - renderer.shaderManager.AddShaderFromFiles("darklands_fog_skinning", "resources/shaders/darklands_fog_skinning.vertex", "resources/shaders/darklands_fog_web.fragment", CONST_ZIP_FILE); - std::cout << "Load resurces step 12x12" << std::endl; - renderer.shaderManager.AddShaderFromFiles("darklands_flash", "resources/shaders/default.vertex", "resources/shaders/darklands_flash_web.fragment", CONST_ZIP_FILE); - std::cout << "Load resurces step 12x13" << std::endl; - renderer.shaderManager.AddShaderFromFiles("cutsceneFade", "resources/shaders/default.vertex", "resources/shaders/cutscene_fade_web.fragment", CONST_ZIP_FILE); - std::cout << "Load resurces step 12x14" << std::endl; - renderer.shaderManager.AddShaderFromFiles("cutsceneBlack", "resources/shaders/default.vertex", "resources/shaders/cutscene_black_web.fragment", CONST_ZIP_FILE); - std::cout << "Load resurces step 12x15" << std::endl; - renderer.shaderManager.AddShaderFromFiles("shadow_depth", "resources/shaders/shadow_depth.vertex", "resources/shaders/shadow_depth_web.fragment", CONST_ZIP_FILE); - std::cout << "Load resurces step 12x16" << std::endl; - renderer.shaderManager.AddShaderFromFiles("shadow_depth_skinning", "resources/shaders/shadow_depth_skinning.vertex", "resources/shaders/shadow_depth_web.fragment", CONST_ZIP_FILE); - std::cout << "Load resurces step 12x17" << std::endl; - renderer.shaderManager.AddShaderFromFiles("default_shadow", "resources/shaders/default_shadow.vertex", "resources/shaders/default_shadow_web.fragment", CONST_ZIP_FILE); - std::cout << "Load resurces step 12x18" << std::endl; - renderer.shaderManager.AddShaderFromFiles("skinning_shadow", "resources/shaders/skinning_shadow.vertex", "resources/shaders/default_shadow_web.fragment", CONST_ZIP_FILE); - std::cout << "Load resurces step 12x19" << std::endl; - renderer.shaderManager.AddShaderFromFiles("fog_shadow", "resources/shaders/fog_shadow.vertex", "resources/shaders/fog_shadow_web.fragment", CONST_ZIP_FILE); - std::cout << "Load resurces step 12x20" << std::endl; - renderer.shaderManager.AddShaderFromFiles("fog_skinning_shadow", "resources/shaders/fog_skinning_shadow.vertex", "resources/shaders/fog_shadow_web.fragment", CONST_ZIP_FILE); - std::cout << "Load resurces step 12x21" << std::endl; - renderer.shaderManager.AddShaderFromFiles("night_fog", "resources/shaders/night_fog.vertex", "resources/shaders/night_fog_web.fragment", CONST_ZIP_FILE); - std::cout << "Load resurces step 12x22" << std::endl; - renderer.shaderManager.AddShaderFromFiles("night_fog_skinning", "resources/shaders/night_fog_skinning.vertex", "resources/shaders/night_fog_web.fragment", CONST_ZIP_FILE); - std::cout << "Load resurces step 12x23" << std::endl; - renderer.shaderManager.AddShaderFromFiles("night_fog_shadow", "resources/shaders/night_fog_shadow.vertex", "resources/shaders/night_fog_shadow_web.fragment", CONST_ZIP_FILE); - std::cout << "Load resurces step 12x24" << std::endl; - renderer.shaderManager.AddShaderFromFiles("night_fog_skinning_shadow", "resources/shaders/night_fog_skinning_shadow.vertex", "resources/shaders/night_fog_shadow_web.fragment", CONST_ZIP_FILE); - std::cout << "Load resurces step 12x25" << std::endl; - + renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/env_sky.vertex", "resources/shaders/env_sky_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "resources/shaders/defaultAtmosphere.vertex", "resources/shaders/defaultAtmosphere_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("planetBake", "resources/shaders/planet_bake.vertex", "resources/shaders/planet_bake_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/planet_stone.vertex", "resources/shaders/planet_stone_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/planet_land.vertex", "resources/shaders/planet_land_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("spark", "resources/shaders/spark.vertex", "resources/shaders/spark_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("skinning", "resources/shaders/skinning.vertex", "resources/shaders/default_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("fog", "resources/shaders/fog.vertex", "resources/shaders/fog_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("fog_skinning", "resources/shaders/fog_skinning.vertex", "resources/shaders/fog_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("darklands_fog", "resources/shaders/darklands_fog.vertex", "resources/shaders/darklands_fog_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("darklands_fog_skinning", "resources/shaders/darklands_fog_skinning.vertex", "resources/shaders/darklands_fog_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("darklands_flash", "resources/shaders/default.vertex", "resources/shaders/darklands_flash_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("cutsceneFade", "resources/shaders/default.vertex", "resources/shaders/cutscene_fade_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("cutsceneBlack", "resources/shaders/default.vertex", "resources/shaders/cutscene_black_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("shadow_depth", "resources/shaders/shadow_depth.vertex", "resources/shaders/shadow_depth_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("shadow_depth_skinning", "resources/shaders/shadow_depth_skinning.vertex", "resources/shaders/shadow_depth_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("default_shadow", "resources/shaders/default_shadow.vertex", "resources/shaders/default_shadow_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("skinning_shadow", "resources/shaders/skinning_shadow.vertex", "resources/shaders/default_shadow_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("fog_shadow", "resources/shaders/fog_shadow.vertex", "resources/shaders/fog_shadow_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("fog_skinning_shadow", "resources/shaders/fog_skinning_shadow.vertex", "resources/shaders/fog_shadow_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("night_fog", "resources/shaders/night_fog.vertex", "resources/shaders/night_fog_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("night_fog_skinning", "resources/shaders/night_fog_skinning.vertex", "resources/shaders/night_fog_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("night_fog_shadow", "resources/shaders/night_fog_shadow.vertex", "resources/shaders/night_fog_shadow_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("night_fog_skinning_shadow", "resources/shaders/night_fog_skinning_shadow.vertex", "resources/shaders/night_fog_shadow_web.fragment", CONST_ZIP_FILE); #else - renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/env_sky.vertex", "resources/shaders/env_sky_desktop.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "resources/shaders/defaultAtmosphere.vertex", "resources/shaders/defaultAtmosphere_desktop.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("planetBake", "resources/shaders/planet_bake.vertex", "resources/shaders/planet_bake_desktop.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/planet_stone.vertex", "resources/shaders/planet_stone_desktop.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/planet_land.vertex", "resources/shaders/planet_land_desktop.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("spark", "resources/shaders/spark.vertex", "resources/shaders/spark_desktop.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("skinning", "resources/shaders/skinning.vertex", "resources/shaders/default_desktop.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("fog", "resources/shaders/fog.vertex", "resources/shaders/fog_desktop.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("fog_skinning", "resources/shaders/fog_skinning.vertex", "resources/shaders/fog_desktop.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("darklands_fog", "resources/shaders/darklands_fog.vertex", "resources/shaders/darklands_fog_desktop.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("darklands_fog_skinning", "resources/shaders/darklands_fog_skinning.vertex", "resources/shaders/darklands_fog_desktop.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("darklands_flash", "resources/shaders/default.vertex", "resources/shaders/darklands_flash_desktop.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("cutsceneFade", "resources/shaders/default.vertex", "resources/shaders/cutscene_fade_desktop.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("cutsceneBlack", "resources/shaders/default.vertex", "resources/shaders/cutscene_black_desktop.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("shadow_depth", "resources/shaders/shadow_depth.vertex", "resources/shaders/shadow_depth_desktop.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("shadow_depth_skinning", "resources/shaders/shadow_depth_skinning.vertex", "resources/shaders/shadow_depth_desktop.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("default_shadow", "resources/shaders/default_shadow.vertex", "resources/shaders/default_shadow_desktop.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("skinning_shadow", "resources/shaders/skinning_shadow.vertex", "resources/shaders/default_shadow_desktop.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("fog_shadow", "resources/shaders/fog_shadow.vertex", "resources/shaders/fog_shadow_desktop.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("fog_skinning_shadow", "resources/shaders/fog_skinning_shadow.vertex", "resources/shaders/fog_shadow_desktop.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("night_fog", "resources/shaders/night_fog.vertex", "resources/shaders/night_fog_desktop.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("night_fog_skinning", "resources/shaders/night_fog_skinning.vertex", "resources/shaders/night_fog_desktop.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("night_fog_shadow", "resources/shaders/night_fog_shadow.vertex", "resources/shaders/night_fog_shadow_desktop.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("night_fog_skinning_shadow", "resources/shaders/night_fog_skinning_shadow.vertex", "resources/shaders/night_fog_shadow_desktop.fragment", CONST_ZIP_FILE); - + renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/env_sky.vertex", "resources/shaders/env_sky_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "resources/shaders/defaultAtmosphere.vertex", "resources/shaders/defaultAtmosphere_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("planetBake", "resources/shaders/planet_bake.vertex", "resources/shaders/planet_bake_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/planet_stone.vertex", "resources/shaders/planet_stone_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/planet_land.vertex", "resources/shaders/planet_land_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("spark", "resources/shaders/spark.vertex", "resources/shaders/spark_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("skinning", "resources/shaders/skinning.vertex", "resources/shaders/default_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("fog", "resources/shaders/fog.vertex", "resources/shaders/fog_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("fog_skinning", "resources/shaders/fog_skinning.vertex", "resources/shaders/fog_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("darklands_fog", "resources/shaders/darklands_fog.vertex", "resources/shaders/darklands_fog_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("darklands_fog_skinning", "resources/shaders/darklands_fog_skinning.vertex", "resources/shaders/darklands_fog_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("darklands_flash", "resources/shaders/default.vertex", "resources/shaders/darklands_flash_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("cutsceneFade", "resources/shaders/default.vertex", "resources/shaders/cutscene_fade_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("cutsceneBlack", "resources/shaders/default.vertex", "resources/shaders/cutscene_black_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("shadow_depth", "resources/shaders/shadow_depth.vertex", "resources/shaders/shadow_depth_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("shadow_depth_skinning", "resources/shaders/shadow_depth_skinning.vertex", "resources/shaders/shadow_depth_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("default_shadow", "resources/shaders/default_shadow.vertex", "resources/shaders/default_shadow_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("skinning_shadow", "resources/shaders/skinning_shadow.vertex", "resources/shaders/default_shadow_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("fog_shadow", "resources/shaders/fog_shadow.vertex", "resources/shaders/fog_shadow_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("fog_skinning_shadow", "resources/shaders/fog_skinning_shadow.vertex", "resources/shaders/fog_shadow_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("night_fog", "resources/shaders/night_fog.vertex", "resources/shaders/night_fog_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("night_fog_skinning", "resources/shaders/night_fog_skinning.vertex", "resources/shaders/night_fog_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("night_fog_shadow", "resources/shaders/night_fog_shadow.vertex", "resources/shaders/night_fog_shadow_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("night_fog_skinning_shadow", "resources/shaders/night_fog_skinning_shadow.vertex", "resources/shaders/night_fog_shadow_desktop.fragment", CONST_ZIP_FILE); #endif + }); - renderer.textureManager.LoadFromPng("resources/dialogue/portrait_aiperi.png", CONST_ZIP_FILE); - renderer.textureManager.LoadFromPng("resources/dialogue/portrait_ghost.png", CONST_ZIP_FILE); - renderer.textureManager.LoadFromPng("resources/dialogue/portrait_hero_neutral.png", CONST_ZIP_FILE); - renderer.textureManager.LoadFromPng("resources/dialogue/portrait_hero_think.png", CONST_ZIP_FILE); - renderer.textureManager.LoadFromPng("resources/dialogue/portrait_phone.png", CONST_ZIP_FILE); - renderer.textureManager.LoadFromPng("resources/dialogue/portrait_student_boy.png", CONST_ZIP_FILE); - renderer.textureManager.LoadFromPng("resources/dialogue/portrait_student_girl.png", CONST_ZIP_FILE); - renderer.textureManager.LoadFromPng("resources/dialogue/portrait_teacher.png", CONST_ZIP_FILE); + loadSteps.push([this]() { + renderer.textureManager.LoadFromPng("resources/dialogue/portrait_aiperi.png", CONST_ZIP_FILE); + renderer.textureManager.LoadFromPng("resources/dialogue/portrait_ghost.png", CONST_ZIP_FILE); + renderer.textureManager.LoadFromPng("resources/dialogue/portrait_hero_neutral.png", CONST_ZIP_FILE); + renderer.textureManager.LoadFromPng("resources/dialogue/portrait_hero_think.png", CONST_ZIP_FILE); + renderer.textureManager.LoadFromPng("resources/dialogue/portrait_phone.png", CONST_ZIP_FILE); + renderer.textureManager.LoadFromPng("resources/dialogue/portrait_student_boy.png", CONST_ZIP_FILE); + renderer.textureManager.LoadFromPng("resources/dialogue/portrait_student_girl.png", CONST_ZIP_FILE); + renderer.textureManager.LoadFromPng("resources/dialogue/portrait_teacher.png", CONST_ZIP_FILE); + }); - std::cout << "Load resurces step 4" << std::endl; + loadSteps.push([this]() { + ItemRegistry::instance().loadFromJson("resources/config/items.json", CONST_ZIP_FILE); + Character::nameTranslations.loadFromFile("resources/config/translate_names.json"); + }); - ItemRegistry::instance().loadFromJson("resources/config/items.json", CONST_ZIP_FILE); + // createLocations() is also used by the full-reset path (see performFullReset()), + // so it stays a single synchronous function rather than being split into + // per-location queue steps; it's queued here as one coarse step. + loadSteps.push([this]() { + createLocations(); + }); - Character::nameTranslations.loadFromFile("resources/config/translate_names.json"); + loadSteps.push([this]() { + // Wire inventory callbacks: tutorial tracking + toast notifications. + gameState.inventory.onItemAdded = [this](const std::string& itemId) { + menuManager.onItemPickedUp(itemId); + const Item* item = ItemRegistry::instance().findById(itemId); + if (item) menuManager.showToast(menuManager.toastTextureItemAdded, item->name); + audioPlayer->playSoundAsync("audio/831642__ienba__generic-game-pick-up.ogg"); + }; + gameState.inventory.onItemRemoved = [this](const std::string& itemId) { + const Item* item = ItemRegistry::instance().findById(itemId); + if (item) menuManager.showToast(menuManager.toastTextureItemRemoved, item->name); + audioPlayer->playSoundAsync("audio/831642__ienba__generic-game-pick-up.ogg"); + }; - createLocations(); + // Wire phone dialogue start function so MenuManager can trigger dialogues. + menuManager.startDialogueFunc = [this](const std::string& id) { + if (currentLocation()) currentLocation()->dialogueSystem.startDialogue(id); + }; - // Wire inventory callbacks: tutorial tracking + toast notifications. - gameState.inventory.onItemAdded = [this](const std::string& itemId) { - menuManager.onItemPickedUp(itemId); - const Item* item = ItemRegistry::instance().findById(itemId); - if (item) menuManager.showToast(menuManager.toastTextureItemAdded, item->name); - audioPlayer->playSoundAsync("audio/831642__ienba__generic-game-pick-up.ogg"); - }; - gameState.inventory.onItemRemoved = [this](const std::string& itemId) { - const Item* item = ItemRegistry::instance().findById(itemId); - if (item) menuManager.showToast(menuManager.toastTextureItemRemoved, item->name); - audioPlayer->playSoundAsync("audio/831642__ienba__generic-game-pick-up.ogg"); - }; + menuManager.startDarklandsTransitionFunc = [this]() { + startDarklandsTransition(); + }; - // Wire phone dialogue start function so MenuManager can trigger dialogues. - menuManager.startDialogueFunc = [this](const std::string& id) { - if (currentLocation()) currentLocation()->dialogueSystem.startDialogue(id); - }; + menuManager.startNightTransitionFunc = [this]() { + startNightTransition(); + }; - menuManager.startDarklandsTransitionFunc = [this]() { - startDarklandsTransition(); - }; + menuManager.chatOpenCallback = [this](int chatIndex) { + if (currentLocation()) + currentLocation()->scriptEngine.callChatOpenCallback(chatIndex); + }; - menuManager.startNightTransitionFunc = [this]() { - startNightTransition(); - }; + menuManager.skipCutsceneFunc = [this]() { + if (currentLocation()) currentLocation()->dialogueSystem.skipCutscene(); + }; + }); - menuManager.chatOpenCallback = [this](int chatIndex) { - if (currentLocation()) - currentLocation()->scriptEngine.callChatOpenCallback(chatIndex); - }; + loadSteps.push([this]() { + try { + menuManager.setup(gameState.inventory, CONST_ZIP_FILE); + std::cout << "UI loaded successfully" << std::endl; + } + catch (const std::exception& e) { + std::cerr << "Failed to load UI: " << e.what() << std::endl; + } - menuManager.skipCutsceneFunc = [this]() { - if (currentLocation()) currentLocation()->dialogueSystem.skipCutscene(); - }; + menuManager.startGameFunc = [this]() { + mainThreadHandler.EnqueueMainThreadTask([this]() { + performResetToInitialState(); + gameState.currentLocationName = "location_dorm"; + currentLocation()->scriptEngine.callLocationEnterCallback(); + this->audioPlayer->crossFadeMusicAsync("audio/obshaga.ogg"); + }); + }; - std::cout << "Load resurces step 13" << std::endl; + menuManager.onSaveGame = [this](int slot) { saveGame(slot); }; + menuManager.onLoadGame = [this](int slot) { loadGame(slot); }; + menuManager.getSlotInfoFunc = [this](int slot) { return readSlotInfo(slot); }; - try { - menuManager.setup(gameState.inventory, CONST_ZIP_FILE); - std::cout << "UI loaded successfully" << std::endl; - } - catch (const std::exception& e) { + // Lets MenuManager run a heavy operation (e.g. reloadLocalizedGameContent()) + // as a chunked sequence instead of one blocking call: the closures are + // drained one per frame by update(), with the loading screen shown for + // the duration, exactly like the initial resource load. + menuManager.runAsLoadingSequence = [this](std::vector> steps) { + for (auto& step : steps) loadSteps.push(std::move(step)); + loadSteps.push([this]() { loadingCompleted = true; }); + loadStepsTotal = loadSteps.size(); + loadStepsDone = 0; + showLoadingProgressBar = false; + loadingCompleted = false; + }; + }); - std::cerr << "Failed to load UI: " << e.what() << std::endl; - } + loadSteps.push([this]() { + if (audioPlayer->init()) { + audioPlayer->setMusicVolume(100); + audioPlayer->setSoundVolume(80); + std::cout << "Audio initialized successfully" << std::endl; + } + else { + std::cout << "Audio initialization failed" << std::endl; + } + }); - menuManager.startGameFunc = [this]() { - mainThreadHandler.EnqueueMainThreadTask([this]() { - performResetToInitialState(); - gameState.currentLocationName = "location_dorm"; - currentLocation()->scriptEngine.callLocationEnterCallback(); - this->audioPlayer->crossFadeMusicAsync("audio/obshaga.ogg"); - }); - }; - - menuManager.onSaveGame = [this](int slot) { saveGame(slot); }; - menuManager.onLoadGame = [this](int slot) { loadGame(slot); }; - menuManager.getSlotInfoFunc = [this](int slot) { return readSlotInfo(slot); }; - - if (audioPlayer->init()) { - audioPlayer->setMusicVolume(100); - audioPlayer->setSoundVolume(80); - std::cout << "Audio initialized successfully" << std::endl; - } - else { - std::cout << "Audio initialization failed" << std::endl; - } - - menuManager.loadSettings(); - - audioPlayer->crossFadeMusicAsync("audio/main menu final.ogg"); - - loadingCompleted = true; + loadSteps.push([this]() { + menuManager.loadSettings(); + audioPlayer->crossFadeMusicAsync("audio/main menu final.ogg"); + }); + loadSteps.push([this]() { + loadingCompleted = true; + }); + loadStepsTotal = loadSteps.size(); + loadStepsDone = 0; } void Game::rearmDormOneShotCallbacks() { @@ -860,8 +859,7 @@ namespace ZL renderer.PopProjectionMatrix(); renderer.shaderManager.PopShader(); -#ifdef EMSCRIPTEN - if (loadingProgressBarFrameTexture && loadingProgressBarTexture) { + if (showLoadingProgressBar && loadingProgressBarFrameTexture && loadingProgressBarTexture) { const float W = Environment::projectionWidth; const float H = Environment::projectionHeight; static constexpr float kBarW = 400.0f; @@ -891,7 +889,7 @@ namespace ZL } // Rebuild fill mesh when progress changes - const float p = std::min(resourcesDownloadProgress, 1.0f); + const float p = (std::min)(currentLoadingProgress(), 1.0f); if (p != loadingBarLastRenderedProgress) { loadingBarLastRenderedProgress = p; if (p > 0.001f) { @@ -932,7 +930,6 @@ namespace ZL renderer.PopProjectionMatrix(); renderer.shaderManager.PopShader(); } -#endif CheckGlError(__FILE__, __LINE__); } @@ -1282,6 +1279,14 @@ namespace ZL } } + // Drain one loading step per frame so the window/tab keeps pumping events + // and repainting between steps instead of freezing for the whole load. + if (!loadingCompleted && !loadSteps.empty()) { + loadSteps.front()(); + loadSteps.pop(); + loadStepsDone++; + } + render(); mainThreadHandler.processMainThreadTasks(); diff --git a/src/Game.h b/src/Game.h index e10f0e2..e497c82 100644 --- a/src/Game.h +++ b/src/Game.h @@ -10,6 +10,7 @@ #include "items/Item.h" #include "items/InteractiveObject.h" #include +#include #include #include #include @@ -49,6 +50,22 @@ namespace ZL { VertexRenderStruct loadingMesh; bool loadingCompleted = false; + // setupPart2() populates this queue with one closure per loading step instead of + // running the work itself; Game::update() drains one step per frame so the window + // keeps pumping events / repainting (and, on Emscripten, the browser keeps the tab + // responsive) instead of freezing for the whole duration of loading. + std::queue> loadSteps; + size_t loadStepsTotal = 0; + size_t loadStepsDone = 0; + float currentLoadingProgress() const { + return loadStepsTotal ? (float)loadStepsDone / (float)loadStepsTotal : resourcesDownloadProgress; + } + + // The progress bar only reflects the initial resource load (its steps are + // pre-counted upfront). Later loadSteps runs, like MenuManager's localized + // content reload, don't have a meaningful denominator, so the bar is hidden. + bool showLoadingProgressBar = true; + GameState gameState; EditorMode editorMode = EditorMode::None; @@ -138,8 +155,13 @@ namespace ZL { static void onResourcesZipLoaded(unsigned handle, void* userData, const char* filename); static void onResourcesZipError(unsigned handle, void* userData, int httpStatus); static void onResourcesZipProgress(unsigned handle, void* userData, int percentComplete); - +#endif + // Only meaningful during the Emscripten zip-download phase, before loadSteps + // exists (see currentLoadingProgress() above); harmless/unused on other platforms. float resourcesDownloadProgress = 0.0f; + + // Progress bar assets/meshes, loaded and drawn on every platform so setupPart2's + // step-by-step progress is visible during desktop/Android loading too. std::shared_ptr loadingProgressBarTexture; std::shared_ptr loadingProgressBarFrameTexture; VertexRenderStruct loadingProgressBarFrameMesh; @@ -147,7 +169,6 @@ namespace ZL { float loadingBarLastRenderedProgress = -1.0f; float loadingBarLastW = -1.0f; float loadingBarLastH = -1.0f; -#endif // Screen-flash transition state (shared by darklands and night transitions) float darklandsFlashAlpha = 0.0f; diff --git a/src/Location.cpp b/src/Location.cpp index b441005..4f1fbf6 100644 --- a/src/Location.cpp +++ b/src/Location.cpp @@ -88,9 +88,11 @@ namespace ZL // Load static game objects gameObjects = GameObjectLoader::loadAndCreateGameObjects(params.gameObjectsJsonPath, renderer, CONST_ZIP_FILE); + SDL_PumpEvents(); // Load interactive objects interactiveObjects = GameObjectLoader::loadAndCreateInteractiveObjects(params.interactiveObjectsJsonPath, renderer, CONST_ZIP_FILE); + SDL_PumpEvents(); //auto playerTexture = std::make_shared(CreateTextureDataFromPng("resources/w/gg/IMG_20260413_182354_992.png", CONST_ZIP_FILE)); //auto playerTexture = renderer.textureManager.LoadFromPng("resources/w/gg/UniV_Grid_2K_Base_color.png", CONST_ZIP_FILE); @@ -140,6 +142,7 @@ namespace ZL player->setupHitSparks(sparkTexture); player->audioPlayer = audioPlayer; std::cout << "Load resurces step 9" << std::endl; + SDL_PumpEvents(); // Load NPCs from JSON. Aggressive (canAttack) NPCs need a player target // and hit sparks; peaceful NPCs don't take damage from the player so @@ -155,6 +158,8 @@ namespace ZL } } + SDL_PumpEvents(); + loadTeleportZones(params.teleportsJsonPath, CONST_ZIP_FILE); loadTriggerZones(params.triggerZonesJsonPath, CONST_ZIP_FILE); loadPointLights(params.lightsJsonPath, CONST_ZIP_FILE); @@ -167,6 +172,7 @@ namespace ZL //#endif setupNavigation(params.navigationJsonPaths); + SDL_PumpEvents(); dialogueSystem.init(renderer, CONST_ZIP_FILE); dialogueSystem.loadDatabase(params.dialoguesJsonPath); @@ -175,6 +181,8 @@ namespace ZL dialogueSystem.loadCutsceneTranslations("resources/dialogue/translate_cutscenes.json"); dialogueSystem.setQuestJournal(journal); + SDL_PumpEvents(); + npcNameText = std::make_unique(); if (!npcNameText->init(renderer, "resources/fonts/DroidSans.ttf", 24, CONST_ZIP_FILE)) { std::cerr << "Failed to init NPC name TextRenderer" << std::endl; @@ -405,6 +413,9 @@ namespace ZL navigationMaps.resize(paths.size()); for (size_t i = 0; i < paths.size(); ++i) { + // Service the OS message queue between builds so the window doesn't + // get flagged as "not responding" while loading many navigation grids. + SDL_PumpEvents(); navigationMaps[i].build(paths[i], CONST_ZIP_FILE); } diff --git a/src/MenuManager.cpp b/src/MenuManager.cpp index bc8a0b2..a633e28 100644 --- a/src/MenuManager.cpp +++ b/src/MenuManager.cpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace ZL { @@ -119,6 +120,9 @@ namespace ZL { } std::shared_ptr MenuManager::loadLocalizedUi(const std::string& basePath, const std::string& zipFile) { + // Service the OS message queue between UI screens so the window doesn't + // get flagged as "not responding" while loading the ~50 UI layout files. + SDL_PumpEvents(); static const std::string marker = "resources/w/ui/"; if (basePath.rfind(marker, 0) == 0) { const std::string localizedPath = "resources/w/ui_" + languageToCode(g_currentLanguage) + "/" + basePath.substr(marker.size()); @@ -507,9 +511,6 @@ namespace ZL { } void MenuManager::reloadLocalizedGameContent() { - ItemRegistry::instance().loadFromJson("resources/config/items.json", CONST_ZIP_FILE); - gameState_.questJournal.reloadDefinitionsPreservingState("resources/config/quests.json", CONST_ZIP_FILE); - // Screens below the Language screen on the menu stack hold onto their old // (pre-reload) UiNode instances, so simply reopening Language on top of // them would leave Main Menu / Settings / the pause menu showing stale @@ -525,16 +526,37 @@ namespace ZL { for (int i = 0; i < depth; ++i) uiManager.popMenu(); } - loadUiRoots(CONST_ZIP_FILE); + // Unloading/reloading ~50 UI screens and their textures is heavy enough to + // freeze the window for a noticeable moment, so run it the same way the + // initial resource load runs: a queue of closures drained one per frame + // with the loading screen shown, instead of one big blocking call here. + std::vector> steps; - toastTextureQuestNew = loadLocalizedTexture("resources/w/ui/img/toast/quest_new001.png", CONST_ZIP_FILE); - toastTextureQuestCompleted = loadLocalizedTexture("resources/w/ui/img/toast/quest_completed001.png", CONST_ZIP_FILE); - toastTextureQuestFailed = loadLocalizedTexture("resources/w/ui/img/toast/quest_failed001.png", CONST_ZIP_FILE); - toastTextureItemAdded = loadLocalizedTexture("resources/w/ui/img/toast/item_received001.png", CONST_ZIP_FILE); - toastTextureItemRemoved = loadLocalizedTexture("resources/w/ui/img/toast/item_removed001.png", CONST_ZIP_FILE); + steps.push_back([this]() { + ItemRegistry::instance().loadFromJson("resources/config/items.json", CONST_ZIP_FILE); + gameState_.questJournal.reloadDefinitionsPreservingState("resources/config/quests.json", CONST_ZIP_FILE); + }); + steps.push_back([this]() { + renderer.textureManager.UnloadByPrefix("resources/w/ui"); + loadUiRoots(CONST_ZIP_FILE); - showMainMenu(); + toastTextureQuestNew = loadLocalizedTexture("resources/w/ui/img/toast/quest_new001.png", CONST_ZIP_FILE); + toastTextureQuestCompleted = loadLocalizedTexture("resources/w/ui/img/toast/quest_completed001.png", CONST_ZIP_FILE); + toastTextureQuestFailed = loadLocalizedTexture("resources/w/ui/img/toast/quest_failed001.png", CONST_ZIP_FILE); + toastTextureItemAdded = loadLocalizedTexture("resources/w/ui/img/toast/item_received001.png", CONST_ZIP_FILE); + toastTextureItemRemoved = loadLocalizedTexture("resources/w/ui/img/toast/item_removed001.png", CONST_ZIP_FILE); + }); + + steps.push_back([this]() { + showMainMenu(); + }); + + if (runAsLoadingSequence) { + runAsLoadingSequence(std::move(steps)); + } else { + for (auto& step : steps) step(); + } } void MenuManager::showLoadGameScreen() diff --git a/src/MenuManager.h b/src/MenuManager.h index ba80926..4e89b9c 100644 --- a/src/MenuManager.h +++ b/src/MenuManager.h @@ -79,6 +79,13 @@ namespace ZL { std::function onLoadGame; std::function getSlotInfoFunc; + // Set by Game: runs the given closures one per frame with the loading + // screen shown (same mechanism as the initial resource load), so a heavy + // operation like reloadLocalizedGameContent() doesn't block the window + // for its whole duration. Game re-enables normal rendering once all the + // closures have run. + std::function>)> runAsLoadingSequence; + // Called when a chat message bubble should be shown (text + direction) void onChatBubbleReady(const std::string& text, bool incoming); diff --git a/src/items/GameObjectLoader.cpp b/src/items/GameObjectLoader.cpp index a42dd4b..1d49d2a 100644 --- a/src/items/GameObjectLoader.cpp +++ b/src/items/GameObjectLoader.cpp @@ -7,6 +7,7 @@ #include "utils/Utils.h" #include #include "../Character.h" +#include namespace ZL { @@ -195,6 +196,9 @@ namespace ZL { std::unordered_map gameObjects; for (const auto& data : loadFromJson(jsonPath, zipPath)) { + // Service the OS message queue between loads so the window doesn't + // get flagged as "not responding" during long location loads. + SDL_PumpEvents(); std::cout << "Loading game object: " << data.name << std::endl; try { LoadedGameObject obj = buildLoadedObject(data, renderer, zipPath); @@ -225,6 +229,7 @@ namespace ZL { return interactiveObjects; for (const auto& data : loadInteractiveFromJson(jsonPath, zipPath)) { + SDL_PumpEvents(); std::cout << "Loading interactive object: " << data.base.name << std::endl; try { InteractiveObject intObj = InteractiveObject::createFromState( @@ -359,6 +364,7 @@ namespace ZL { std::vector> npcs; for (const auto& npcData : loadNpcsFromJson(jsonPath, zipPath)) { + SDL_PumpEvents(); std::cout << "Loading NPC: " << npcData.name << std::endl; try { auto npc = Character::createFromState(npcData.toCharacterState(), renderer, zipPath.c_str()); diff --git a/src/render/TextureManager.cpp b/src/render/TextureManager.cpp index f1eb77f..183b431 100644 --- a/src/render/TextureManager.cpp +++ b/src/render/TextureManager.cpp @@ -528,8 +528,20 @@ namespace ZL void TextureManager::Unload(const std::string& fileName, const std::string& zipFile) { - //For now, we should keep all textures in memory, to make game load faster after restart - //textureMap.erase(MakeKey(fileName, zipFile)); + textureMap.erase(MakeKey(fileName, zipFile)); + } + + void TextureManager::UnloadByPrefix(const std::string& prefix) + { + for (auto& it = textureMap.begin(); it != textureMap.end(); ) { + if (it->first.substr(0, prefix.size()) == prefix) { + std::cout << "Unloading texture with prefix: " << it->first << std::endl; + it = textureMap.erase(it); + } + else { + ++it; + } + } } void TextureManager::UnloadAll() diff --git a/src/render/TextureManager.h b/src/render/TextureManager.h index 3bfe4ca..5230760 100644 --- a/src/render/TextureManager.h +++ b/src/render/TextureManager.h @@ -76,6 +76,7 @@ namespace ZL #endif void Unload(const std::string& fileName, const std::string& zipFile = ""); + void UnloadByPrefix(const std::string& prefix); void UnloadAll(); private: