mirror of
https://github.com/OpenRCT2/OpenRCT2.git
synced 2025-01-22 18:31:59 -05:00
Merge pull request #22301 from AaronVanGeffen/more-progress-bars
Add progress bars to loading saved games and scenarios
This commit is contained in:
commit
1d361a07e3
15 changed files with 153 additions and 50 deletions
|
@ -3720,6 +3720,9 @@ STR_6645 :Makes some UI elements bigger so they are easier to click or tap.
|
|||
STR_6646 :Author: {STRING}
|
||||
STR_6647 :Authors: {STRING}
|
||||
STR_6648 :Loading plugin engine…
|
||||
STR_6649 :Loading scenario…
|
||||
STR_6650 :Loading saved game…
|
||||
STR_6651 :{STRING} ({COMMA32}%)
|
||||
|
||||
#############
|
||||
# Scenarios #
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
- Feature: [#20832] The ride music tab now shows a track listing for the current music style.
|
||||
- Feature: [#22172] [Plugin] Expose ride satisfaction ratings to the plugin API.
|
||||
- Feature: [#22213] [Plugin] Allow plugins to focus on textboxes in custom windows.
|
||||
- Feature: [#22301] Loading save games or scenarios now indicates loading progress.
|
||||
- Feature: [OpenMusic#54] Added Progressive ride music style (feat. Approaching Nirvana).
|
||||
- Change: [#22230] The plugin/script engine is now initialised off the main thread.
|
||||
- Change: [#22251] Hide author info in the scenery window unless debug tools are active.
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include <openrct2/entity/EntityRegistry.h>
|
||||
#include <openrct2/interface/Viewport.h>
|
||||
#include <openrct2/interface/Window.h>
|
||||
#include <openrct2/localisation/StringIds.h>
|
||||
#include <openrct2/management/NewsItem.h>
|
||||
#include <openrct2/object/ObjectManager.h>
|
||||
#include <openrct2/scenario/ScenarioRepository.h>
|
||||
|
@ -48,6 +49,7 @@ namespace OpenRCT2::Title
|
|||
std::unique_ptr<TitleSequence> _sequence;
|
||||
int32_t _position = 0;
|
||||
int32_t _waitCounter = 0;
|
||||
bool _initialLoadCommand = true;
|
||||
|
||||
int32_t _previousWindowWidth = 0;
|
||||
int32_t _previousWindowHeight = 0;
|
||||
|
@ -207,6 +209,7 @@ namespace OpenRCT2::Title
|
|||
{
|
||||
_position = 0;
|
||||
_waitCounter = 0;
|
||||
_initialLoadCommand = true;
|
||||
}
|
||||
|
||||
void Seek(int32_t targetPosition) override
|
||||
|
@ -280,6 +283,20 @@ namespace OpenRCT2::Title
|
|||
return _position != entryPosition;
|
||||
}
|
||||
|
||||
void ReportProgress(uint8_t progress)
|
||||
{
|
||||
if (!_initialLoadCommand)
|
||||
return;
|
||||
|
||||
if (progress == 0)
|
||||
GetContext()->OpenProgress(STR_LOADING_TITLE_SEQUENCE);
|
||||
|
||||
GetContext()->SetProgress(progress, 100, STR_STRING_M_PERCENT, true);
|
||||
|
||||
if (progress == 100)
|
||||
GetContext()->CloseProgress();
|
||||
}
|
||||
|
||||
bool LoadParkFromFile(const u8string& path)
|
||||
{
|
||||
LOG_VERBOSE("TitleSequencePlayer::LoadParkFromFile(%s)", path.c_str());
|
||||
|
@ -294,24 +311,39 @@ namespace OpenRCT2::Title
|
|||
}
|
||||
else
|
||||
{
|
||||
// Inhibit viewport rendering while we're loading
|
||||
WindowSetFlagForAllViewports(VIEWPORT_FLAG_RENDERING_INHIBITED, true);
|
||||
|
||||
ReportProgress(0);
|
||||
auto parkImporter = ParkImporter::Create(path);
|
||||
|
||||
auto result = parkImporter->Load(path);
|
||||
ReportProgress(10);
|
||||
|
||||
auto& objectManager = GetContext()->GetObjectManager();
|
||||
objectManager.LoadObjects(result.RequiredObjects);
|
||||
objectManager.LoadObjects(result.RequiredObjects, true);
|
||||
ReportProgress(90);
|
||||
|
||||
// TODO: Have a separate GameState and exchange once loaded.
|
||||
auto& gameState = GetGameState();
|
||||
parkImporter->Import(gameState);
|
||||
ReportProgress(100);
|
||||
|
||||
MapAnimationAutoCreate();
|
||||
}
|
||||
PrepareParkForPlayback();
|
||||
_initialLoadCommand = false;
|
||||
success = true;
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
Console::Error::WriteLine("Unable to load park: %s", path.c_str());
|
||||
GetContext()->CloseProgress();
|
||||
}
|
||||
|
||||
// Reset viewport rendering inhibition
|
||||
WindowSetFlagForAllViewports(VIEWPORT_FLAG_RENDERING_INHIBITED, false);
|
||||
|
||||
gLoadKeepWindowsOpen = false;
|
||||
return success;
|
||||
}
|
||||
|
@ -334,26 +366,40 @@ namespace OpenRCT2::Title
|
|||
}
|
||||
else
|
||||
{
|
||||
// Inhibit viewport rendering while we're loading
|
||||
WindowSetFlagForAllViewports(VIEWPORT_FLAG_RENDERING_INHIBITED, true);
|
||||
|
||||
ReportProgress(0);
|
||||
bool isScenario = ParkImporter::ExtensionIsScenario(hintPath);
|
||||
auto parkImporter = ParkImporter::Create(hintPath);
|
||||
|
||||
auto result = parkImporter->LoadFromStream(stream, isScenario);
|
||||
ReportProgress(30);
|
||||
|
||||
auto& objectManager = GetContext()->GetObjectManager();
|
||||
objectManager.LoadObjects(result.RequiredObjects);
|
||||
objectManager.LoadObjects(result.RequiredObjects, true);
|
||||
ReportProgress(70);
|
||||
|
||||
// TODO: Have a separate GameState and exchange once loaded.
|
||||
auto& gameState = GetGameState();
|
||||
|
||||
parkImporter->Import(gameState);
|
||||
ReportProgress(100);
|
||||
|
||||
MapAnimationAutoCreate();
|
||||
}
|
||||
PrepareParkForPlayback();
|
||||
_initialLoadCommand = false;
|
||||
success = true;
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
Console::Error::WriteLine("Unable to load park: %s", hintPath.c_str());
|
||||
GetContext()->CloseProgress();
|
||||
}
|
||||
|
||||
// Reset viewport rendering inhibition
|
||||
WindowSetFlagForAllViewports(VIEWPORT_FLAG_RENDERING_INHIBITED, false);
|
||||
|
||||
gLoadKeepWindowsOpen = false;
|
||||
return success;
|
||||
}
|
||||
|
|
|
@ -48,10 +48,6 @@ static Widget _mainWidgets[] = {
|
|||
|
||||
void OnDraw(DrawPixelInfo& dpi) override
|
||||
{
|
||||
// Skip viewport render during preloader
|
||||
if (GetContext()->GetActiveScene() == GetContext()->GetPreloaderScene())
|
||||
return;
|
||||
|
||||
ViewportRender(dpi, viewport, { { dpi.x, dpi.y }, { dpi.x + dpi.width, dpi.y + dpi.height } });
|
||||
}
|
||||
|
||||
|
|
|
@ -656,13 +656,24 @@ namespace OpenRCT2
|
|||
ContextOpenIntent(&intent);
|
||||
}
|
||||
|
||||
void SetProgress(uint32_t currentProgress, uint32_t totalCount, StringId format = STR_NONE) override
|
||||
void SetProgress(
|
||||
uint32_t currentProgress, uint32_t totalCount, StringId format = STR_NONE, bool forceDraw = false) override
|
||||
{
|
||||
auto intent = Intent(INTENT_ACTION_PROGRESS_SET);
|
||||
intent.PutExtra(INTENT_EXTRA_PROGRESS_OFFSET, currentProgress);
|
||||
intent.PutExtra(INTENT_EXTRA_PROGRESS_TOTAL, totalCount);
|
||||
intent.PutExtra(INTENT_EXTRA_STRING_ID, format);
|
||||
ContextOpenIntent(&intent);
|
||||
|
||||
// Ideally, we'd force a redraw at all times at this point. OpenGL has to be directed
|
||||
// from the main thread, though, so this cannot be invoked when off main thread.
|
||||
// It's fine (and indeed useful!) for synchronous calls, so we keep it as an option.
|
||||
if (!gOpenRCT2Headless && forceDraw)
|
||||
{
|
||||
_uiContext->ProcessMessages();
|
||||
WindowInvalidateByClass(WindowClass::ProgressWindow);
|
||||
Draw();
|
||||
}
|
||||
}
|
||||
|
||||
void CloseProgress() override
|
||||
|
@ -755,18 +766,30 @@ namespace OpenRCT2
|
|||
parkImporter = ParkImporter::CreateS6(*_objectRepository);
|
||||
}
|
||||
|
||||
// Inhibit viewport rendering while we're loading
|
||||
WindowSetFlagForAllViewports(VIEWPORT_FLAG_RENDERING_INHIBITED, true);
|
||||
|
||||
OpenProgress(asScenario ? STR_LOADING_SCENARIO : STR_LOADING_SAVED_GAME);
|
||||
SetProgress(0, 100, STR_STRING_M_PERCENT, true);
|
||||
|
||||
auto result = parkImporter->LoadFromStream(stream, info.Type == FILE_TYPE::SCENARIO, false, path.c_str());
|
||||
SetProgress(10, 100, STR_STRING_M_PERCENT, true);
|
||||
|
||||
// From this point onwards the currently loaded park will be corrupted if loading fails
|
||||
// so reload the title screen if that happens.
|
||||
loadTitleScreenFirstOnFail = true;
|
||||
|
||||
GameUnloadScripts();
|
||||
_objectManager->LoadObjects(result.RequiredObjects);
|
||||
_objectManager->LoadObjects(result.RequiredObjects, true);
|
||||
SetProgress(90, 100, STR_STRING_M_PERCENT, true);
|
||||
|
||||
// TODO: Have a separate GameState and exchange once loaded.
|
||||
auto& gameState = ::GetGameState();
|
||||
parkImporter->Import(gameState);
|
||||
SetProgress(100, 100, STR_STRING_M_PERCENT, true);
|
||||
|
||||
// Reset viewport rendering inhibition
|
||||
WindowSetFlagForAllViewports(VIEWPORT_FLAG_RENDERING_INHIBITED, false);
|
||||
|
||||
gScenarioSavePath = path;
|
||||
gCurrentLoadedPath = path;
|
||||
|
@ -841,6 +864,7 @@ namespace OpenRCT2
|
|||
windowManager->ShowError(STR_PARK_USES_FALLBACK_IMAGES_WARNING, STR_EMPTY, Formatter());
|
||||
}
|
||||
|
||||
CloseProgress();
|
||||
return true;
|
||||
}
|
||||
catch (const ObjectLoadException& e)
|
||||
|
@ -916,6 +940,8 @@ namespace OpenRCT2
|
|||
Console::Error::WriteLine(e.what());
|
||||
}
|
||||
|
||||
CloseProgress();
|
||||
WindowSetFlagForAllViewports(VIEWPORT_FLAG_RENDERING_INHIBITED, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -160,7 +160,9 @@ namespace OpenRCT2
|
|||
virtual void DisposeDrawingEngine() = 0;
|
||||
|
||||
virtual void OpenProgress(StringId captionStringId) = 0;
|
||||
virtual void SetProgress(uint32_t currentProgress, uint32_t totalCount, StringId format = STR_NONE) = 0;
|
||||
virtual void SetProgress(
|
||||
uint32_t currentProgress, uint32_t totalCount, StringId format = STR_NONE, bool forceDraw = false)
|
||||
= 0;
|
||||
virtual void CloseProgress() = 0;
|
||||
|
||||
virtual bool LoadParkFromFile(const u8string& path, bool loadTitleScreenOnFail = false, bool asScenario = false) = 0;
|
||||
|
|
|
@ -927,6 +927,9 @@ void ViewportRotateAll(int32_t direction)
|
|||
*/
|
||||
void ViewportRender(DrawPixelInfo& dpi, const Viewport* viewport, const ScreenRect& screenRect)
|
||||
{
|
||||
if (viewport->flags & VIEWPORT_FLAG_RENDERING_INHIBITED)
|
||||
return;
|
||||
|
||||
auto [topLeft, bottomRight] = screenRect;
|
||||
|
||||
if (bottomRight.x <= viewport->pos.x)
|
||||
|
@ -1018,6 +1021,9 @@ static void ViewportPaint(const Viewport* viewport, DrawPixelInfo& dpi, const Sc
|
|||
PROFILED_FUNCTION();
|
||||
|
||||
const uint32_t viewFlags = viewport->flags;
|
||||
if (viewFlags & VIEWPORT_FLAG_RENDERING_INHIBITED)
|
||||
return;
|
||||
|
||||
uint32_t width = screenRect.GetWidth();
|
||||
uint32_t height = screenRect.GetHeight();
|
||||
const uint32_t bitmask = viewport->zoom >= ZoomLevel{ 0 } ? 0xFFFFFFFF & (viewport->zoom.ApplyTo(0xFFFFFFFF)) : 0xFFFFFFFF;
|
||||
|
|
|
@ -66,6 +66,7 @@ enum : uint32_t
|
|||
VIEWPORT_FLAG_INVISIBLE_SUPPORTS = (1u << 29),
|
||||
|
||||
VIEWPORT_FLAG_INDEPEDENT_ROTATION = (1u << 30),
|
||||
VIEWPORT_FLAG_RENDERING_INHIBITED = (1u << 31),
|
||||
};
|
||||
|
||||
enum class VisibilityKind
|
||||
|
|
|
@ -101,6 +101,19 @@ void WindowVisitEach(std::function<void(WindowBase*)> func)
|
|||
}
|
||||
}
|
||||
|
||||
void WindowSetFlagForAllViewports(uint32_t viewportFlag, bool enabled)
|
||||
{
|
||||
WindowVisitEach([&](WindowBase* w) {
|
||||
if (w->viewport != nullptr)
|
||||
{
|
||||
if (enabled)
|
||||
w->viewport->flags |= viewportFlag;
|
||||
else
|
||||
w->viewport->flags &= ~viewportFlag;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* rct2: 0x006ED7B0
|
||||
|
|
|
@ -491,6 +491,8 @@ extern bool gDisableErrorWindowSound;
|
|||
std::list<std::shared_ptr<WindowBase>>::iterator WindowGetIterator(const WindowBase* w);
|
||||
void WindowVisitEach(std::function<void(WindowBase*)> func);
|
||||
|
||||
void WindowSetFlagForAllViewports(uint32_t viewportFlag, bool enabled);
|
||||
|
||||
void WindowDispatchUpdateAll();
|
||||
void WindowUpdateAllViewports();
|
||||
void WindowUpdateAll();
|
||||
|
|
|
@ -1685,6 +1685,9 @@ enum : StringId
|
|||
STR_STRING_M_OF_N_KIB = 6643,
|
||||
|
||||
STR_LOADING_PLUGIN_ENGINE = 6648,
|
||||
STR_LOADING_SCENARIO = 6649,
|
||||
STR_LOADING_SAVED_GAME = 6650,
|
||||
STR_STRING_M_PERCENT = 6651,
|
||||
|
||||
// Have to include resource strings (from scenarios and objects) for the time being now that language is partially working
|
||||
/* MAX_STR_COUNT = 32768 */ // MAX_STR_COUNT - upper limit for number of strings, not the current count strings
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include "../ParkImporter.h"
|
||||
#include "../audio/audio.h"
|
||||
#include "../core/Console.hpp"
|
||||
#include "../core/JobPool.h"
|
||||
#include "../core/Memory.hpp"
|
||||
#include "../localisation/StringIds.h"
|
||||
#include "../ride/Ride.h"
|
||||
|
@ -38,6 +39,8 @@
|
|||
#include <thread>
|
||||
#include <unordered_set>
|
||||
|
||||
using namespace OpenRCT2;
|
||||
|
||||
/**
|
||||
* Represents an object that is to be loaded or is loaded and ready
|
||||
* to be placed in an object list.
|
||||
|
@ -183,13 +186,13 @@ public:
|
|||
return RepositoryItemToObject(ori, slot);
|
||||
}
|
||||
|
||||
void LoadObjects(const ObjectList& objectList) override
|
||||
void LoadObjects(const ObjectList& objectList, const bool reportProgress) override
|
||||
{
|
||||
// Find all the required objects
|
||||
auto requiredObjects = GetRequiredObjects(objectList);
|
||||
|
||||
// Load the required objects
|
||||
LoadObjects(requiredObjects);
|
||||
LoadObjects(requiredObjects, reportProgress);
|
||||
|
||||
// Update indices.
|
||||
UpdateSceneryGroupIndexes();
|
||||
|
@ -556,31 +559,17 @@ private:
|
|||
return requiredObjects;
|
||||
}
|
||||
|
||||
template<typename T, typename TFunc> static void ParallelFor(const std::vector<T>& items, TFunc func)
|
||||
void ReportProgress(size_t numLoaded, size_t numRequired)
|
||||
{
|
||||
auto partitions = std::thread::hardware_concurrency();
|
||||
auto partitionSize = (items.size() + (partitions - 1)) / partitions;
|
||||
std::vector<std::thread> threads;
|
||||
for (size_t n = 0; n < partitions; n++)
|
||||
{
|
||||
auto begin = n * partitionSize;
|
||||
auto end = std::min(items.size(), begin + partitionSize);
|
||||
threads.emplace_back(
|
||||
[func](size_t pbegin, size_t pend) {
|
||||
for (size_t i = pbegin; i < pend; i++)
|
||||
{
|
||||
func(i);
|
||||
}
|
||||
},
|
||||
begin, end);
|
||||
}
|
||||
for (auto& t : threads)
|
||||
{
|
||||
t.join();
|
||||
}
|
||||
constexpr auto kObjectLoadMinProgress = 10;
|
||||
constexpr auto kObjectLoadMaxProgress = 90;
|
||||
constexpr auto kObjectLoadProgressRange = kObjectLoadMaxProgress - kObjectLoadMinProgress;
|
||||
|
||||
const auto currentProgress = kObjectLoadMinProgress + (numLoaded * kObjectLoadProgressRange / numRequired);
|
||||
OpenRCT2::GetContext()->SetProgress(static_cast<uint32_t>(currentProgress), 100, STR_STRING_M_PERCENT, true);
|
||||
}
|
||||
|
||||
void LoadObjects(std::vector<ObjectToLoad>& requiredObjects)
|
||||
void LoadObjects(std::vector<ObjectToLoad>& requiredObjects, bool reportProgress)
|
||||
{
|
||||
std::vector<Object*> objects;
|
||||
std::vector<Object*> newLoadedObjects;
|
||||
|
@ -607,11 +596,11 @@ private:
|
|||
std::sort(objectsToLoad.begin(), objectsToLoad.end());
|
||||
objectsToLoad.erase(std::unique(objectsToLoad.begin(), objectsToLoad.end()), objectsToLoad.end());
|
||||
|
||||
// Load the objects.
|
||||
// Prepare for loading objects multi-threaded
|
||||
auto numProcessed = 0;
|
||||
auto numRequired = objectsToLoad.size();
|
||||
std::mutex commonMutex;
|
||||
ParallelFor(objectsToLoad, [&](size_t i) {
|
||||
const auto* requiredObject = objectsToLoad[i];
|
||||
|
||||
auto loadSingleObject = [&](const ObjectRepositoryItem* requiredObject) {
|
||||
// Object requires to be loaded, if the object successfully loads it will register it
|
||||
// as a loaded object otherwise placed into the badObjects list.
|
||||
auto newObject = _objectRepository.LoadObject(requiredObject);
|
||||
|
@ -628,7 +617,24 @@ private:
|
|||
// Connect the ori to the registered object
|
||||
_objectRepository.RegisterLoadedObject(requiredObject, std::move(newObject));
|
||||
}
|
||||
});
|
||||
|
||||
numProcessed++;
|
||||
};
|
||||
|
||||
auto completionFn = [&]() {
|
||||
if (reportProgress && (numProcessed % 100) == 0)
|
||||
ReportProgress(numProcessed, numRequired);
|
||||
};
|
||||
|
||||
// Dispatch loading the objects
|
||||
JobPool jobs{};
|
||||
for (auto* object : objectsToLoad)
|
||||
{
|
||||
jobs.AddTask([object, &loadSingleObject]() { loadSingleObject(object); }, completionFn);
|
||||
}
|
||||
|
||||
// Wait until all jobs are fully completed
|
||||
jobs.Join();
|
||||
|
||||
// Assign the loaded objects to the required objects
|
||||
for (auto& requiredObject : requiredObjects)
|
||||
|
|
|
@ -36,7 +36,7 @@ struct IObjectManager
|
|||
virtual Object* LoadObject(const RCTObjectEntry* entry) = 0;
|
||||
virtual Object* LoadObject(const ObjectEntryDescriptor& descriptor) = 0;
|
||||
virtual Object* LoadObject(const ObjectEntryDescriptor& descriptor, ObjectEntryIndex slot) = 0;
|
||||
virtual void LoadObjects(const ObjectList& entries) = 0;
|
||||
virtual void LoadObjects(const ObjectList& entries, const bool reportProgress = false) = 0;
|
||||
virtual void UnloadObjects(const std::vector<ObjectEntryDescriptor>& entries) = 0;
|
||||
virtual void UnloadAllTransient() = 0;
|
||||
virtual void UnloadAll() = 0;
|
||||
|
|
|
@ -41,6 +41,7 @@ void PreloaderScene::Load()
|
|||
gameStateInitAll(GetGameState(), DEFAULT_MAP_SIZE);
|
||||
ViewportInitAll();
|
||||
ContextOpenWindow(WindowClass::MainWindow);
|
||||
WindowSetFlagForAllViewports(VIEWPORT_FLAG_RENDERING_INHIBITED, true);
|
||||
WindowResizeGui(ContextGetWidth(), ContextGetHeight());
|
||||
|
||||
// Reset screen
|
||||
|
|
|
@ -109,19 +109,10 @@ void TitleScene::Load()
|
|||
gameStateInitAll(GetGameState(), DEFAULT_MAP_SIZE);
|
||||
ViewportInitAll();
|
||||
ContextOpenWindow(WindowClass::MainWindow);
|
||||
CreateWindows();
|
||||
|
||||
GetContext().OpenProgress(STR_LOADING_TITLE_SEQUENCE);
|
||||
|
||||
TitleInitialise();
|
||||
OpenRCT2::Audio::PlayTitleMusic();
|
||||
|
||||
if (gOpenRCT2ShowChangelog)
|
||||
{
|
||||
gOpenRCT2ShowChangelog = false;
|
||||
ContextOpenWindow(WindowClass::Changelog);
|
||||
}
|
||||
|
||||
if (_sequencePlayer != nullptr)
|
||||
{
|
||||
// Force the title sequence to load / update so we
|
||||
|
@ -131,7 +122,13 @@ void TitleScene::Load()
|
|||
_sequencePlayer->Update();
|
||||
}
|
||||
|
||||
GetContext().CloseProgress();
|
||||
CreateWindows();
|
||||
|
||||
if (gOpenRCT2ShowChangelog)
|
||||
{
|
||||
gOpenRCT2ShowChangelog = false;
|
||||
ContextOpenWindow(WindowClass::Changelog);
|
||||
}
|
||||
|
||||
LOG_VERBOSE("TitleScene::Load() finished");
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue