Index: binaries/data/mods/public/maps/random/rmgen/RandomMapLogger.js =================================================================== --- binaries/data/mods/public/maps/random/rmgen/RandomMapLogger.js +++ binaries/data/mods/public/maps/random/rmgen/RandomMapLogger.js @@ -4,11 +4,13 @@ this.startTime = Engine.GetMicroseconds(); this.prefix = ""; // seems noisy - this.printDirectly( - this.prefix + - "Generating " + g_MapSettings.Name + - " of size " + g_MapSettings.Size + - " and " + getNumPlayers() + " players.\n"); + // Don't print in test cases + if (g_MapSettings.Name) + this.printDirectly( + this.prefix + + "Generating " + g_MapSettings.Name + + " of size " + g_MapSettings.Size + + " and " + (g_MapSettings.PlayerData.length - 1) + " players.\n"); } RandomMapLogger.prototype.printDirectly = function(string) Index: binaries/data/mods/public/maps/random/tests/test_TileClass.js =================================================================== --- /dev/null +++ binaries/data/mods/public/maps/random/tests/test_TileClass.js @@ -0,0 +1,94 @@ +Engine.LoadLibrary("rmgen"); + +Engine.GetMicroseconds = () => 0; + +var g_MapSettings = { "Size": 512 }; +var g_Map = new RandomMap(0, "blackness"); + +// Test that that it checks by value, not by reference +{ + let tileClass = new TileClass(2); + let reference1 = new Vector2D(1, 1); + let reference2 = new Vector2D(1.000, 1.0); + tileClass.add(reference1); + TS_ASSERT(tileClass.has(reference2)); +} + +// Test out-of-bounds +{ + let tileClass = new TileClass(32); + let absentPoints = [ + new Vector2D(0, 0), + new Vector2D(0, 1), + new Vector2D(1, 0), + new Vector2D(-1, -1), + new Vector2D(2048, 0), + new Vector2D(0, NaN), + new Vector2D(0, Infinity) + ]; + + absentPoints.forEach(point => { + TS_ASSERT(!tileClass.has(point)); + }); +} + +// Test multi-insertion +{ + let tileClass = new TileClass(32); + let point = new Vector2D(1, 1); + + tileClass.add(point); + tileClass.add(point); + TS_ASSERT_EQUALS(tileClass.countMembersInRadius(point, 0), 1); + + // Still one point remaining + tileClass.remove(point); + tileClass.remove(point); + TS_ASSERT(!tileClass.has(point)); +} + +// Test Multi-insertion removal +{ + let tileClass = new TileClass(32); + let point = new Vector2D(2, 7); + + tileClass.add(point); + tileClass.add(point); + tileClass.remove(point); + TS_ASSERT(tileClass.has(point)); + + tileClass.remove(point); + TS_ASSERT(!tileClass.has(point)); +} + +//Test Multi-insertion removal +{ + let tileClass = new TileClass(55); + let point = new Vector2D(5, 4); + + for (let i = 0; i < 50; ++i) + tileClass.add(point); + + tileClass.remove(point); + TS_ASSERT(tileClass.has(point)); + + tileClass.remove(point); + TS_ASSERT(tileClass.has(point)); +} + +// Test getters +{ + let tileClass = new TileClass(88); + let point = new Vector2D(5, 1); + tileClass.add(point); + + let point2 = new Vector2D(4, 9); + tileClass.add(point2); + + TS_ASSERT_EQUALS(tileClass.countMembersInRadius(point, 1), 1); + TS_ASSERT_EQUALS(tileClass.countMembersInRadius(point, 100), 2); + + TS_ASSERT_EQUALS(tileClass.countNonMembersInRadius(point, 1), 4); + TS_ASSERT_EQUALS(tileClass.countNonMembersInRadius(point, 2), 12); + TS_ASSERT_EQUALS(tileClass.countNonMembersInRadius(point, 3), 28); +} Index: source/graphics/MapGenerator.h =================================================================== --- source/graphics/MapGenerator.h +++ source/graphics/MapGenerator.h @@ -81,11 +81,13 @@ * - Initialize and constructor/destructor must be called from the main thread. * - ScriptInterface created and destroyed by thread * - StructuredClone used to return JS map data - JS:Values can't be used across threads/runtimes. + * + * In test-mode, threading is disabled. */ class CMapGeneratorWorker { public: - CMapGeneratorWorker(); + CMapGeneratorWorker(const bool testing, ScriptInterface* scriptInterface); ~CMapGeneratorWorker(); /** @@ -111,12 +113,27 @@ */ shared_ptr GetResults(); + /** + * Perform the map generation or test. + */ + bool Run(); + private: /** - * Expose functions defined in this class to the script. + * Perform the map generation - not called for tests. + */ + bool RunMapGenerator(); + + /** + * Expose functions, globals and classes defined in this class to the map and test scripts. + */ + void RegisterScriptFunctions_Common(); + + /** + * Expose functions to the mapscripts but not the tests. */ - void RegisterScriptFunctions(); + void RegisterScriptFunctions_MapGenerator(); /** * Load all scripts of the given library @@ -182,11 +199,6 @@ static void* RunThread(void* data); /** - * Perform the map generation. - */ - bool Run(); - - /** * Currently loaded script librarynames. */ std::set m_LoadedLibraries; @@ -227,6 +239,12 @@ CTemplateLoader m_TemplateLoader; /** + * Set to true to spawn a new thread for mapgeneration. + * Set to false to run the testscripts in the current thread. + */ + bool m_Testing; + + /** * Holds the mapgeneration thread identifier. */ pthread_t m_WorkerThread; Index: source/graphics/MapGenerator.cpp =================================================================== --- source/graphics/MapGenerator.cpp +++ source/graphics/MapGenerator.cpp @@ -57,7 +57,8 @@ return true; } -CMapGeneratorWorker::CMapGeneratorWorker() +CMapGeneratorWorker::CMapGeneratorWorker(const bool testing, ScriptInterface* scriptInterface) : + m_Testing(testing), m_ScriptInterface(scriptInterface) { // If something happens before we initialize, that's a failure m_Progress = -1; @@ -66,7 +67,8 @@ CMapGeneratorWorker::~CMapGeneratorWorker() { // Wait for thread to end - pthread_join(m_WorkerThread, NULL); + if (!m_Testing) + pthread_join(m_WorkerThread, NULL); } void CMapGeneratorWorker::Initialize(const VfsPath& scriptFile, const std::string& settings) @@ -78,6 +80,10 @@ m_ScriptPath = scriptFile; m_Settings = settings; + // For tests, the Run function is called in the same thread + if (m_Testing) + return; + // Launch the worker thread int ret = pthread_create(&m_WorkerThread, NULL, &RunThread, this); ENSURE(ret == 0); @@ -116,15 +122,29 @@ bool CMapGeneratorWorker::Run() { - JSContext* cx = m_ScriptInterface->GetContext(); - JSAutoRequest rq(cx); - m_ScriptInterface->SetCallbackData(static_cast (this)); + RegisterScriptFunctions_Common(); + // Replace RNG with a seeded deterministic function m_ScriptInterface->ReplaceNondeterministicRNG(m_MapGenRNG); - RegisterScriptFunctions(); + if (m_Testing) + { + m_MapGenRNG.seed(0); + return m_ScriptInterface->LoadGlobalScriptFile(m_ScriptPath); + } + else + { + RegisterScriptFunctions_MapGenerator(); + return RunMapGenerator(); + } +} + +bool CMapGeneratorWorker::RunMapGenerator() +{ + JSContext* cx = m_ScriptInterface->GetContext(); + JSAutoRequest rq(cx); // Parse settings JS::RootedValue settingsVal(cx); @@ -168,7 +188,7 @@ return true; } -void CMapGeneratorWorker::RegisterScriptFunctions() +void CMapGeneratorWorker::RegisterScriptFunctions_Common() { // VFS JSI_VFS::RegisterScriptFunctions_Maps(*m_ScriptInterface); @@ -181,17 +201,6 @@ m_ScriptInterface->RegisterFunction("LoadHeightmapImage"); m_ScriptInterface->RegisterFunction("LoadMapTerrain"); - // Progression and profiling - m_ScriptInterface->RegisterFunction("SetProgress"); - m_ScriptInterface->RegisterFunction("GetMicroseconds"); - m_ScriptInterface->RegisterFunction("ExportMap"); - - // Template functions - m_ScriptInterface->RegisterFunction("GetTemplate"); - m_ScriptInterface->RegisterFunction("TemplateExists"); - m_ScriptInterface->RegisterFunction, std::string, bool, CMapGeneratorWorker::FindTemplates>("FindTemplates"); - m_ScriptInterface->RegisterFunction, std::string, bool, CMapGeneratorWorker::FindActorTemplates>("FindActorTemplates"); - // Engine constants // Length of one tile of the terrain grid in metres. @@ -202,6 +211,20 @@ m_ScriptInterface->SetGlobal("MAP_BORDER_WIDTH", static_cast(MAP_EDGE_TILES)); } +void CMapGeneratorWorker::RegisterScriptFunctions_MapGenerator() +{ + // Template functions + m_ScriptInterface->RegisterFunction("GetTemplate"); + m_ScriptInterface->RegisterFunction("TemplateExists"); + m_ScriptInterface->RegisterFunction, std::string, bool, CMapGeneratorWorker::FindTemplates>("FindTemplates"); + m_ScriptInterface->RegisterFunction, std::string, bool, CMapGeneratorWorker::FindActorTemplates>("FindActorTemplates"); + + // Progression and profiling + m_ScriptInterface->RegisterFunction("SetProgress"); + m_ScriptInterface->RegisterFunction("GetMicroseconds"); + m_ScriptInterface->RegisterFunction("ExportMap"); +} + int CMapGeneratorWorker::GetProgress() { std::lock_guard lock(m_WorkerMutex); @@ -409,7 +432,7 @@ ////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////// -CMapGenerator::CMapGenerator() : m_Worker(new CMapGeneratorWorker()) +CMapGenerator::CMapGenerator() : m_Worker(new CMapGeneratorWorker(false, nullptr)) { }