1
0
forked from 0ad/0ad

Random maps generated in their own thread, loading GUI is updated with progress.

Fixes some bugs in game loader error handling.

This was SVN commit r9220.
This commit is contained in:
historic_bruno 2011-04-10 05:31:18 +00:00
parent 469d0fe5c5
commit 942a45372c
11 changed files with 356 additions and 90 deletions

View File

@ -17,7 +17,7 @@ function cancelOnError(msg)
{ {
Engine.PushGuiPage("page_msgbox.xml", { Engine.PushGuiPage("page_msgbox.xml", {
width: 400, width: 400,
height: 300, height: 200,
message: '[font="serif-bold-18"]' + msg + '[/font]', message: '[font="serif-bold-18"]' + msg + '[/font]',
title: "Loading Aborted", title: "Loading Aborted",
mode: 2 mode: 2

View File

@ -179,6 +179,8 @@ for (var i=1; i<=numPlayers; i++)
} }
} }
RMS.SetProgress(5);
// create lakes // create lakes
log("Creating lakes..."); log("Creating lakes...");
placer = new ClumpPlacer(mapArea * 0.009, 0.8, 0.1, 0); placer = new ClumpPlacer(mapArea * 0.009, 0.8, 0.1, 0);
@ -194,6 +196,8 @@ createAreas(
round(1.3 * numPlayers) round(1.3 * numPlayers)
); );
RMS.SetProgress(22);
// create bumps // create bumps
log("Creating bumps..."); log("Creating bumps...");
placer = new ClumpPlacer(10, 0.3, 0.06, 0); placer = new ClumpPlacer(10, 0.3, 0.06, 0);
@ -205,6 +209,8 @@ createAreas(
mapArea/1000 mapArea/1000
); );
RMS.SetProgress(25);
// create hills // create hills
log("Creating hills..."); log("Creating hills...");
placer = new ClumpPlacer(mapArea * 0.0015, 0.2, 0.1, 0); placer = new ClumpPlacer(mapArea * 0.0015, 0.2, 0.1, 0);
@ -220,6 +226,8 @@ createAreas(
2 * numPlayers 2 * numPlayers
); );
RMS.SetProgress(30);
// create forests // create forests
log("Creating forests..."); log("Creating forests...");
placer = new ClumpPlacer(mapArea * 0.002, 0.1, 0.1, 0); placer = new ClumpPlacer(mapArea * 0.002, 0.1, 0.1, 0);
@ -234,6 +242,8 @@ createAreas(
6 * numPlayers 6 * numPlayers
); );
RMS.SetProgress(53);
// create dirt patches // create dirt patches
log("Creating dirt patches..."); log("Creating dirt patches...");
var sizes = [8,14,20]; var sizes = [8,14,20];
@ -293,6 +303,8 @@ createObjectGroups(group, 0,
// 2 * numPlayers, 50 // 2 * numPlayers, 50
// ); // );
RMS.SetProgress(60);
// create small decorative rocks // create small decorative rocks
log("Creating large decorative rocks..."); log("Creating large decorative rocks...");
group = new SimpleGroup( group = new SimpleGroup(
@ -360,6 +372,8 @@ createObjectGroups(group, undefined,
mapArea/90 mapArea/90
); );
RMS.SetProgress(80);
// create large grass tufts // create large grass tufts
log("Creating large grass tufts..."); log("Creating large grass tufts...");
group = new SimpleGroup( group = new SimpleGroup(
@ -370,6 +384,8 @@ createObjectGroups(group, undefined,
mapArea/900 mapArea/900
); );
RMS.SetProgress(87);
// create bushes // create bushes
log("Creating bushes..."); log("Creating bushes...");
group = new SimpleGroup( group = new SimpleGroup(
@ -386,7 +402,7 @@ group = new SimpleGroup(
[new SimpleObject(aReeds, 5,10, 0,1.5, -PI/8,PI/8)] [new SimpleObject(aReeds, 5,10, 0,1.5, -PI/8,PI/8)]
); );
createObjectGroups(group, undefined, createObjectGroups(group, undefined,
[new BorderTileClassConstraint(clWater, 3, 0), new StayInTileClassConstraint(clWater, 1)], [new BorderTileClassConstraint(clWater, 3, 0), stayClasses(clWater, 1)],
10 * numPlayers, 100 10 * numPlayers, 100
); );

View File

@ -137,6 +137,8 @@ for (var i=0; i < numPlayers; i++)
createObjectGroup(group, 0, avoidClasses(clBaseResource,1)); createObjectGroup(group, 0, avoidClasses(clBaseResource,1));
} }
RMS.SetProgress(5);
// create patches // create patches
log("Creating sand patches..."); log("Creating sand patches...");
var placer = new ClumpPlacer(30, 0.2, 0.1, 0); var placer = new ClumpPlacer(30, 0.2, 0.1, 0);
@ -146,6 +148,8 @@ createAreas(placer, [painter, paintClass(clPatch)],
mapArea/600 mapArea/600
); );
RMS.SetProgress(24);
log("Creating dirt patches..."); log("Creating dirt patches...");
placer = new ClumpPlacer(10, 0.2, 0.1, 0); placer = new ClumpPlacer(10, 0.2, 0.1, 0);
painter = new TerrainPainter([tSand, tDirt]); painter = new TerrainPainter([tSand, tDirt]);
@ -154,6 +158,8 @@ createAreas(placer, [painter, paintClass(clPatch)],
mapArea/600 mapArea/600
); );
RMS.SetProgress(32);
// create the oasis (roughly 4% of map area) // create the oasis (roughly 4% of map area)
log("Creating oasis..."); log("Creating oasis...");
placer = new ClumpPlacer(mapArea * 0.04, 0.6, 0.1, 0, mapSize/2, mapSize/2); placer = new ClumpPlacer(mapArea * 0.04, 0.6, 0.1, 0, mapSize/2, mapSize/2);
@ -161,6 +167,8 @@ painter = new LayeredPainter([[tSand, pForest], tShore, tWaterDeep], [6,1]);
elevationPainter = new SmoothElevationPainter(ELEVATION_MODIFY, -11, 5); elevationPainter = new SmoothElevationPainter(ELEVATION_MODIFY, -11, 5);
createArea(placer, [painter, elevationPainter, paintClass(clForest)], null); createArea(placer, [painter, elevationPainter, paintClass(clForest)], null);
RMS.SetProgress(51);
// create hills // create hills
log("Creating level 1 hills..."); log("Creating level 1 hills...");
placer = new ClumpPlacer(150, 0.25, 0.1, 0.3); placer = new ClumpPlacer(150, 0.25, 0.1, 0.3);
@ -174,6 +182,8 @@ createAreas(placer, [terrainPainter, elevationPainter, paintClass(clHill1)],
mapArea/3800, 100 mapArea/3800, 100
); );
RMS.SetProgress(70);
log("Creating small level 1 hills..."); log("Creating small level 1 hills...");
placer = new ClumpPlacer(60, 0.25, 0.1, 0.3); placer = new ClumpPlacer(60, 0.25, 0.1, 0.3);
terrainPainter = new LayeredPainter( terrainPainter = new LayeredPainter(
@ -186,6 +196,8 @@ createAreas(placer, [terrainPainter, elevationPainter, paintClass(clHill1)],
mapArea/2800, 100 mapArea/2800, 100
); );
RMS.SetProgress(81);
log("Creating level 2 hills..."); log("Creating level 2 hills...");
placer = new ClumpPlacer(60, 0.2, 0.1, 0.9); placer = new ClumpPlacer(60, 0.2, 0.1, 0.9);
terrainPainter = new LayeredPainter( terrainPainter = new LayeredPainter(
@ -194,10 +206,12 @@ terrainPainter = new LayeredPainter(
); );
elevationPainter = new SmoothElevationPainter(ELEVATION_MODIFY, 16, 1); elevationPainter = new SmoothElevationPainter(ELEVATION_MODIFY, 16, 1);
createAreas(placer, [terrainPainter, elevationPainter, paintClass(clHill2)], createAreas(placer, [terrainPainter, elevationPainter, paintClass(clHill2)],
[avoidClasses(clHill2, 1), new StayInTileClassConstraint(clHill1, 0)], [avoidClasses(clHill2, 1), stayClasses(clHill1, 0)],
mapArea/2800, 200 mapArea/2800, 200
); );
RMS.SetProgress(91);
log("Creating level 3 hills..."); log("Creating level 3 hills...");
placer = new ClumpPlacer(25, 0.2, 0.1, 0.9); placer = new ClumpPlacer(25, 0.2, 0.1, 0.9);
terrainPainter = new LayeredPainter( terrainPainter = new LayeredPainter(
@ -206,7 +220,7 @@ terrainPainter = new LayeredPainter(
); );
elevationPainter = new SmoothElevationPainter(ELEVATION_MODIFY, 16, 1); elevationPainter = new SmoothElevationPainter(ELEVATION_MODIFY, 16, 1);
createAreas(placer, [terrainPainter, elevationPainter, paintClass(clHill3)], createAreas(placer, [terrainPainter, elevationPainter, paintClass(clHill3)],
[avoidClasses(clHill3, 1), new StayInTileClassConstraint(clHill2, 0)], [avoidClasses(clHill3, 1), stayClasses(clHill2, 0)],
mapArea/9000, 300 mapArea/9000, 300
); );
@ -237,6 +251,8 @@ createObjectGroups(group, 0,
mapArea/4000, 100 mapArea/4000, 100
); );
RMS.SetProgress(97);
// create decorative rocks for hills // create decorative rocks for hills
log("Creating decorative rocks..."); log("Creating decorative rocks...");
group = new SimpleGroup([new SimpleObject(aDecorativeRock, 1,1, 0,0)], true); group = new SimpleGroup([new SimpleObject(aDecorativeRock, 1,1, 0,0)], true);

View File

@ -32,21 +32,20 @@ var g_Camera = {
function InitMap() function InitMap()
{ {
if (g_MapSettings === undefined || g_MapSettings == {}) if (g_MapSettings === undefined)
{ // If settings missing, warn and use some defaults {
warn("InitMapGen: settings missing"); // Should never get this far, failed settings would abort prior to loading scripts
g_MapSettings = { error("InitMapGen: settings missing");
Size : 13,
BaseTerrain: "grass1_spring",
BaseHeight: 0,
PlayerData : [ {}, {} ]
};
} }
// Create new map // Create new map
log("Creating new map..."); log("Creating new map...");
var terrain = createTerrain(g_MapSettings.BaseTerrain); var terrain = createTerrain(g_MapSettings.BaseTerrain);
// XXX: Temporary hack to keep typed arrays from complaining about invalid arguments,
// until SpiderMonkey gets upgraded
g_MapSettings.Size = Math.floor(g_MapSettings.Size);
g_Map = new Map(g_MapSettings.Size * TILES_PER_PATCH, g_MapSettings.BaseHeight); g_Map = new Map(g_MapSettings.Size * TILES_PER_PATCH, g_MapSettings.BaseHeight);
g_Map.initTerrain(terrain); g_Map.initTerrain(terrain);
} }

View File

@ -27,71 +27,152 @@
#define RMS_RUNTIME_SIZE 96 * 1024 * 1024 #define RMS_RUNTIME_SIZE 96 * 1024 * 1024
CMapGenerator::CMapGenerator() : m_ScriptInterface("RMS", "MapGenerator", ScriptInterface::CreateRuntime(RMS_RUNTIME_SIZE)) CMapGeneratorWorker::CMapGeneratorWorker()
{ {
m_ScriptInterface.SetCallbackData(static_cast<void*> (this)); // If something happens before we initialize, that's a failure
m_Progress = -1;
// Replace RNG with a seeded deterministic function
m_ScriptInterface.ReplaceNondeterministicFunctions(m_MapGenRNG);
// functions for RMS
m_ScriptInterface.RegisterFunction<bool, std::wstring, CMapGenerator::LoadLibrary>("LoadLibrary");
m_ScriptInterface.RegisterFunction<void, CScriptValRooted, CMapGenerator::ExportMap>("ExportMap");
} }
bool CMapGenerator::GenerateMap(const VfsPath& scriptFile, const CScriptValRooted& settings) CMapGeneratorWorker::~CMapGeneratorWorker()
{ {
// Wait for thread to end
pthread_join(m_WorkerThread, NULL);
}
void CMapGeneratorWorker::Initialize(const VfsPath& scriptFile, const std::string& settings)
{
CScopeLock lock(m_WorkerMutex);
// Set progress to positive value
m_Progress = 1;
m_ScriptPath = scriptFile;
m_Settings = settings;
// Launch the worker thread
int ret = pthread_create(&m_WorkerThread, NULL, &RunThread, this);
debug_assert(ret == 0);
}
void* CMapGeneratorWorker::RunThread(void *data)
{
debug_SetThreadName("MapGenerator");
CMapGeneratorWorker* self = static_cast<CMapGeneratorWorker*>(data);
self->m_ScriptInterface = new ScriptInterface("RMS", "MapGenerator", ScriptInterface::CreateRuntime(RMS_RUNTIME_SIZE));
// Run map generation scripts
if (!self->Run())
{
// Failed: set progress to error
CScopeLock lock(self->m_WorkerMutex);
self->m_Progress = -1;
}
// At this point the random map scripts are done running, so the thread has no further purpose
// and can die. The data will be stored in m_MapData already if successful, or m_Progress
// will contain an error value on failure.
// Cleanup ScriptInterface
SAFE_DELETE(self->m_ScriptInterface);
return NULL;
}
bool CMapGeneratorWorker::Run()
{
m_ScriptInterface->SetCallbackData(static_cast<void*> (this));
// Replace RNG with a seeded deterministic function
m_ScriptInterface->ReplaceNondeterministicFunctions(m_MapGenRNG);
// Functions for RMS
m_ScriptInterface->RegisterFunction<bool, std::wstring, CMapGeneratorWorker::LoadLibrary>("LoadLibrary");
m_ScriptInterface->RegisterFunction<void, CScriptValRooted, CMapGeneratorWorker::ExportMap>("ExportMap");
m_ScriptInterface->RegisterFunction<void, int, CMapGeneratorWorker::SetProgress>("SetProgress");
m_ScriptInterface->RegisterFunction<void, CMapGeneratorWorker::MaybeGC>("MaybeGC");
// Parse settings
CScriptValRooted settingsVal = m_ScriptInterface->ParseJSON(m_Settings);
if (settingsVal.undefined())
{
LOGERROR(L"CMapGeneratorWorker::Initialize: Failed to parse settings");
return false;
}
// Init RNG seed // Init RNG seed
uint32 seed; uint32 seed;
if (!m_ScriptInterface.GetProperty(settings.get(), "Seed", seed)) if (!m_ScriptInterface->GetProperty(settingsVal.get(), "Seed", seed))
{ // No seed specified { // No seed specified
LOGWARNING(L"GenerateMap: No seed value specified - using 0"); LOGWARNING(L"CMapGeneratorWorker::Initialize: No seed value specified - using 0");
seed = 0; seed = 0;
} }
m_MapGenRNG.seed(seed); m_MapGenRNG.seed(seed);
// Copy settings to script context // Copy settings to global variable
if (!m_ScriptInterface.SetProperty(m_ScriptInterface.GetGlobalObject(), "g_MapSettings", settings)) if (!m_ScriptInterface->SetProperty(m_ScriptInterface->GetGlobalObject(), "g_MapSettings", settingsVal))
{
LOGERROR(L"CMapGeneratorWorker::Initialize: Failed to define g_MapSettings");
return false; return false;
}
// Load RMS // Load RMS
LOGMESSAGE(L"Loading RMS '%ls'", scriptFile.string().c_str()); LOGMESSAGE(L"Loading RMS '%ls'", m_ScriptPath.string().c_str());
if (!m_ScriptInterface.LoadGlobalScriptFile(scriptFile)) if (!m_ScriptInterface->LoadGlobalScriptFile(m_ScriptPath))
{ {
LOGERROR(L"Failed to load RMS '%ls'", scriptFile.string().c_str()); LOGERROR(L"CMapGeneratorWorker::Initialize: Failed to load RMS '%ls'", m_ScriptPath.string().c_str());
return false; return false;
} }
return true; return true;
} }
ScriptInterface& CMapGenerator::GetScriptInterface() int CMapGeneratorWorker::GetProgress()
{ {
return m_ScriptInterface; CScopeLock lock(m_WorkerMutex);
return m_Progress;
} }
CScriptValRooted& CMapGenerator::GetMapData() shared_ptr<ScriptInterface::StructuredClone> CMapGeneratorWorker::GetResults()
{ {
CScopeLock lock(m_WorkerMutex);
return m_MapData; return m_MapData;
} }
bool CMapGenerator::LoadLibrary(void* cbdata, std::wstring name) bool CMapGeneratorWorker::LoadLibrary(void* cbdata, std::wstring name)
{ {
CMapGenerator* self = static_cast<CMapGenerator*> (cbdata); CMapGeneratorWorker* self = static_cast<CMapGeneratorWorker*>(cbdata);
return self->LoadScripts(name); return self->LoadScripts(name);
} }
void CMapGenerator::ExportMap(void* cbdata, CScriptValRooted data) void CMapGeneratorWorker::ExportMap(void* cbdata, CScriptValRooted data)
{ {
CMapGenerator* self = static_cast<CMapGenerator*> (cbdata); CMapGeneratorWorker* self = static_cast<CMapGeneratorWorker*>(cbdata);
// Copy results // Copy results
self->m_MapData = data; CScopeLock lock(self->m_WorkerMutex);
self->m_MapData = self->m_ScriptInterface->WriteStructuredClone(data.get());
self->m_Progress = 0;
} }
bool CMapGenerator::LoadScripts(const std::wstring& libraryName) void CMapGeneratorWorker::SetProgress(void* cbdata, int progress)
{
CMapGeneratorWorker* self = static_cast<CMapGeneratorWorker*>(cbdata);
// Copy data
CScopeLock lock(self->m_WorkerMutex);
self->m_Progress = progress;
}
void CMapGeneratorWorker::MaybeGC(void* cbdata)
{
CMapGeneratorWorker* self = static_cast<CMapGeneratorWorker*>(cbdata);
self->m_ScriptInterface->MaybeGC();
}
bool CMapGeneratorWorker::LoadScripts(const std::wstring& libraryName)
{ {
// Ignore libraries that are already loaded // Ignore libraries that are already loaded
if (m_LoadedLibraries.find(libraryName) != m_LoadedLibraries.end()) if (m_LoadedLibraries.find(libraryName) != m_LoadedLibraries.end())
@ -114,7 +195,7 @@ bool CMapGenerator::LoadScripts(const std::wstring& libraryName)
{ {
LOGMESSAGE(L"Loading map generator script '%ls'", it->string().c_str()); LOGMESSAGE(L"Loading map generator script '%ls'", it->string().c_str());
if (!m_ScriptInterface.LoadGlobalScriptFile(*it)) if (!m_ScriptInterface->LoadGlobalScriptFile(*it))
{ {
LOGERROR(L"Failed to load script '%ls'", it->string().c_str()); LOGERROR(L"Failed to load script '%ls'", it->string().c_str());
return false; return false;
@ -123,3 +204,32 @@ bool CMapGenerator::LoadScripts(const std::wstring& libraryName)
return true; return true;
} }
CMapGenerator::CMapGenerator() : m_Worker(new CMapGeneratorWorker())
{
}
CMapGenerator::~CMapGenerator()
{
delete m_Worker;
}
void CMapGenerator::GenerateMap(const VfsPath& scriptFile, const std::string& settings)
{
m_Worker->Initialize(scriptFile, settings);
}
int CMapGenerator::GetProgress()
{
return m_Worker->GetProgress();
}
shared_ptr<ScriptInterface::StructuredClone> CMapGenerator::GetResults()
{
return m_Worker->GetResults();
}

View File

@ -18,40 +18,125 @@
#ifndef INCLUDED_MAPGENERATOR #ifndef INCLUDED_MAPGENERATOR
#define INCLUDED_MAPGENERATOR #define INCLUDED_MAPGENERATOR
#include "ps/CStr.h"
#include "ps/FileIo.h" #include "ps/FileIo.h"
#include "ps/ThreadUtil.h"
#include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptInterface.h"
#include <boost/random/linear_congruential.hpp> #include <boost/random/linear_congruential.hpp>
class CMapGeneratorWorker;
/**
* Random map generator interface. Initialized by CMapReader and then checked
* periodically during loading, until it's finished (progress value is 0).
*
* The actual work is performed by CMapGeneratorWorker in a separate thread.
*/
class CMapGenerator class CMapGenerator
{ {
public: public:
// constructor
CMapGenerator(); CMapGenerator();
~CMapGenerator();
// return success of map generation /**
bool GenerateMap(const VfsPath& scriptFile, const CScriptValRooted& settings); * Start the map generator thread
*
* @param scriptFile The VFS path for the script, e.g. "maps/random/latium.js"
* @param settings JSON string containing settings for the map generator
*/
void GenerateMap(const VfsPath& scriptFile, const std::string& settings);
// accessors /**
ScriptInterface& GetScriptInterface(); * Get status of the map generator thread
*
* @return Progress percentage 1-100 if active, 0 when finished, or -1 when
*/
int GetProgress();
CScriptValRooted& GetMapData(); /**
* Get random map data, according to this format:
* http://trac.wildfiregames.com/wiki/Random_Map_Generator_Internals#Dataformat
*
* @return StructuredClone containing map data
*/
shared_ptr<ScriptInterface::StructuredClone> GetResults();
// callbacks for script functions
static bool LoadLibrary(void* cbdata, std::wstring name);
static void ExportMap(void* cbdata, CScriptValRooted data);
private: private:
CMapGeneratorWorker* m_Worker;
bool LoadScripts(const std::wstring& libraryName);
ScriptInterface m_ScriptInterface;
CScriptValRooted m_MapData;
std::set<std::wstring> m_LoadedLibraries;
boost::rand48 m_MapGenRNG;
}; };
/**
* Random map generator worker thread.
* (This is run in a thread so that the GUI remains responsive while loading)
*
* Thread-safety:
* - Initialize and constructor/destructor must be called from the main thread.
* - ScriptInterface created and destroyed by thread
* - StructuredClone used to return JS map data - jsvals can't be used across threads/runtimes
*/
class CMapGeneratorWorker
{
public:
CMapGeneratorWorker();
~CMapGeneratorWorker();
/**
* Start the map generator thread
*
* @param scriptFile The VFS path for the script, e.g. "maps/random/latium.js"
* @param settings JSON string containing settings for the map generator
*/
void Initialize(const VfsPath& scriptFile, const std::string& settings);
/**
* Get status of the map generator thread
*
* @return Progress percentage 1-100 if active, 0 when finished, or -1 when
*/
int GetProgress();
/**
* Get random map data, according to this format:
* http://trac.wildfiregames.com/wiki/Random_Map_Generator_Internals#Dataformat
*
* @return StructuredClone containing map data
*/
shared_ptr<ScriptInterface::StructuredClone> GetResults();
private:
// Mapgen
/**
* Load all scripts of the given library
*
* @param libraryName String specifying name of the library (subfolder of ../maps/random/)
*/
bool LoadScripts(const std::wstring& libraryName);
// callbacks for script functions
static bool LoadLibrary(void* cbdata, std::wstring name);
static void ExportMap(void* cbdata, CScriptValRooted data);
static void SetProgress(void* cbdata, int progress);
static void MaybeGC(void* cbdata);
std::set<std::wstring> m_LoadedLibraries;
shared_ptr<ScriptInterface::StructuredClone> m_MapData;
boost::rand48 m_MapGenRNG;
int m_Progress;
ScriptInterface* m_ScriptInterface;
VfsPath m_ScriptPath;
std::string m_Settings;
// Thread
static void* RunThread(void* data);
bool Run();
pthread_t m_WorkerThread;
CMutex m_WorkerMutex;
};
#endif //INCLUDED_MAPGENERATOR #endif //INCLUDED_MAPGENERATOR

View File

@ -49,7 +49,7 @@
CMapReader::CMapReader() CMapReader::CMapReader()
: xml_reader(0), m_PatchesPerSide(0) : xml_reader(0), m_PatchesPerSide(0), m_MapGen(0)
{ {
cur_terrain_tex = 0; // important - resets generator state cur_terrain_tex = 0; // important - resets generator state
@ -161,7 +161,7 @@ void CMapReader::LoadRandomMap(const CStrW& scriptFile, const CScriptValRooted&
RegMemFun(this, &CMapReader::LoadPlayerSettings, L"CMapReader::LoadPlayerSettings", 50); RegMemFun(this, &CMapReader::LoadPlayerSettings, L"CMapReader::LoadPlayerSettings", 50);
// load map generator with random map script // load map generator with random map script
RegMemFun(this, &CMapReader::GenerateMap, L"CMapReader::GenerateMap", 2000); RegMemFun(this, &CMapReader::GenerateMap, L"CMapReader::GenerateMap", 5000);
// parse RMS results into terrain structure // parse RMS results into terrain structure
RegMemFun(this, &CMapReader::ParseTerrain, L"CMapReader::ParseTerrain", 500); RegMemFun(this, &CMapReader::ParseTerrain, L"CMapReader::ParseTerrain", 500);
@ -1037,8 +1037,7 @@ int CMapReader::ReadXML()
// finished or failed // finished or failed
if (ret <= 0) if (ret <= 0)
{ {
delete xml_reader; SAFE_DELETE(xml_reader);
xml_reader = 0;
} }
return ret; return ret;
@ -1067,28 +1066,54 @@ int CMapReader::LoadRMSettings()
int CMapReader::GenerateMap() int CMapReader::GenerateMap()
{ {
TIMER(L"GenerateMap"); if (!m_MapGen)
{
// Initialize map generator
m_MapGen = new CMapGenerator();
CMapGenerator mapGen; VfsPath scriptPath;
if (m_ScriptFile.length())
scriptPath = L"maps/random/"+m_ScriptFile;
VfsPath scriptPath; // Stringify settings to pass across threads
std::string scriptSettings = pSimulation2->GetScriptInterface().StringifyJSON(m_ScriptSettings.get());
if (m_ScriptFile.length())
scriptPath = L"maps/random/"+m_ScriptFile; // Try to generate map
m_MapGen->GenerateMap(scriptPath, scriptSettings);
// Copy map settings from simulator to mapgen context
CScriptValRooted scriptSettings(mapGen.GetScriptInterface().GetContext(), mapGen.GetScriptInterface().CloneValueFromOtherContext(pSimulation2->GetScriptInterface(), m_ScriptSettings.get()));
// Try to generate map
if (!mapGen.GenerateMap(scriptPath, scriptSettings))
{ // RMS failed - return to main menu
throw PSERROR_Game_World_MapLoadFailed("Error generating random map.\nCheck application log for details.");
} }
// Copy data from mapgen to simulator context // Check status
m_MapData = CScriptValRooted(pSimulation2->GetScriptInterface().GetContext(), pSimulation2->GetScriptInterface().CloneValueFromOtherContext(mapGen.GetScriptInterface(), mapGen.GetMapData().get())); int progress = m_MapGen->GetProgress();
if (progress < 0)
{
// RMS failed - return to main menu
throw PSERROR_Game_World_MapLoadFailed("Error generating random map.\nCheck application log for details.");
}
else if (progress == 0)
{
// Finished, get results as StructuredClone object, which must be read to obtain the JS val
shared_ptr<ScriptInterface::StructuredClone> results = m_MapGen->GetResults();
return 0; // Parse data into simulation context
CScriptValRooted data(pSimulation2->GetScriptInterface().GetContext(), pSimulation2->GetScriptInterface().ReadStructuredClone(results));
if (data.undefined())
{
// RMS failed - return to main menu
throw PSERROR_Game_World_MapLoadFailed("Error generating random map.\nCheck application log for details.");
}
else
{
m_MapData = data;
}
}
else
{
// Still working
}
// return progress
return progress;
}; };
@ -1312,3 +1337,17 @@ int CMapReader::ParseCamera()
return 0; return 0;
} }
CMapReader::~CMapReader()
{
// Cleaup objects
if (xml_reader)
{
delete xml_reader;
}
if (m_MapGen)
{
delete m_MapGen;
}
}

View File

@ -38,8 +38,8 @@ class CTerrainTextureEntry;
class CScriptValRooted; class CScriptValRooted;
class ScriptInterface; class ScriptInterface;
class CGameView; class CGameView;
class CXMLReader; class CXMLReader;
class CMapGenerator;
class CMapReader : public CMapIO class CMapReader : public CMapIO
{ {
@ -48,6 +48,8 @@ class CMapReader : public CMapIO
public: public:
// constructor // constructor
CMapReader(); CMapReader();
~CMapReader();
// LoadMap: try to load the map from given file; reinitialise the scene to new data if successful // LoadMap: try to load the map from given file; reinitialise the scene to new data if successful
void LoadMap(const VfsPath& pathname, CTerrain*, WaterManager*, SkyManager*, CLightEnv*, CGameView*, void LoadMap(const VfsPath& pathname, CTerrain*, WaterManager*, SkyManager*, CLightEnv*, CGameView*,
CCinemaManager*, CTriggerManager*, CSimulation2*, int playerID); CCinemaManager*, CTriggerManager*, CSimulation2*, int playerID);
@ -119,6 +121,8 @@ private:
CScriptValRooted m_ScriptSettings; CScriptValRooted m_ScriptSettings;
CScriptValRooted m_MapData; CScriptValRooted m_MapData;
CMapGenerator* m_MapGen;
// state latched by LoadMap and held until DelayedLoadFinished // state latched by LoadMap and held until DelayedLoadFinished
CFileUnpacker unpacker; CFileUnpacker unpacker;
CTerrain* pTerrain; CTerrain* pTerrain;

View File

@ -1026,8 +1026,8 @@ bool Autostart(const CmdLineArgs& args)
else else
{ {
// Problem with JSON file // Problem with JSON file
CStrW msg = L"Error reading random map script \"" + scriptPath + L"\".\nCheck application log for details."; LOGERROR(L"Error reading random map script '%ls'", scriptPath.c_str());
throw PSERROR_Game_World_MapLoadFailed(msg.ToUTF8().c_str()); throw PSERROR_Game_World_MapLoadFailed("Error reading random map script.\nCheck application log for details.");
} }
// Get optional map size argument (default 12) // Get optional map size argument (default 12)
@ -1162,9 +1162,11 @@ bool Autostart(const CmdLineArgs& args)
void CancelLoad(const CStrW& message) void CancelLoad(const CStrW& message)
{ {
//Cancel loader
LDR_Cancel(); LDR_Cancel();
// Call the cancelOnError GUI function, but only if it exists // Call the cancelOnError GUI function, defined in ..gui/common/functions_utility_error.js
// So all GUI pages that load games should include this script
if (g_GUI && g_GUI->HasPages()) if (g_GUI && g_GUI->HasPages())
{ {
JSContext* cx = g_ScriptingHost.getContext(); JSContext* cx = g_ScriptingHost.getContext();

View File

@ -144,17 +144,12 @@ void LDR_EndRegistering()
// note: no special notification will be returned by LDR_ProgressiveLoad. // note: no special notification will be returned by LDR_ProgressiveLoad.
void LDR_Cancel() void LDR_Cancel()
{ {
// note: calling during registering doesn't make sense - that
// should be an atomic sequence of begin, register [..], end.
debug_assert(state == LOADING);
// the queue doesn't need to be emptied now; that'll happen during the // the queue doesn't need to be emptied now; that'll happen during the
// next LDR_StartRegistering. for now, it is sufficient to set the // next LDR_StartRegistering. for now, it is sufficient to set the
// state, so that LDR_ProgressiveLoad is a no-op. // state, so that LDR_ProgressiveLoad is a no-op.
state = IDLE; state = IDLE;
} }
// helper routine for LDR_ProgressiveLoad. // helper routine for LDR_ProgressiveLoad.
// tries to prevent starting a long task when at the end of a timeslice. // tries to prevent starting a long task when at the end of a timeslice.
static bool HaveTimeForNextTask(double time_left, double time_budget, int estimated_duration_ms) static bool HaveTimeForNextTask(double time_left, double time_budget, int estimated_duration_ms)
@ -269,7 +264,7 @@ LibError LDR_ProgressiveLoad(double time_budget, wchar_t* description, size_t ma
// .. failed; abort. loading will continue when we're called in // .. failed; abort. loading will continue when we're called in
// the next iteration of the main loop. // the next iteration of the main loop.
// rationale: bail immediately instead of remembering the first // rationale: bail immediately instead of remembering the first
// error that came up so we report can all errors that happen. // error that came up so we can report all errors that happen.
else if(status < 0) else if(status < 0)
{ {
ret = (LibError)status; ret = (LibError)status;

View File

@ -89,7 +89,7 @@ void CWorld::RegisterInit(const CStrW& mapFile, int playerID)
{ {
delete reader; delete reader;
LOGERROR(L"Failed to load scenario %ls: %hs", mapfilename.string().c_str(), err.what()); LOGERROR(L"Failed to load scenario %ls: %hs", mapfilename.string().c_str(), err.what());
throw PSERROR_Game_World_MapLoadFailed("Failed to load scenario"); throw PSERROR_Game_World_MapLoadFailed("Failed to load scenario.\nCheck application log for details.");
} }
} }
} }