From 3d356459e1ae2f66bc1a69057bf3cb4b7775d78d Mon Sep 17 00:00:00 2001 From: lhog <1476261+lhog@users.noreply.github.com> Date: Wed, 4 Dec 2024 08:16:37 +0100 Subject: [PATCH] Another take on archive scanning splash screen (#1798) * Another take on https://github.com/beyond-all-reason/spring/pull/1792 Apparently (and obviously in the hindsight) the previous code worked for local skirmishes only. The unified approach will use `PreGame::Draw()` to display what's going on both with the network connection and archives check-summing. --- rts/Game/PreGame.cpp | 153 ++++++++++++++---------------------- rts/Game/PreGame.h | 8 +- rts/Menu/SelectMenu.cpp | 10 ++- rts/System/SplashScreen.cpp | 2 +- rts/System/SpringApp.cpp | 12 ++- 5 files changed, 83 insertions(+), 102 deletions(-) diff --git a/rts/Game/PreGame.cpp b/rts/Game/PreGame.cpp index 7f896649b2..02e5c61d48 100644 --- a/rts/Game/PreGame.cpp +++ b/rts/Game/PreGame.cpp @@ -1,6 +1,7 @@ /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ #include +#include #include @@ -40,7 +41,6 @@ #include "System/FileSystem/ArchiveScanner.h" #include "System/FileSystem/FileSystem.h" #include "System/FileSystem/VFSHandler.h" -#include "System/FileSystem/Misc.hpp" #include "System/LoadSave/DemoRecorder.h" #include "System/LoadSave/DemoReader.h" #include "System/LoadSave/LoadSaveHandler.h" @@ -168,37 +168,57 @@ int CPreGame::KeyPressed(int keyCode, int scanCode, bool isRepeat) return 0; } +void CPreGame::AsyncExecute(CPreGame::AsyncExecFuncType execFunc, const std::string& argument) +{ + pendingTask = std::async(std::launch::async, + [execFunc, argument/*copy the argument explicitly*/, this]() { + return std::invoke(execFunc, this, argument); + } + ); +} bool CPreGame::Draw() { +#ifndef HEADLESS RECOIL_DETAILED_TRACY_ZONE; - spring_msecs(10).sleep(true); + spring_msecs(10).sleep(true); // 100 fps + ClearScreen(); + static constexpr const float4 color = { 1.0f, 1.0f, 1.0f, 1.0f }; + font->Begin(); + font->SetTextColor(color.x, color.y, color.z, color.w); if (!clientNet->Connected()) { if (clientSetup->isHost) - font->glFormat(0.5f, 0.48f, 2.0f, FONT_CENTER | FONT_SCALE | FONT_NORM, "Waiting for server to start"); + font->glFormat(0.5f, 0.60f, 2.0f, FONT_CENTER | FONT_SCALE | FONT_NORM, "Waiting for server to start"); else - font->glFormat(0.5f, 0.48f, 2.0f, FONT_CENTER | FONT_SCALE | FONT_NORM, "Connecting to server (%ds)", (spring_gettime() - connectTimer).toSecsi()); + font->glFormat(0.5f, 0.60f, 2.0f, FONT_CENTER | FONT_SCALE | FONT_NORM, "Connecting to server (%ds)", (spring_gettime() - connectTimer).toSecsi()); } else { - font->glPrint(0.5f, 0.48f, 2.0f, FONT_CENTER | FONT_SCALE | FONT_NORM, "Waiting for server response"); + font->glPrint(0.5f, 0.60f, 2.0f, FONT_CENTER | FONT_SCALE | FONT_NORM, "Waiting for server response"); } if (clientSetup->showServerName.empty()) { - font->glFormat(0.60f, 0.40f, 1.0f, FONT_SCALE | FONT_NORM, "Connecting to: %s", clientNet->ConnectionStr().c_str()); - font->glFormat(0.60f, 0.35f, 1.0f, FONT_SCALE | FONT_NORM, "User name: %s", clientSetup->myPlayerName.c_str()); + font->glFormat(0.60f, 0.50f, 1.0f, FONT_SCALE | FONT_NORM, "Connecting to: %s", clientNet->ConnectionStr().c_str()); + font->glFormat(0.60f, 0.45f, 1.0f, FONT_SCALE | FONT_NORM, "User name: %s", clientSetup->myPlayerName.c_str()); } else { - font->glFormat(0.60f, 0.40f, 1.0f, FONT_SCALE | FONT_NORM, "Connecting to: %s", clientSetup->showServerName.c_str()); + font->glFormat(0.60f, 0.50f, 1.0f, FONT_SCALE | FONT_NORM, "Connecting to: %s", clientSetup->showServerName.c_str()); + } + + if (archiveScanner->GetNumFilesHashed() > 0) { + font->glFormat(0.60f, 0.35f, 1.0f, FONT_SCALE | FONT_NORM, "[Performing necessary checksum calculations]"); + font->glFormat(0.60f, 0.30f, 1.0f, FONT_SCALE | FONT_NORM, "Number of files checked: %u", archiveScanner->GetNumFilesHashed()); } - font->glFormat(0.5f,0.25f,0.8f,FONT_CENTER | FONT_SCALE | FONT_NORM, "Press SHIFT + ESC to quit"); + + font->glFormat(0.5f, 0.15f, 0.8f, FONT_CENTER | FONT_SCALE | FONT_NORM, "Press SHIFT + ESC to quit"); + // credits - font->glFormat(0.5f,0.06f,1.0f,FONT_CENTER | FONT_SCALE | FONT_NORM, "Spring %s", SpringVersion::GetFull().c_str()); - font->glPrint(0.5f,0.02f,0.6f,FONT_CENTER | FONT_SCALE | FONT_NORM, "This program is distributed under the GNU General Public License, see doc/LICENSE for more info"); + font->glFormat(0.5f, 0.06f, 1.0f, FONT_CENTER | FONT_SCALE | FONT_NORM, "Recoil %s", SpringVersion::GetFull().c_str()); + font->glPrint(0.5f, 0.02f, 0.6f, FONT_CENTER | FONT_SCALE | FONT_NORM, "This program is distributed under the GNU General Public License, see doc/LICENSE for more info"); font->End(); - +#endif return true; } @@ -244,79 +264,6 @@ void CPreGame::AddModArchivesToVFS(const CGameSetup* setup) modFileName = archiveScanner->ArchiveFromName(setup->modName); } -sha512::raw_digest CPreGame::GetArchiveCompleteChecksumBytesWithSplashScreen(const std::string& archiveName, const spring_time& start, const std::string& bitmapFileName) -{ - //can't use ThreadPool::Enqueue as nested calls to ThreadPool::Enqueue don't seem to work well - auto future = std::async(std::launch::async, [&archiveName]() { return archiveScanner->GetArchiveCompleteChecksumBytes(archiveName); }); -#ifndef HEADLESS - - VA_TYPE_2DT quadElems[] = { - {0.0f, 1.0f, 0.0f, 0.0f}, - {0.0f, 0.0f, 0.0f, 1.0f}, - {1.0f, 0.0f, 1.0f, 1.0f}, - {1.0f, 1.0f, 1.0f, 0.0f}, - }; - - CBitmap bmp; - if (!bmp.Load(bitmapFileName)) { - bmp.AllocDummy({ 0, 0, 0, 255 }); - quadElems[0].x = 0.5f - 0.125f * 0.5f; quadElems[0].y = 0.5f + 0.125f * 0.5f * globalRendering->aspectRatio; - quadElems[1].x = 0.5f - 0.125f * 0.5f; quadElems[1].y = 0.5f - 0.125f * 0.5f * globalRendering->aspectRatio; - quadElems[2].x = 0.5f + 0.125f * 0.5f; quadElems[2].y = 0.5f - 0.125f * 0.5f * globalRendering->aspectRatio; - quadElems[3].x = 0.5f + 0.125f * 0.5f; quadElems[3].y = 0.5f + 0.125f * 0.5f * globalRendering->aspectRatio; - } - - auto texID = bmp.CreateTexture(); - - static constexpr const unsigned int fontFlags = FONT_NORM | FONT_SCALE; - static constexpr const float4 color = { 1.0f, 1.0f, 1.0f, 1.0f }; - static constexpr const float4 coors = { 0.5f, 0.175f, 0.8f, 0.04f }; // x, y, scale, spacing - static constexpr const char* caption = "[Performing necessary checksum calculations]"; - auto texWidth = font->GetTextWidth(caption) * globalRendering->pixelX * font->GetSize() * coors.z; -#endif - - using namespace std::chrono_literals; - while (future.wait_for(10ms) != std::future_status::ready) { -#ifndef HEADLESS - glClear(GL_COLOR_BUFFER_BIT); - - auto& rb = RenderBuffer::GetTypedRenderBuffer(); - rb.AssertSubmission(); - auto& sh = rb.GetShader(); - - rb.AddQuadTriangles( - { quadElems[0].x, quadElems[0].y, quadElems[0].s, quadElems[0].t }, - { quadElems[1].x, quadElems[1].y, quadElems[1].s, quadElems[1].t }, - { quadElems[2].x, quadElems[2].y, quadElems[2].s, quadElems[2].t }, - { quadElems[3].x, quadElems[3].y, quadElems[3].s, quadElems[3].t } - ); - - glBindTexture(GL_TEXTURE_2D, texID); - sh.Enable(); - rb.DrawElements(GL_TRIANGLES); - sh.Disable(); - glBindTexture(GL_TEXTURE_2D, 0); - - font->Begin(); - font->SetTextColor(color.x, color.y, color.z, color.w); - font->glFormat(coors.x - texWidth * 0.5f, coors.y - (coors.w * coors.z * 0.0f), coors.z, fontFlags, caption); - font->glFormat(coors.x - texWidth * 0.5f, coors.y - (coors.w * coors.z * 1.0f), coors.z, fontFlags, "* Scanning archive named: %s", archiveName); - font->glFormat(coors.x - texWidth * 0.5f, coors.y - (coors.w * coors.z * 2.0f), coors.z, fontFlags, "* Dependent files checked: %u", archiveScanner->GetNumFilesHashed()); - font->glFormat(coors.x - texWidth * 0.5f, coors.y - (coors.w * coors.z * 3.0f), coors.z, fontFlags, "* Time Elapsed: %.1fms", (spring_now() - start).toMilliSecsf()); - font->End(); - - globalRendering->SwapBuffers(true, true); -#endif - spring::UnfreezeSpring(WDT_MAIN); - } -#ifndef HEADLESS - glDeleteTextures(1, &texID); -#endif - return future.get(); -} - - - void CPreGame::StartServer(const std::string& setupscript) { assert(gameServer == nullptr); @@ -350,16 +297,13 @@ void CPreGame::StartServer(const std::string& setupscript) // (Which is OK, since unitsync does not have map options available either.) startGameSetup->LoadStartPositions(); - const auto splashScreenFiles = FileSystemMisc::GetSplashScreenFiles(); - const auto& splashScreenFile = splashScreenFiles[guRNG.NextInt(splashScreenFiles.size())]; { - auto start = spring_now(); archiveScanner->ResetNumFilesHashed(); const std::string mapArchive = archiveScanner->ArchiveFromName(startGameSetup->mapName); - const auto mapChecksum = GetArchiveCompleteChecksumBytesWithSplashScreen(mapArchive, start, splashScreenFile); + const auto mapChecksum = archiveScanner->GetArchiveCompleteChecksumBytes(mapArchive); const std::string modArchive = archiveScanner->ArchiveFromName(startGameSetup->modName); - const auto modChecksum = GetArchiveCompleteChecksumBytesWithSplashScreen(modArchive, start, splashScreenFile); + const auto modChecksum = archiveScanner->GetArchiveCompleteChecksumBytes(modArchive); startGameData->SetMapChecksum(mapChecksum.data()); startGameData->SetModChecksum(modChecksum.data()); @@ -386,6 +330,9 @@ void CPreGame::UpdateClientNet() RECOIL_DETAILED_TRACY_ZONE; //FIXME move this code to a external file and move that to rts/Net/ + if (HasPendingAsyncTask()) + return; + clientNet->Update(); if (clientNet->CheckTimeout(0, true)) { @@ -404,7 +351,7 @@ void CPreGame::UpdateClientNet() std::shared_ptr packet; - while ((packet = clientNet->GetData(gs->frameNum))) { + while (!HasPendingAsyncTask() && (packet = clientNet->GetData(gs->frameNum))) { const unsigned char* inbuf = packet->data; if (packet->length <= 0) { @@ -478,7 +425,10 @@ void CPreGame::UpdateClientNet() // server first sends this to let us know about teams, allyteams // etc. (not if we are joining mid-game as an extra player), see // NETMSG_SETPLAYERNUM - GameDataReceived(packet); + pendingTask = std::async(std::launch::async, + &CPreGame::GameDataReceived, this, + packet + ); } break; case NETMSG_SETPLAYERNUM: { @@ -596,6 +546,7 @@ void CPreGame::ReadDataFromDemo(const std::string& demoName) void CPreGame::GameDataReceived(std::shared_ptr packet) { SCOPED_ONCE_TIMER("PreGame::GameDataReceived"); + ENTER_SYNCED_CODE(); // because of async execution try { // in demos, gameData is first new'ed in ReadDataFromDemo() @@ -640,6 +591,8 @@ void CPreGame::GameDataReceived(std::shared_ptr packet throw content_error("Invalid allyteam in game-data"); } + archiveScanner->ResetNumFilesHashed(); + // load archives into VFS AddMapArchivesToVFS(gameSetup); AddModArchivesToVFS(gameSetup); @@ -703,5 +656,21 @@ void CPreGame::GameDataReceived(std::shared_ptr packet LOG("[PreGame::%s] recording demo to \"%s\"", __func__, (clientNet->GetDemoRecorder()->GetName()).c_str()); } + + LEAVE_SYNCED_CODE(); +} + +bool CPreGame::HasPendingAsyncTask() +{ + if (!pendingTask.valid()) + return false; + + using namespace std::chrono_literals; + if (pendingTask.wait_for(0ms) != std::future_status::ready) + return true; + + pendingTask.get(); + pendingTask = {}; + return false; } diff --git a/rts/Game/PreGame.h b/rts/Game/PreGame.h index 143add2ee5..50abadf37e 100644 --- a/rts/Game/PreGame.h +++ b/rts/Game/PreGame.h @@ -5,6 +5,7 @@ #include #include +#include #include "GameController.h" #include "System/Misc/SpringTime.h" @@ -50,9 +51,9 @@ class CPreGame : public CGameController int KeyPressed(int keyCode, int scanCode, bool isRepeat) override; + using AsyncExecFuncType = void (CPreGame::*)(const std::string&); + void AsyncExecute(AsyncExecFuncType execFunc, const std::string& argument); private: - sha512::raw_digest GetArchiveCompleteChecksumBytesWithSplashScreen(const std::string& archiveName, const spring_time& start, const std::string& bitmapFileName); - void AddMapArchivesToVFS(const CGameSetup* setup); void AddModArchivesToVFS(const CGameSetup* setup); @@ -67,6 +68,7 @@ class CPreGame : public CGameController void GameDataReceived(std::shared_ptr packet); + bool HasPendingAsyncTask(); private: /** @brief GameData we received from server @@ -76,6 +78,8 @@ class CPreGame : public CGameController std::shared_ptr gameData; std::shared_ptr clientSetup; + std::future pendingTask; + std::string modFileName; ILoadSaveHandler* saveFileHandler; diff --git a/rts/Menu/SelectMenu.cpp b/rts/Menu/SelectMenu.cpp index c2949faf9e..d4012fb2af 100644 --- a/rts/Menu/SelectMenu.cpp +++ b/rts/Menu/SelectMenu.cpp @@ -204,7 +204,8 @@ void SelectMenu::Demo() clientSetup->demoFile = userDemo; pregame = new CPreGame(clientSetup); - pregame->LoadDemoFile(clientSetup->demoFile); + pregame->AsyncExecute(&CPreGame::LoadDemoFile, clientSetup->demoFile); + //pregame->LoadDemoFile(clientSetup->demoFile); return (agui::gui->RmElement(this)); }; @@ -225,7 +226,8 @@ void SelectMenu::Load() clientSetup->saveFile = userSave; pregame = new CPreGame(clientSetup); - pregame->LoadSaveFile(clientSetup->saveFile); + pregame->AsyncExecute(&CPreGame::LoadSaveFile, clientSetup->saveFile); + //pregame->LoadSaveFile(clientSetup->saveFile); return (agui::gui->RmElement(this)); }; @@ -257,7 +259,9 @@ void SelectMenu::Single() selw->userScript.clear(); pregame = new CPreGame(clientSetup); - pregame->LoadSetupScript(StartScriptGen::CreateDefaultSetup(selw->userMap, selw->userMod, selw->userScript, clientSetup->myPlayerName)); + auto f = &CPreGame::LoadSetupScript; + pregame->AsyncExecute(&CPreGame::LoadSetupScript, StartScriptGen::CreateDefaultSetup(selw->userMap, selw->userMod, selw->userScript, clientSetup->myPlayerName)); + //pregame->LoadSetupScript(StartScriptGen::CreateDefaultSetup(selw->userMap, selw->userMod, selw->userScript, clientSetup->myPlayerName)); return (agui::gui->RmElement(this)); } } diff --git a/rts/System/SplashScreen.cpp b/rts/System/SplashScreen.cpp index 311a8afad2..1da9ff8b23 100644 --- a/rts/System/SplashScreen.cpp +++ b/rts/System/SplashScreen.cpp @@ -44,7 +44,7 @@ void ShowSplashScreen( "[Initializing Virtual File System]", "* archives scanned: %u", "* scantime elapsed: %.1fms", - "Spring %s", + "Recoil %s", "This program is distributed under the GNU General Public License, see doc/LICENSE for more information.", }; diff --git a/rts/System/SpringApp.cpp b/rts/System/SpringApp.cpp index d936dcbc38..e5ad8d8cbc 100644 --- a/rts/System/SpringApp.cpp +++ b/rts/System/SpringApp.cpp @@ -553,7 +553,8 @@ CGameController* SpringApp::LoadSaveFile(const std::string& saveFile) clientSetup->isHost = true; pregame = new CPreGame(clientSetup); - pregame->LoadSaveFile(saveFile); + pregame->AsyncExecute(&CPreGame::LoadSaveFile, saveFile); + //pregame->LoadSaveFile(saveFile); return pregame; } @@ -564,7 +565,8 @@ CGameController* SpringApp::LoadDemoFile(const std::string& demoFile) clientSetup->myPlayerName += " (spec)"; pregame = new CPreGame(clientSetup); - pregame->LoadDemoFile(demoFile); + pregame->AsyncExecute(&CPreGame::LoadDemoFile, demoFile); + //pregame->LoadDemoFile(demoFile); return pregame; } @@ -594,8 +596,10 @@ CGameController* SpringApp::RunScript(const std::string& buf) pregame = new CPreGame(clientSetup); - if (clientSetup->isHost) - pregame->LoadSetupScript(buf); + if (clientSetup->isHost) { + pregame->AsyncExecute(&CPreGame::LoadSetupScript, buf); + //pregame->LoadSetupScript(buf); + } return pregame; }