mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
Use a coroutine for Loader tasks
Some tasks are invoked multiple times. Normally those tasks are broken up inside a loop and had to be continued there. With coroutines that is easier as it's possible to suspend inside a loop. Coroutines which are lambdas should not capture anythig as the lifetime of the captured values might end before the coroutine completes. For that purpose `std::bind_front` is used.
This commit is contained in:
parent
b67faf35c3
commit
5586802b86
12 changed files with 421 additions and 322 deletions
|
|
@ -206,20 +206,16 @@ CMiniMapTexture& CGameView::GetMiniMapTexture()
|
|||
void CGameView::RegisterInit()
|
||||
{
|
||||
// CGameView init
|
||||
PS::Loader::Register([this]
|
||||
PS::Loader::Register(std::bind_front([](ICameraController* cameraController) -> PS::Loader::Task
|
||||
{
|
||||
m->CameraController->LoadConfig();
|
||||
return 0;
|
||||
}, L"CGameView init", 1);
|
||||
cameraController->LoadConfig();
|
||||
co_return 0;
|
||||
}, m->CameraController.get()), L"CGameView init", 1);
|
||||
|
||||
PS::Loader::Register([]
|
||||
{
|
||||
return g_TexMan.StartTerrainTextures();
|
||||
}, L"StartTerrainTextures", 1);
|
||||
PS::Loader::Register([]
|
||||
{
|
||||
return g_TexMan.PollTerrainTextures();
|
||||
}, L"PollTerrainTextures", 60);
|
||||
return g_TexMan.LoadTerrainTextures();
|
||||
}, L"TerrainTextures", 61);
|
||||
}
|
||||
|
||||
void CGameView::BeginFrame()
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@
|
|||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <js/PropertyAndElement.h>
|
||||
#include <ranges>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
|
@ -150,21 +151,21 @@ void CMapReader::LoadMap(const VfsPath& pathname, const ScriptContext& cx, JS::
|
|||
|
||||
// load map or script settings script
|
||||
if (settings.isUndefined())
|
||||
PS::Loader::Register([this]
|
||||
PS::Loader::Register(std::bind_front([](CMapReader* mapReader) -> PS::Loader::Task
|
||||
{
|
||||
return LoadScriptSettings();
|
||||
}, L"CMapReader::LoadScriptSettings", 50);
|
||||
co_return mapReader->LoadScriptSettings();
|
||||
}, this), L"CMapReader::LoadScriptSettings", 50);
|
||||
else
|
||||
PS::Loader::Register([this]
|
||||
PS::Loader::Register(std::bind_front([](CMapReader* mapReader) -> PS::Loader::Task
|
||||
{
|
||||
return LoadRMSettings();
|
||||
}, L"CMapReader::LoadRMSettings", 50);
|
||||
co_return mapReader->LoadRMSettings();
|
||||
}, this), L"CMapReader::LoadRMSettings", 50);
|
||||
|
||||
// load player settings script (must be done before reading map)
|
||||
PS::Loader::Register([this]
|
||||
PS::Loader::Register(std::bind_front([](CMapReader* mapReader) -> PS::Loader::Task
|
||||
{
|
||||
return LoadPlayerSettings();
|
||||
}, L"CMapReader::LoadPlayerSettings", 50);
|
||||
co_return mapReader->LoadPlayerSettings();
|
||||
}, this), L"CMapReader::LoadPlayerSettings", 50);
|
||||
|
||||
// unpack the data
|
||||
if (!only_xml)
|
||||
|
|
@ -174,16 +175,16 @@ void CMapReader::LoadMap(const VfsPath& pathname, const ScriptContext& cx, JS::
|
|||
}, L"CMapReader::UnpackMap", 1200);
|
||||
|
||||
// read the corresponding XML file
|
||||
PS::Loader::Register([this]
|
||||
PS::Loader::Register(std::bind_front([](CMapReader* mapReader) -> PS::Loader::Task
|
||||
{
|
||||
return ReadXML();
|
||||
}, L"CMapReader::ReadXML", 50);
|
||||
co_return mapReader->ReadXML();
|
||||
}, this), L"CMapReader::ReadXML", 50);
|
||||
|
||||
// apply terrain data to the world
|
||||
PS::Loader::Register([this]
|
||||
PS::Loader::Register(std::bind_front([](CMapReader* mapReader) -> PS::Loader::Task
|
||||
{
|
||||
return ApplyTerrainData();
|
||||
}, L"CMapReader::ApplyTerrainData", 5);
|
||||
co_return mapReader->ApplyTerrainData();
|
||||
}, this), L"CMapReader::ApplyTerrainData", 5);
|
||||
|
||||
// read entities
|
||||
PS::Loader::Register([this]
|
||||
|
|
@ -192,16 +193,16 @@ void CMapReader::LoadMap(const VfsPath& pathname, const ScriptContext& cx, JS::
|
|||
}, L"CMapReader::ReadXMLEntities", 5800);
|
||||
|
||||
// apply misc data to the world
|
||||
PS::Loader::Register([this]
|
||||
PS::Loader::Register(std::bind_front([](CMapReader* mapReader) -> PS::Loader::Task
|
||||
{
|
||||
return ApplyData();
|
||||
}, L"CMapReader::ApplyData", 5);
|
||||
co_return mapReader->ApplyData();
|
||||
}, this), L"CMapReader::ApplyData", 5);
|
||||
|
||||
// load map settings script (must be done after reading map)
|
||||
PS::Loader::Register([this]
|
||||
PS::Loader::Register(std::bind_front([](CMapReader* mapReader) -> PS::Loader::Task
|
||||
{
|
||||
return LoadMapSettings();
|
||||
}, L"CMapReader::LoadMapSettings", 5);
|
||||
co_return mapReader->LoadMapSettings();
|
||||
}, this), L"CMapReader::LoadMapSettings", 5);
|
||||
}
|
||||
|
||||
// LoadRandomMap: try to load the map data; reinitialise the scene to new data if successful
|
||||
|
|
@ -232,86 +233,70 @@ void CMapReader::LoadRandomMap(const CStrW& scriptFile, const ScriptContext& cx,
|
|||
only_xml = false;
|
||||
|
||||
// copy random map settings (before entity creation)
|
||||
PS::Loader::Register([this]
|
||||
PS::Loader::Register(std::bind_front([](CMapReader* mapReader) -> PS::Loader::Task
|
||||
{
|
||||
return LoadRMSettings();
|
||||
}, L"CMapReader::LoadRMSettings", 50);
|
||||
co_return mapReader->LoadRMSettings();
|
||||
}, this), L"CMapReader::LoadRMSettings", 50);
|
||||
|
||||
// load player settings script (must be done before reading map)
|
||||
PS::Loader::Register([this]
|
||||
PS::Loader::Register(std::bind_front([](CMapReader* mapReader) -> PS::Loader::Task
|
||||
{
|
||||
return LoadPlayerSettings();
|
||||
}, L"CMapReader::LoadPlayerSettings", 50);
|
||||
co_return mapReader->LoadPlayerSettings();
|
||||
}, this), L"CMapReader::LoadPlayerSettings", 50);
|
||||
|
||||
// load map generator with random map script
|
||||
PS::Loader::Register([this, scriptFile]
|
||||
{
|
||||
return StartMapGeneration(scriptFile);
|
||||
}, L"CMapReader::StartMapGeneration", 1);
|
||||
|
||||
PS::Loader::Register([this]
|
||||
{
|
||||
return PollMapGeneration();
|
||||
}, L"CMapReader::PollMapGeneration", 19999);
|
||||
return RunMapGeneration(scriptFile);
|
||||
}, L"CMapReader::RunMapGeneration", 20000);
|
||||
|
||||
// parse RMS results into terrain structure
|
||||
PS::Loader::Register([this]
|
||||
PS::Loader::Register(std::bind_front([](CMapReader* mapReader) -> PS::Loader::Task
|
||||
{
|
||||
return ParseTerrain();
|
||||
}, L"CMapReader::ParseTerrain", 500);
|
||||
co_return mapReader->ParseTerrain();
|
||||
}, this), L"CMapReader::ParseTerrain", 500);
|
||||
|
||||
// parse RMS results into environment settings
|
||||
PS::Loader::Register([this]
|
||||
PS::Loader::Register(std::bind_front([](CMapReader* mapReader) -> PS::Loader::Task
|
||||
{
|
||||
return ParseEnvironment();
|
||||
}, L"CMapReader::ParseEnvironment", 5);
|
||||
co_return mapReader->ParseEnvironment();
|
||||
}, this), L"CMapReader::ParseEnvironment", 5);
|
||||
|
||||
// parse RMS results into camera settings
|
||||
PS::Loader::Register([this]
|
||||
PS::Loader::Register(std::bind_front([](CMapReader* mapReader) -> PS::Loader::Task
|
||||
{
|
||||
return ParseCamera();
|
||||
}, L"CMapReader::ParseCamera", 5);
|
||||
co_return mapReader->ParseCamera();
|
||||
}, this), L"CMapReader::ParseCamera", 5);
|
||||
|
||||
// apply terrain data to the world
|
||||
PS::Loader::Register([this]
|
||||
PS::Loader::Register(std::bind_front([](CMapReader* mapReader) -> PS::Loader::Task
|
||||
{
|
||||
return ApplyTerrainData();
|
||||
}, L"CMapReader::ApplyTerrainData", 5);
|
||||
co_return mapReader->ApplyTerrainData();
|
||||
}, this), L"CMapReader::ApplyTerrainData", 5);
|
||||
|
||||
// parse RMS results into entities
|
||||
PS::Loader::Register([this]
|
||||
{
|
||||
return StartParseEntities();
|
||||
}, L"CMapReader::StartParseEntities", 10);
|
||||
PS::Loader::Register([this]
|
||||
{
|
||||
return PollParseEntities();
|
||||
}, L"CMapReader::PollParseEntities", 1000);
|
||||
return ParseEntities();
|
||||
}, L"CMapReader::ParseEntities", 1010);
|
||||
|
||||
// apply misc data to the world
|
||||
PS::Loader::Register([this]
|
||||
PS::Loader::Register(std::bind_front([](CMapReader* mapReader) -> PS::Loader::Task
|
||||
{
|
||||
return ApplyData();
|
||||
}, L"CMapReader::ApplyData", 5);
|
||||
co_return mapReader->ApplyData();
|
||||
}, this), L"CMapReader::ApplyData", 5);
|
||||
|
||||
// load map settings script (must be done after reading map)
|
||||
PS::Loader::Register([this]
|
||||
PS::Loader::Register(std::bind_front([](CMapReader* mapReader) -> PS::Loader::Task
|
||||
{
|
||||
return LoadMapSettings();
|
||||
}, L"CMapReader::LoadMapSettings", 5);
|
||||
co_return mapReader->LoadMapSettings();
|
||||
}, this), L"CMapReader::LoadMapSettings", 5);
|
||||
}
|
||||
|
||||
// UnpackTerrain: unpack the terrain from the end of the input data stream
|
||||
// - data: map size, heightmap, list of textures used by map, texture tile assignments
|
||||
int CMapReader::UnpackTerrain()
|
||||
PS::Loader::Task CMapReader::UnpackTerrain()
|
||||
{
|
||||
// yield after this time is reached. balances increased progress bar
|
||||
// smoothness vs. slowing down loading.
|
||||
const double end_time = timer_Time() + 200e-3;
|
||||
|
||||
// first call to generator (this is skipped after first call,
|
||||
// i.e. when the loop below was interrupted)
|
||||
if (cur_terrain_tex == 0)
|
||||
{
|
||||
m_PatchesPerSide = (ssize_t)unpacker.UnpackSize();
|
||||
|
||||
|
|
@ -319,15 +304,15 @@ int CMapReader::UnpackTerrain()
|
|||
size_t verticesPerSide = m_PatchesPerSide*PATCH_SIZE+1;
|
||||
m_Heightmap.resize(SQR(verticesPerSide));
|
||||
unpacker.UnpackRaw(&m_Heightmap[0], SQR(verticesPerSide)*sizeof(u16));
|
||||
|
||||
// unpack # textures
|
||||
num_terrain_tex = unpacker.UnpackSize();
|
||||
m_TerrainTextures.reserve(num_terrain_tex);
|
||||
}
|
||||
|
||||
// unpack # textures
|
||||
const std::size_t numTerrainTex{unpacker.UnpackSize()};
|
||||
m_TerrainTextures.reserve(numTerrainTex);
|
||||
|
||||
// unpack texture names; find handle for each texture.
|
||||
// interruptible.
|
||||
while (cur_terrain_tex < num_terrain_tex)
|
||||
for (std::size_t curTerrainTex{0}; curTerrainTex != numTerrainTex; ++curTerrainTex)
|
||||
{
|
||||
CStr texturename;
|
||||
unpacker.UnpackString(texturename);
|
||||
|
|
@ -338,8 +323,7 @@ int CMapReader::UnpackTerrain()
|
|||
m_TerrainTextures.push_back(texentry);
|
||||
}
|
||||
|
||||
cur_terrain_tex++;
|
||||
LDR_CHECK_TIMEOUT(cur_terrain_tex, num_terrain_tex);
|
||||
co_yield 100 * (curTerrainTex + 1) / numTerrainTex;
|
||||
}
|
||||
|
||||
// unpack tile data [3ms]
|
||||
|
|
@ -347,10 +331,7 @@ int CMapReader::UnpackTerrain()
|
|||
m_Tiles.resize(size_t(SQR(tilesPerSide)));
|
||||
unpacker.UnpackRaw(&m_Tiles[0], sizeof(STileDesc)*m_Tiles.size());
|
||||
|
||||
// reset generator state.
|
||||
cur_terrain_tex = 0;
|
||||
|
||||
return 0;
|
||||
co_return 0;
|
||||
}
|
||||
|
||||
int CMapReader::ApplyTerrainData()
|
||||
|
|
@ -504,7 +485,7 @@ public:
|
|||
void ReadXML();
|
||||
|
||||
// return semantics: see Loader.cpp!LoadFunc.
|
||||
int ProgressiveReadEntities();
|
||||
PS::Loader::Task ProgressiveReadEntities();
|
||||
|
||||
private:
|
||||
CXeromyces xmb_file;
|
||||
|
|
@ -529,10 +510,6 @@ private:
|
|||
|
||||
XMBElementList nodes; // children of root
|
||||
|
||||
// loop counters
|
||||
size_t node_idx;
|
||||
size_t entity_idx;
|
||||
|
||||
// # entities+nonentities processed and total (for progress calc)
|
||||
int completed_jobs, total_jobs;
|
||||
|
||||
|
|
@ -546,15 +523,12 @@ private:
|
|||
void ReadCamera(XMBElement parent);
|
||||
void ReadPaths(XMBElement parent);
|
||||
void ReadTriggers(XMBElement parent);
|
||||
int ReadEntities(XMBElement parent, double end_time);
|
||||
PS::Loader::Task ReadEntities(XMBElement parent);
|
||||
};
|
||||
|
||||
|
||||
void CXMLReader::Init(const VfsPath& xml_filename)
|
||||
{
|
||||
// must only assign once, so do it here
|
||||
node_idx = entity_idx = 0;
|
||||
|
||||
if (xmb_file.Load(g_VFS, xml_filename, "scenario") != PSRETURN_OK)
|
||||
throw PSERROR_Game_World_MapLoadFailed("Could not read map XML file!");
|
||||
|
||||
|
|
@ -1023,7 +997,7 @@ void CXMLReader::ReadTriggers(XMBElement /*parent*/)
|
|||
{
|
||||
}
|
||||
|
||||
int CXMLReader::ReadEntities(XMBElement parent, double end_time)
|
||||
PS::Loader::Task CXMLReader::ReadEntities(XMBElement parent)
|
||||
{
|
||||
XMBElementList entities = parent.GetChildNodes();
|
||||
|
||||
|
|
@ -1031,12 +1005,8 @@ int CXMLReader::ReadEntities(XMBElement parent, double end_time)
|
|||
CSimulation2& sim = *m_MapReader.pSimulation2;
|
||||
CmpPtr<ICmpPlayerManager> cmpPlayerManager(sim, SYSTEM_ENTITY);
|
||||
|
||||
while (entity_idx < entities.size())
|
||||
for (XMBElement entity : entities)
|
||||
{
|
||||
// all new state at this scope and below doesn't need to be
|
||||
// wrapped, since we only yield after a complete iteration.
|
||||
|
||||
XMBElement entity = entities[entity_idx++];
|
||||
ENSURE(entity.GetNodeName() == el_entity);
|
||||
|
||||
XMBAttributeList attrs = entity.GetAttributes();
|
||||
|
|
@ -1143,7 +1113,7 @@ int CXMLReader::ReadEntities(XMBElement parent, double end_time)
|
|||
if (cmpPlayer && cmpPlayer->IsRemoved())
|
||||
{
|
||||
completed_jobs++;
|
||||
LDR_CHECK_TIMEOUT(completed_jobs, total_jobs);
|
||||
co_yield 100 * completed_jobs / total_jobs;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -1214,10 +1184,10 @@ int CXMLReader::ReadEntities(XMBElement parent, double end_time)
|
|||
}
|
||||
|
||||
completed_jobs++;
|
||||
LDR_CHECK_TIMEOUT(completed_jobs, total_jobs);
|
||||
co_yield 100 * completed_jobs / total_jobs;
|
||||
}
|
||||
|
||||
return 0;
|
||||
co_return 0;
|
||||
}
|
||||
|
||||
void CXMLReader::ReadXML()
|
||||
|
|
@ -1266,32 +1236,28 @@ void CXMLReader::ReadXML()
|
|||
}
|
||||
}
|
||||
|
||||
int CXMLReader::ProgressiveReadEntities()
|
||||
PS::Loader::Task CXMLReader::ProgressiveReadEntities()
|
||||
{
|
||||
// yield after this time is reached. balances increased progress bar
|
||||
// smoothness vs. slowing down loading.
|
||||
const double end_time = timer_Time() + 200e-3;
|
||||
if (m_MapReader.m_SkipEntities)
|
||||
co_return 0;
|
||||
|
||||
int ret;
|
||||
|
||||
while (node_idx < nodes.size())
|
||||
for (XMBElement node : nodes)
|
||||
{
|
||||
XMBElement node = nodes[node_idx];
|
||||
CStr name = xmb_file.GetElementString(node.GetNodeName());
|
||||
if (name == "Entities")
|
||||
{
|
||||
if (!m_MapReader.m_SkipEntities)
|
||||
{
|
||||
ret = ReadEntities(node, end_time);
|
||||
if (ret != 0) // error or timed out
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
if (name != "Entities")
|
||||
continue;
|
||||
|
||||
node_idx++;
|
||||
PS::Loader::Task subTask{ReadEntities(node)};
|
||||
while (!subTask.IsDone())
|
||||
{
|
||||
co_yield subTask.GetProgress();
|
||||
subTask.Step(-1);
|
||||
}
|
||||
if (subTask.Get() < 0)
|
||||
co_return subTask.Get();
|
||||
}
|
||||
|
||||
return 0;
|
||||
co_return 0;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -1339,17 +1305,22 @@ int CMapReader::ReadXML()
|
|||
}
|
||||
|
||||
// progressive
|
||||
int CMapReader::ReadXMLEntities()
|
||||
PS::Loader::Task CMapReader::ReadXMLEntities()
|
||||
{
|
||||
if (!m_XmlReader)
|
||||
m_XmlReader = std::make_unique<CXMLReader>(m_FilenameXml, *this);
|
||||
|
||||
int ret = m_XmlReader->ProgressiveReadEntities();
|
||||
// finished or failed
|
||||
if (ret <= 0)
|
||||
m_XmlReader.reset();
|
||||
PS::Loader::Task task{m_XmlReader->ProgressiveReadEntities()};
|
||||
while (!task.IsDone())
|
||||
{
|
||||
co_yield task.GetProgress();
|
||||
// Do as litle as possible.
|
||||
task.Step(-1);
|
||||
}
|
||||
|
||||
return ret;
|
||||
m_XmlReader.reset();
|
||||
|
||||
co_return task.Get();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -1366,22 +1337,20 @@ int CMapReader::LoadRMSettings()
|
|||
return 0;
|
||||
}
|
||||
|
||||
struct CMapReader::GeneratorState
|
||||
[[noreturn]] void ThrowMapGenerationError()
|
||||
{
|
||||
throw PSERROR_Game_World_MapLoadFailed{
|
||||
"Error generating random map.\nCheck application log for details."};
|
||||
}
|
||||
|
||||
PS::Loader::Task CMapReader::RunMapGeneration(const CStrW& scriptFile)
|
||||
{
|
||||
std::atomic<int> progress{1};
|
||||
Future<Script::StructuredClone> task;
|
||||
};
|
||||
|
||||
int CMapReader::StartMapGeneration(const CStrW& scriptFile)
|
||||
{
|
||||
ScriptRequest rq(pSimulation2->GetScriptInterface());
|
||||
|
||||
m_GeneratorState = std::make_unique<GeneratorState>();
|
||||
|
||||
// The settings are stringified to pass them to the task.
|
||||
m_GeneratorState->task = {g_TaskManager,
|
||||
[&progress = m_GeneratorState->progress, scriptFile,
|
||||
settings = Script::StringifyJSON(rq, &m_ScriptSettings)](const StopToken stopToken)
|
||||
Future<Script::StructuredClone> task = {g_TaskManager,
|
||||
[&progress, scriptFile, settings = Script::StringifyJSON(ScriptRequest{
|
||||
pSimulation2->GetScriptInterface()}, &m_ScriptSettings)](const StopToken stopToken)
|
||||
{
|
||||
PROFILE2("Map Generation");
|
||||
|
||||
|
|
@ -1400,29 +1369,19 @@ int CMapReader::StartMapGeneration(const CStrW& scriptFile)
|
|||
return RunMapGenerationScript(stopToken, progress, mapgenInterface, scriptPath, settings);
|
||||
}};
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
[[noreturn]] void ThrowMapGenerationError()
|
||||
{
|
||||
throw PSERROR_Game_World_MapLoadFailed{
|
||||
"Error generating random map.\nCheck application log for details."};
|
||||
}
|
||||
|
||||
int CMapReader::PollMapGeneration()
|
||||
{
|
||||
ENSURE(m_GeneratorState);
|
||||
|
||||
if (IsQuitRequested())
|
||||
while (!task.IsDone())
|
||||
{
|
||||
LOGWARNING("Quit requested!");
|
||||
return -1;
|
||||
co_yield progress.load();
|
||||
co_await std::suspend_always{};
|
||||
|
||||
if (IsQuitRequested())
|
||||
{
|
||||
LOGWARNING("Quit requested!");
|
||||
co_return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_GeneratorState->task.IsDone())
|
||||
return m_GeneratorState->progress.load();
|
||||
|
||||
const Script::StructuredClone results{m_GeneratorState->task.Get()};
|
||||
const Script::StructuredClone results{task.Get()};
|
||||
if (!results)
|
||||
ThrowMapGenerationError();
|
||||
|
||||
|
|
@ -1436,7 +1395,7 @@ int CMapReader::PollMapGeneration()
|
|||
|
||||
m_MapData.init(rq.cx, data);
|
||||
|
||||
return 0;
|
||||
co_return 0;
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -1468,17 +1427,14 @@ int CMapReader::ParseTerrain()
|
|||
// load textures
|
||||
std::vector<std::string> textureNames;
|
||||
getTerrainProperty(m_MapData, "textureNames", textureNames);
|
||||
num_terrain_tex = textureNames.size();
|
||||
|
||||
while (cur_terrain_tex < num_terrain_tex)
|
||||
for (const std::string& textureName : textureNames)
|
||||
{
|
||||
if (CTerrainTextureManager::IsInitialised())
|
||||
{
|
||||
CTerrainTextureEntry* texentry = g_TexMan.FindTexture(textureNames[cur_terrain_tex]);
|
||||
CTerrainTextureEntry* texentry = g_TexMan.FindTexture(textureName);
|
||||
m_TerrainTextures.push_back(texentry);
|
||||
}
|
||||
|
||||
cur_terrain_tex++;
|
||||
}
|
||||
|
||||
// build tile data
|
||||
|
|
@ -1514,12 +1470,10 @@ int CMapReader::ParseTerrain()
|
|||
}
|
||||
}
|
||||
|
||||
// reset generator state
|
||||
cur_terrain_tex = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct CMapReader::ParseEntitiesState
|
||||
struct ParseEntitiesState
|
||||
{
|
||||
ScriptRequest rq;
|
||||
CmpPtr<ICmpPlayerManager> cmpPlayerManager;
|
||||
|
|
@ -1530,28 +1484,25 @@ struct CMapReader::ParseEntitiesState
|
|||
: rq(sim.GetScriptInterface()), cmpPlayerManager(sim, SYSTEM_ENTITY) {}
|
||||
};
|
||||
|
||||
int CMapReader::StartParseEntities()
|
||||
PS::Loader::Task CMapReader::ParseEntities()
|
||||
{
|
||||
PROFILE2("StartParseEntities");
|
||||
PROFILE2("ParseEntities");
|
||||
|
||||
m_ParseEntitiesState = std::make_unique<ParseEntitiesState>(*pSimulation2);
|
||||
if (!Script::GetProperty(m_ParseEntitiesState->rq, m_MapData, "entities", m_ParseEntitiesState->entities))
|
||||
LOGWARNING("CMapReader::ParseEntities() failed to get 'entities' property");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CMapReader::PollParseEntities()
|
||||
{
|
||||
PROFILE2("PollParseEntities");
|
||||
|
||||
ParseEntitiesState& state{*m_ParseEntitiesState};
|
||||
CSimulation2& sim{*pSimulation2};
|
||||
const size_t numberOfEntitesToLoadPerCall{100};
|
||||
for (size_t iteration{0}; iteration < numberOfEntitesToLoadPerCall && state.currentEntityIndex < state.entities.size(); ++iteration, ++state.currentEntityIndex)
|
||||
ScriptRequest rq{sim.GetScriptInterface()};
|
||||
CmpPtr<ICmpPlayerManager> cmpPlayerManager{sim, SYSTEM_ENTITY};
|
||||
|
||||
std::vector<Entity> entities;
|
||||
if (!Script::GetProperty(rq, m_MapData, "entities", entities))
|
||||
LOGWARNING("CMapReader::ParseEntities() failed to get 'entities' property");
|
||||
|
||||
for (std::size_t index{0}; index != entities.size(); ++index)
|
||||
{
|
||||
const Entity& currEnt{state.entities[state.currentEntityIndex]};
|
||||
co_yield Clamp<int>(index * 80 / entities.size(), 20, 100);
|
||||
|
||||
const Entity& currEnt{entities[index]};
|
||||
// Get current entity struct
|
||||
entity_id_t player = state.cmpPlayerManager->GetPlayerByID(currEnt.playerID);
|
||||
entity_id_t player = cmpPlayerManager->GetPlayerByID(currEnt.playerID);
|
||||
CmpPtr<ICmpPlayer> cmpPlayer(sim, player);
|
||||
// Don't add entities for removed players.
|
||||
if (cmpPlayer && cmpPlayer->IsRemoved())
|
||||
|
|
@ -1591,12 +1542,7 @@ int CMapReader::PollParseEntities()
|
|||
}
|
||||
}
|
||||
|
||||
if (state.currentEntityIndex < state.entities.size())
|
||||
return Clamp<int>(state.currentEntityIndex * 80 / state.entities.size(), 20, 100);
|
||||
|
||||
m_ParseEntitiesState.reset();
|
||||
|
||||
return 0;
|
||||
co_return 0;
|
||||
}
|
||||
|
||||
int CMapReader::ParseEnvironment()
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
#include "ps/CStr.h"
|
||||
#include "ps/Errors.h"
|
||||
#include "ps/FileIo.h"
|
||||
#include "ps/Loader.h"
|
||||
#include "simulation2/system/Entity.h"
|
||||
|
||||
#include <cstddef>
|
||||
|
|
@ -82,7 +83,7 @@ private:
|
|||
int LoadMapSettings();
|
||||
|
||||
// UnpackTerrain: unpack the terrain from the input stream
|
||||
int UnpackTerrain();
|
||||
PS::Loader::Task UnpackTerrain();
|
||||
// UnpackCinema: unpack the cinematic tracks from the input stream
|
||||
int UnpackCinema();
|
||||
|
||||
|
|
@ -94,21 +95,19 @@ private:
|
|||
int ReadXML();
|
||||
|
||||
// read entity data from the XML file
|
||||
int ReadXMLEntities();
|
||||
PS::Loader::Task ReadXMLEntities();
|
||||
|
||||
// Copy random map settings over to sim
|
||||
int LoadRMSettings();
|
||||
|
||||
// Generate random map
|
||||
int StartMapGeneration(const CStrW& scriptFile);
|
||||
int PollMapGeneration();
|
||||
PS::Loader::Task RunMapGeneration(const CStrW& scriptFile);
|
||||
|
||||
// Parse script data into terrain
|
||||
int ParseTerrain();
|
||||
|
||||
// Parse script data into entities
|
||||
int StartParseEntities();
|
||||
int PollParseEntities();
|
||||
PS::Loader::Task ParseEntities();
|
||||
|
||||
// Parse script data into environment
|
||||
int ParseEnvironment();
|
||||
|
|
@ -134,12 +133,6 @@ private:
|
|||
JS::PersistentRootedValue m_ScriptSettings;
|
||||
JS::PersistentRootedValue m_MapData;
|
||||
|
||||
struct GeneratorState;
|
||||
std::unique_ptr<GeneratorState> m_GeneratorState;
|
||||
|
||||
struct ParseEntitiesState;
|
||||
std::unique_ptr<ParseEntitiesState> m_ParseEntitiesState;
|
||||
|
||||
CFileUnpacker unpacker;
|
||||
CTerrain* pTerrain;
|
||||
WaterManager* pWaterMan;
|
||||
|
|
@ -159,11 +152,6 @@ private:
|
|||
entity_id_t m_StartingCameraTarget;
|
||||
CVector3D m_StartingCamera;
|
||||
|
||||
// UnpackTerrain generator state
|
||||
// It's important to initialize it to 0 - resets generator state
|
||||
size_t cur_terrain_tex{0};
|
||||
size_t num_terrain_tex;
|
||||
|
||||
std::unique_ptr<CXMLReader> m_XmlReader;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -122,41 +122,24 @@ static Status AddTextureCallback(const VfsPath& pathname, const CFileInfo&, cons
|
|||
return INFO::OK;
|
||||
}
|
||||
|
||||
struct CTerrainTextureManager::LoadTexturesState
|
||||
PS::Loader::Task CTerrainTextureManager::LoadTerrainTextures()
|
||||
{
|
||||
vfs::ForEachFileContext context;
|
||||
AddTextureCallbackData data;
|
||||
|
||||
LoadTexturesState(const VfsPath& startPath, CTerrainTextureManager* self)
|
||||
: context{startPath}, data{self, std::make_shared<CTerrainProperties>(CTerrainPropertiesPtr())} {}
|
||||
};
|
||||
|
||||
int CTerrainTextureManager::StartTerrainTextures()
|
||||
{
|
||||
m_LoadTexturesState = std::make_unique<LoadTexturesState>(VfsPath{L"art/terrains/"}, this);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CTerrainTextureManager::PollTerrainTextures()
|
||||
{
|
||||
LoadTexturesState& state{*m_LoadTexturesState};
|
||||
const size_t numberOfDirectoriesToLoadPerCall{10};
|
||||
for (size_t iteration{0}; !state.context.empty() && iteration < numberOfDirectoriesToLoadPerCall; ++iteration)
|
||||
vfs::ForEachFileContext context{VfsPath{L"art/terrains/"}};
|
||||
AddTextureCallbackData data{this, std::make_shared<CTerrainProperties>(CTerrainPropertiesPtr())};
|
||||
while (true)
|
||||
{
|
||||
vfs::ForEachFileNext(state.context, g_VFS, AddTextureCallback, (uintptr_t)&state.data, L"*.xml", vfs::DIR_RECURSIVE, AddTextureDirCallback, (uintptr_t)&state.data);
|
||||
}
|
||||
vfs::ForEachFileNext(context, g_VFS, AddTextureCallback,
|
||||
reinterpret_cast<uintptr_t>(&data), L"*.xml", vfs::DIR_RECURSIVE,
|
||||
AddTextureDirCallback, reinterpret_cast<uintptr_t>(&data));
|
||||
|
||||
if (context.empty())
|
||||
co_return 0;
|
||||
|
||||
if (!state.context.empty())
|
||||
{
|
||||
// We don't know exact number so just using a rough approximation of the
|
||||
// current number.
|
||||
const size_t totalApproximateAmountOfTextures{1000};
|
||||
return Clamp<int>(m_TextureEntries.size() * 90 / totalApproximateAmountOfTextures, 10, 100);
|
||||
co_yield Clamp<int>(m_TextureEntries.size() * 90 / totalApproximateAmountOfTextures, 10, 100);
|
||||
}
|
||||
|
||||
m_LoadTexturesState.reset();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
CTerrainGroup* CTerrainTextureManager::FindGroup(const CStr& name)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
#include "lib/file/vfs/vfs_path.h"
|
||||
#include "lib/types.h"
|
||||
#include "ps/CStr.h"
|
||||
#include "ps/Loader.h"
|
||||
#include "ps/Singleton.h"
|
||||
#include "renderer/backend/ITexture.h"
|
||||
|
||||
|
|
@ -99,8 +100,7 @@ public:
|
|||
|
||||
// Find all XML's in the directory (with subdirs) and try to load them as
|
||||
// terrain XML's
|
||||
int StartTerrainTextures();
|
||||
int PollTerrainTextures();
|
||||
PS::Loader::Task LoadTerrainTextures();
|
||||
|
||||
void UnloadTerrainTextures();
|
||||
|
||||
|
|
@ -137,9 +137,6 @@ private:
|
|||
// A way to separate file loading and uploading to GPU to not stall uploading.
|
||||
// Once we get a properly threaded loading we might optimize that.
|
||||
std::vector<CTerrainTextureManager::TerrainAlphaMap::iterator> m_AlphaMapsToUpload;
|
||||
|
||||
struct LoadTexturesState;
|
||||
std::unique_ptr<LoadTexturesState> m_LoadTexturesState;
|
||||
};
|
||||
|
||||
// access to sole CTerrainTextureManager object
|
||||
|
|
|
|||
|
|
@ -282,22 +282,23 @@ void CGame::RegisterInit(const JS::HandleValue attribs, const std::string& saved
|
|||
m_World->RegisterInit(mapFile, scriptInterface.GetContext(), settings, m_PlayerID);
|
||||
}
|
||||
if (m_GameView)
|
||||
PS::Loader::Register([&waterManager = g_Renderer.GetSceneRenderer().GetWaterManager()]
|
||||
PS::Loader::Register([]() -> PS::Loader::Task
|
||||
{
|
||||
return waterManager.LoadWaterTextures();
|
||||
co_return g_Renderer.GetSceneRenderer().GetWaterManager().LoadWaterTextures();
|
||||
}, L"LoadWaterTextures", 80);
|
||||
|
||||
if (m_IsSavedGame)
|
||||
PS::Loader::Register([this, savedState]
|
||||
PS::Loader::Register(std::bind_front(
|
||||
[](CGame* game, const std::string& state) -> PS::Loader::Task
|
||||
{
|
||||
return LoadInitialState(savedState);
|
||||
}, L"Loading game", 1000);
|
||||
co_return game->LoadInitialState(state);
|
||||
}, this, savedState), L"Loading game", 1000);
|
||||
|
||||
if (m_IsVisualReplay)
|
||||
PS::Loader::Register([this]
|
||||
PS::Loader::Register(std::bind_front([](CGame* game) -> PS::Loader::Task
|
||||
{
|
||||
return LoadVisualReplayData();
|
||||
}, L"Loading visual replay data", 1000);
|
||||
co_return game->LoadVisualReplayData();
|
||||
}, this), L"Loading visual replay data", 1000);
|
||||
|
||||
PS::Loader::EndRegistering();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,11 +24,11 @@
|
|||
|
||||
#include "lib/code_annotation.h"
|
||||
#include "lib/secure_crt.h"
|
||||
#include "lib/timer.h"
|
||||
#include "lib/utf8.h"
|
||||
|
||||
#include <deque>
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
|
|
@ -84,13 +84,7 @@ struct LoadRequest
|
|||
|
||||
using LoadRequests = std::deque<LoadRequest>;
|
||||
LoadRequests load_requests;
|
||||
|
||||
// Returns true if the return code indicates that the `LoadRequest` didn't
|
||||
// finish and should be reinvoked in the next frame.
|
||||
bool WasInterrupted(const int ret)
|
||||
{
|
||||
return 0 < ret && ret <= 100;
|
||||
}
|
||||
std::optional<PS::Loader::Task> currentTask;
|
||||
} // anonymous namespace
|
||||
|
||||
// call before starting to register load requests.
|
||||
|
|
@ -118,7 +112,7 @@ void Register(LoadFunc func, std::wstring description, int estimatedDurationMs)
|
|||
{
|
||||
ENSURE(state == REGISTERING); // must be called between PS::Loader::(Begin|End)Register
|
||||
|
||||
load_requests.emplace_back(std::move(func), std::move(description), estimatedDurationMs);
|
||||
load_requests.push_back({std::move(func), std::move(description), estimatedDurationMs});
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -208,8 +202,18 @@ ProgressiveLoadResult ProgressiveLoad(double time_budget)
|
|||
|
||||
// call this task's function and bill elapsed time.
|
||||
const double t0 = timer_Time();
|
||||
const int status = lr.func();
|
||||
const bool timed_out = WasInterrupted(status);
|
||||
if (!currentTask.has_value())
|
||||
currentTask.emplace(lr.func());
|
||||
try
|
||||
{
|
||||
currentTask->Step(time_left);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
currentTask.reset();
|
||||
throw;
|
||||
}
|
||||
const bool timed_out = !currentTask->IsDone();
|
||||
const double elapsed_time = timer_Time() - t0;
|
||||
time_left -= elapsed_time;
|
||||
task_elapsed_time += elapsed_time;
|
||||
|
|
@ -232,7 +236,7 @@ ProgressiveLoadResult ProgressiveLoad(double time_budget)
|
|||
// note: monotonicity is guaranteed since we never add more than
|
||||
// its estimated_duration_ms.
|
||||
if(timed_out)
|
||||
current_estimate += estimated_duration * status/100.0;
|
||||
current_estimate += estimated_duration * currentTask->GetProgress() / 100.0;
|
||||
|
||||
progress = current_estimate / total_estimated_duration;
|
||||
}
|
||||
|
|
@ -248,19 +252,16 @@ ProgressiveLoadResult ProgressiveLoad(double time_budget)
|
|||
// the next iteration of the main loop.
|
||||
// rationale: bail immediately instead of remembering the first
|
||||
// error that came up so we can report all errors that happen.
|
||||
else if(status < 0)
|
||||
{
|
||||
ret.status = static_cast<Status>(status);
|
||||
goto done;
|
||||
}
|
||||
else if(currentTask->Get() < 0)
|
||||
ret.status = static_cast<Status>(currentTask->Get());
|
||||
// .. function called PS::Loader::Cancel; abort. return OK since this is an
|
||||
// intentional cancellation, not an error.
|
||||
else if(state != LOADING)
|
||||
{
|
||||
ret.status = INFO::OK;
|
||||
goto done;
|
||||
}
|
||||
// .. succeeded; continue and process next queued task.
|
||||
|
||||
currentTask.reset();
|
||||
goto done;
|
||||
}
|
||||
|
||||
// queue is empty, we just finished.
|
||||
|
|
|
|||
|
|
@ -23,9 +23,13 @@
|
|||
|
||||
#include "lib/debug.h"
|
||||
#include "lib/status.h"
|
||||
#include "lib/timer.h"
|
||||
|
||||
#include <coroutine>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace PS::Loader
|
||||
{
|
||||
|
|
@ -104,19 +108,122 @@ Then in the main loop, call PS::Loader::ProgressiveLoad().
|
|||
// or issuing via console while already loading.
|
||||
void BeginRegistering();
|
||||
|
||||
/**
|
||||
* Coroutine which performs the actual work.
|
||||
*
|
||||
* `co_yield ...` can be used to yield the current progress. Iff the timeout is
|
||||
* reached, the coroutine suspends.
|
||||
* `co_await std::suspend_always{}` is usefull to force a suspention. e.g. When
|
||||
* no progress can be made such as when the work is done on a different
|
||||
* thread.
|
||||
* `co_return 0` notifies the loader that the task is fineshed without an
|
||||
* error.
|
||||
* `co_return ...` when the returned value is negative the loader interprets
|
||||
* that as a task failure. `PS::Loader::ProgressiveLoad` will abort
|
||||
* immediately and forward the error code.
|
||||
*/
|
||||
class Task
|
||||
{
|
||||
public:
|
||||
class promise_type
|
||||
{
|
||||
class SuspendIf
|
||||
{
|
||||
public:
|
||||
explicit SuspendIf(const bool suspend) :
|
||||
m_Suspend{suspend}
|
||||
{}
|
||||
|
||||
// callback function of a task; performs the actual work.
|
||||
//
|
||||
// return semantics:
|
||||
// - if the entire task was successfully completed, return 0;
|
||||
// it will then be de-queued.
|
||||
// - if the work can be split into smaller subtasks, process those until
|
||||
// <time_left> is reached or exceeded and then return an estimate
|
||||
// of progress in percent (<= 100, otherwise it's a warning;
|
||||
// != 0, or it's treated as "finished")
|
||||
// - on failure, return a negative error code or 'warning' (see above);
|
||||
// PS::Loader::ProgressiveLoad will abort immediately and return that.
|
||||
using LoadFunc = std::function<int()>;
|
||||
bool await_ready() const noexcept
|
||||
{
|
||||
return !m_Suspend;
|
||||
}
|
||||
void await_suspend(std::coroutine_handle<promise_type>) const noexcept
|
||||
{}
|
||||
void await_resume() const noexcept
|
||||
{}
|
||||
|
||||
private:
|
||||
bool m_Suspend;
|
||||
};
|
||||
public:
|
||||
Task get_return_object() noexcept
|
||||
{
|
||||
return Task{std::coroutine_handle<promise_type>::from_promise(*this)};
|
||||
}
|
||||
std::suspend_always initial_suspend() const noexcept { return {}; }
|
||||
std::suspend_always final_suspend() const noexcept { return {}; }
|
||||
void return_value(const int result) noexcept
|
||||
{
|
||||
m_Result = result;
|
||||
}
|
||||
void unhandled_exception() noexcept
|
||||
{
|
||||
m_Exception = std::current_exception();
|
||||
}
|
||||
|
||||
SuspendIf yield_value(const int progress) noexcept
|
||||
{
|
||||
m_Progress = progress;
|
||||
return SuspendIf{m_StepEnd < timer_Time()};
|
||||
}
|
||||
|
||||
int m_Progress{0};
|
||||
double m_StepEnd;
|
||||
int m_Result{0};
|
||||
std::exception_ptr m_Exception;
|
||||
};
|
||||
|
||||
Task(const Task&) = delete;
|
||||
Task& operator =(const Task&) = delete;
|
||||
Task(Task&& other) noexcept :
|
||||
m_Handle{std::exchange(other.m_Handle, {})}
|
||||
{}
|
||||
Task& operator =(Task&& other) noexcept
|
||||
{
|
||||
m_Handle = std::exchange(other.m_Handle, {});
|
||||
return *this;
|
||||
}
|
||||
|
||||
~Task()
|
||||
{
|
||||
if (m_Handle)
|
||||
m_Handle.destroy();
|
||||
}
|
||||
|
||||
[[nodiscard]] double GetProgress() const noexcept
|
||||
{
|
||||
return m_Handle.promise().m_Progress;
|
||||
}
|
||||
|
||||
void Step(const double timeBudget)
|
||||
{
|
||||
m_Handle.promise().m_StepEnd = timeBudget + timer_Time();
|
||||
m_Handle.resume();
|
||||
std::exception_ptr exception{std::exchange(m_Handle.promise().m_Exception, {})};
|
||||
if (exception)
|
||||
std::rethrow_exception(std::move(exception));
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsDone() const noexcept
|
||||
{
|
||||
return m_Handle.done();
|
||||
}
|
||||
|
||||
[[nodiscard]] int Get() const noexcept
|
||||
{
|
||||
return m_Handle.promise().m_Result;
|
||||
}
|
||||
|
||||
private:
|
||||
explicit Task(std::coroutine_handle<promise_type> h) noexcept :
|
||||
m_Handle{std::move(h)}
|
||||
{}
|
||||
|
||||
std::coroutine_handle<promise_type> m_Handle;
|
||||
};
|
||||
|
||||
using LoadFunc = std::function<PS::Loader::Task()>;
|
||||
|
||||
// register a task (later processed in FIFO order).
|
||||
// <func>: function that will perform the actual work; see LoadFunc.
|
||||
|
|
@ -169,22 +276,6 @@ ProgressiveLoadResult ProgressiveLoad(double time_budget);
|
|||
// returns 0 on success or a negative error code.
|
||||
Status NonprogressiveLoad();
|
||||
|
||||
|
||||
// boilerplate check-if-timed-out and return-progress-percent code.
|
||||
// completed_jobs and total_jobs are ints and must be updated by caller.
|
||||
// assumes presence of a local variable (double)<end_time>
|
||||
// (as returned by timer_Time()) that indicates the time at which to abort.
|
||||
#define LDR_CHECK_TIMEOUT(completed_jobs, total_jobs)\
|
||||
if(timer_Time() > end_time)\
|
||||
{\
|
||||
size_t progress_percent = ((completed_jobs)*100 / (total_jobs));\
|
||||
/* 0 means "finished", so don't return that! */\
|
||||
if(progress_percent == 0)\
|
||||
progress_percent = 1;\
|
||||
ENSURE(0 < progress_percent && progress_percent <= 100);\
|
||||
return (int)progress_percent;\
|
||||
}
|
||||
|
||||
} // namespace PS::Loader
|
||||
|
||||
#endif // #ifndef INCLUDED_LOADER
|
||||
|
|
|
|||
|
|
@ -86,10 +86,10 @@ void CWorld::RegisterInit(const CStrW& mapFile, const ScriptContext& cx, JS::Han
|
|||
m_Game.GetSimulation2(), &m_Game.GetSimulation2()->GetSimContext(), playerID,
|
||||
false);
|
||||
// fails immediately, or registers for delay loading
|
||||
PS::Loader::Register([this]
|
||||
PS::Loader::Register(std::bind_front([](CWorld* world) -> PS::Loader::Task
|
||||
{
|
||||
return DeleteMapReader();
|
||||
}, L"CWorld::DeleteMapReader", 5);
|
||||
co_return world->DeleteMapReader();
|
||||
}, this), L"CWorld::DeleteMapReader", 5);
|
||||
}
|
||||
catch (PSERROR_File& err)
|
||||
{
|
||||
|
|
@ -111,10 +111,10 @@ void CWorld::RegisterInitRMS(const CStrW& scriptFile, const ScriptContext& cx, J
|
|||
pTriggerManager, CRenderer::IsInitialised() ? &g_Renderer.GetPostprocManager() : nullptr,
|
||||
m_Game.GetSimulation2(), playerID);
|
||||
// registers for delay loading
|
||||
PS::Loader::Register([this]
|
||||
PS::Loader::Register(std::bind_front([](CWorld* world) -> PS::Loader::Task
|
||||
{
|
||||
return DeleteMapReader();
|
||||
}, L"CWorld::DeleteMapReader", 5);
|
||||
co_return world->DeleteMapReader();
|
||||
}, this), L"CWorld::DeleteMapReader", 5);
|
||||
}
|
||||
|
||||
int CWorld::DeleteMapReader()
|
||||
|
|
|
|||
104
source/ps/tests/test_Loader.h
Normal file
104
source/ps/tests/test_Loader.h
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/* Copyright (C) 2025 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. 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.
|
||||
*
|
||||
* 0 A.D. 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 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "lib/self_test.h"
|
||||
|
||||
#include "ps/Loader.h"
|
||||
|
||||
class TestLoader : public CxxTest::TestSuite
|
||||
{
|
||||
public:
|
||||
void test_PausedYield()
|
||||
{
|
||||
int position{0};
|
||||
PS::Loader::Task task{[](int& p) -> PS::Loader::Task
|
||||
{
|
||||
p = 1;
|
||||
co_yield 50;
|
||||
p = 2;
|
||||
co_return 0;
|
||||
}(position)};
|
||||
|
||||
TS_ASSERT_EQUALS(task.GetProgress(), 0);
|
||||
TS_ASSERT(!task.IsDone());
|
||||
TS_ASSERT_EQUALS(position, 0);
|
||||
|
||||
task.Step(-1);
|
||||
|
||||
TS_ASSERT_EQUALS(task.GetProgress(), 50);
|
||||
TS_ASSERT(!task.IsDone());
|
||||
TS_ASSERT_EQUALS(position, 1);
|
||||
|
||||
task.Step(-1);
|
||||
|
||||
TS_ASSERT_EQUALS(task.GetProgress(), 50);
|
||||
TS_ASSERT(task.IsDone());
|
||||
TS_ASSERT_EQUALS(position, 2);
|
||||
}
|
||||
|
||||
void test_UnpausedYield()
|
||||
{
|
||||
int position{0};
|
||||
PS::Loader::Task task{[](int& p) -> PS::Loader::Task
|
||||
{
|
||||
p = 1;
|
||||
co_yield 50;
|
||||
p = 2;
|
||||
co_return 0;
|
||||
}(position)};
|
||||
|
||||
TS_ASSERT_EQUALS(task.GetProgress(), 0);
|
||||
TS_ASSERT(!task.IsDone());
|
||||
TS_ASSERT_EQUALS(position, 0);
|
||||
|
||||
task.Step(10);
|
||||
|
||||
TS_ASSERT_EQUALS(task.GetProgress(), 50);
|
||||
TS_ASSERT(task.IsDone());
|
||||
TS_ASSERT_EQUALS(position, 2);
|
||||
}
|
||||
|
||||
void test_ForceAwait()
|
||||
{
|
||||
int position{0};
|
||||
PS::Loader::Task task{[](int& p) -> PS::Loader::Task
|
||||
{
|
||||
p = 1;
|
||||
co_yield 50;
|
||||
p = 2;
|
||||
co_await std::suspend_always{};
|
||||
p = 3;
|
||||
co_return 0;
|
||||
}(position)};
|
||||
|
||||
TS_ASSERT_EQUALS(task.GetProgress(), 0);
|
||||
TS_ASSERT(!task.IsDone());
|
||||
TS_ASSERT_EQUALS(position, 0);
|
||||
|
||||
task.Step(10);
|
||||
|
||||
TS_ASSERT_EQUALS(task.GetProgress(), 50);
|
||||
TS_ASSERT(!task.IsDone());
|
||||
TS_ASSERT_EQUALS(position, 2);
|
||||
|
||||
task.Step(-1);
|
||||
|
||||
TS_ASSERT_EQUALS(task.GetProgress(), 50);
|
||||
TS_ASSERT(task.IsDone());
|
||||
TS_ASSERT_EQUALS(position, 3);
|
||||
}
|
||||
};
|
||||
|
|
@ -132,7 +132,7 @@ public:
|
|||
return static_cast<CSimulation2Impl*>(param)->ReloadChangedFile(path);
|
||||
}
|
||||
|
||||
int ProgressiveLoad();
|
||||
PS::Loader::Task ProgressiveLoad();
|
||||
void Update(int turnLength, const std::vector<SimulationCommand>& commands);
|
||||
static void UpdateComponents(CSimContext& simContext, fixed turnLengthFixed, const std::vector<SimulationCommand>& commands);
|
||||
void Interpolate(float simFrameLength, float frameOffset, float realFrameLength);
|
||||
|
|
@ -267,15 +267,9 @@ Status CSimulation2Impl::ReloadChangedFile(const VfsPath& path)
|
|||
return INFO::OK;
|
||||
}
|
||||
|
||||
int CSimulation2Impl::ProgressiveLoad()
|
||||
PS::Loader::Task CSimulation2Impl::ProgressiveLoad()
|
||||
{
|
||||
// yield after this time is reached. balances increased progress bar
|
||||
// smoothness vs. slowing down loading.
|
||||
const double end_time = timer_Time() + 200e-3;
|
||||
|
||||
int ret;
|
||||
|
||||
do
|
||||
while (true)
|
||||
{
|
||||
bool progressed = false;
|
||||
int total = 0;
|
||||
|
|
@ -286,13 +280,10 @@ int CSimulation2Impl::ProgressiveLoad()
|
|||
m_ComponentManager.BroadcastMessage(msg);
|
||||
|
||||
if (!progressed || total == 0)
|
||||
return 0; // we have nothing left to load
|
||||
co_return 0; // we have nothing left to load
|
||||
|
||||
ret = Clamp(100*progress / total, 1, 100);
|
||||
co_yield Clamp(100*progress / total, 1, 100);
|
||||
}
|
||||
while (timer_Time() < end_time);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void CSimulation2Impl::DumpSerializationTestState(SerializationTestState& state, const OsPath& path, const OsPath::String& suffix)
|
||||
|
|
@ -849,7 +840,7 @@ void CSimulation2::LoadMapSettings()
|
|||
m->LoadTriggerScripts(m->m_ComponentManager, m->m_MapSettings, &m->m_LoadedScripts);
|
||||
}
|
||||
|
||||
int CSimulation2::ProgressiveLoad()
|
||||
PS::Loader::Task CSimulation2::ProgressiveLoad()
|
||||
{
|
||||
return m->ProgressiveLoad();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
#include "lib/code_annotation.h"
|
||||
#include "lib/file/vfs/vfs_path.h"
|
||||
#include "lib/status.h"
|
||||
#include "ps/Loader.h"
|
||||
#include "simulation2/system/DebugOptions.h"
|
||||
#include "simulation2/system/Entity.h"
|
||||
|
||||
|
|
@ -140,7 +141,7 @@ public:
|
|||
/**
|
||||
* RegMemFun incremental loader function.
|
||||
*/
|
||||
int ProgressiveLoad();
|
||||
PS::Loader::Task ProgressiveLoad();
|
||||
|
||||
/**
|
||||
* Reload any scripts that were loaded from the given filename.
|
||||
|
|
|
|||
Loading…
Reference in a new issue