LCOV - code coverage report
Current view: top level - source/graphics - MapGenerator.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 32 180 17.8 %
Date: 2023-01-19 00:18:29 Functions: 6 27 22.2 %

          Line data    Source code
       1             : /* Copyright (C) 2022 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "MapGenerator.h"
      21             : 
      22             : #include "graphics/MapIO.h"
      23             : #include "graphics/Patch.h"
      24             : #include "graphics/Terrain.h"
      25             : #include "lib/external_libraries/libsdl.h"
      26             : #include "lib/status.h"
      27             : #include "lib/timer.h"
      28             : #include "lib/file/vfs/vfs_path.h"
      29             : #include "maths/MathUtil.h"
      30             : #include "ps/CLogger.h"
      31             : #include "ps/FileIo.h"
      32             : #include "ps/Profile.h"
      33             : #include "ps/TaskManager.h"
      34             : #include "ps/scripting/JSInterface_VFS.h"
      35             : #include "scriptinterface/FunctionWrapper.h"
      36             : #include "scriptinterface/ScriptContext.h"
      37             : #include "scriptinterface/ScriptConversions.h"
      38             : #include "scriptinterface/ScriptInterface.h"
      39             : #include "scriptinterface/JSON.h"
      40             : #include "simulation2/helpers/MapEdgeTiles.h"
      41             : 
      42             : #include <string>
      43             : #include <vector>
      44             : 
      45             : // TODO: Maybe this should be optimized depending on the map size.
      46             : constexpr int RMS_CONTEXT_SIZE = 96 * 1024 * 1024;
      47             : 
      48             : extern bool IsQuitRequested();
      49             : 
      50             : static bool
      51           0 : MapGeneratorInterruptCallback(JSContext* UNUSED(cx))
      52             : {
      53             :     // This may not use SDL_IsQuitRequested(), because it runs in a thread separate to SDL, see SDL_PumpEvents
      54           0 :     if (IsQuitRequested())
      55             :     {
      56           0 :         LOGWARNING("Quit requested!");
      57           0 :         return false;
      58             :     }
      59             : 
      60           0 :     return true;
      61             : }
      62             : 
      63           6 : CMapGeneratorWorker::CMapGeneratorWorker(ScriptInterface* scriptInterface) :
      64           6 :     m_ScriptInterface(scriptInterface)
      65             : {
      66             :     // If something happens before we initialize, that's a failure
      67           6 :     m_Progress = -1;
      68           6 : }
      69             : 
      70          12 : CMapGeneratorWorker::~CMapGeneratorWorker()
      71             : {
      72             :     // Cancel or wait for the task to end.
      73           6 :     m_WorkerThread.CancelOrWait();
      74           6 : }
      75             : 
      76           0 : void CMapGeneratorWorker::Initialize(const VfsPath& scriptFile, const std::string& settings)
      77             : {
      78           0 :     std::lock_guard<std::mutex> lock(m_WorkerMutex);
      79             : 
      80             :     // Set progress to positive value
      81           0 :     m_Progress = 1;
      82           0 :     m_ScriptPath = scriptFile;
      83           0 :     m_Settings = settings;
      84             : 
      85             :     // Start generating the map asynchronously.
      86           0 :     m_WorkerThread = Threading::TaskManager::Instance().PushTask([this]() {
      87           0 :         PROFILE2("Map Generation");
      88             : 
      89           0 :         std::shared_ptr<ScriptContext> mapgenContext = ScriptContext::CreateContext(RMS_CONTEXT_SIZE);
      90             : 
      91             :         // Enable the script to be aborted
      92           0 :         JS_AddInterruptCallback(mapgenContext->GetGeneralJSContext(), MapGeneratorInterruptCallback);
      93             : 
      94           0 :         m_ScriptInterface = new ScriptInterface("Engine", "MapGenerator", mapgenContext);
      95             : 
      96             :         // Run map generation scripts
      97           0 :         if (!Run() || m_Progress > 0)
      98             :         {
      99             :             // Don't leave progress in an unknown state, if generator failed, set it to -1
     100           0 :             std::lock_guard<std::mutex> lock(m_WorkerMutex);
     101           0 :             m_Progress = -1;
     102             :         }
     103             : 
     104           0 :         SAFE_DELETE(m_ScriptInterface);
     105             : 
     106             :         // At this point the random map scripts are done running, so the thread has no further purpose
     107             :         //  and can die. The data will be stored in m_MapData already if successful, or m_Progress
     108             :         //  will contain an error value on failure.
     109           0 :     });
     110           0 : }
     111             : 
     112           0 : bool CMapGeneratorWorker::Run()
     113             : {
     114           0 :     ScriptRequest rq(m_ScriptInterface);
     115             : 
     116             :     // Parse settings
     117           0 :     JS::RootedValue settingsVal(rq.cx);
     118           0 :     if (!Script::ParseJSON(rq, m_Settings, &settingsVal) && settingsVal.isUndefined())
     119             :     {
     120           0 :         LOGERROR("CMapGeneratorWorker::Run: Failed to parse settings");
     121           0 :         return false;
     122             :     }
     123             : 
     124             :     // Prevent unintentional modifications to the settings object by random map scripts
     125           0 :     if (!Script::FreezeObject(rq, settingsVal, true))
     126             :     {
     127           0 :         LOGERROR("CMapGeneratorWorker::Run: Failed to deepfreeze settings");
     128           0 :         return false;
     129             :     }
     130             : 
     131             :     // Init RNG seed
     132           0 :     u32 seed = 0;
     133           0 :     if (!Script::HasProperty(rq, settingsVal, "Seed") ||
     134           0 :         !Script::GetProperty(rq, settingsVal, "Seed", seed))
     135           0 :         LOGWARNING("CMapGeneratorWorker::Run: No seed value specified - using 0");
     136             : 
     137           0 :     InitScriptInterface(seed);
     138             : 
     139           0 :     RegisterScriptFunctions_MapGenerator();
     140             : 
     141             :     // Copy settings to global variable
     142           0 :     JS::RootedValue global(rq.cx, rq.globalValue());
     143           0 :     if (!Script::SetProperty(rq, global, "g_MapSettings", settingsVal, true, true))
     144             :     {
     145           0 :         LOGERROR("CMapGeneratorWorker::Run: Failed to define g_MapSettings");
     146           0 :         return false;
     147             :     }
     148             : 
     149             :     // Load RMS
     150           0 :     LOGMESSAGE("Loading RMS '%s'", m_ScriptPath.string8());
     151           0 :     if (!m_ScriptInterface->LoadGlobalScriptFile(m_ScriptPath))
     152             :     {
     153           0 :         LOGERROR("CMapGeneratorWorker::Run: Failed to load RMS '%s'", m_ScriptPath.string8());
     154           0 :         return false;
     155             :     }
     156             : 
     157           0 :     return true;
     158             : }
     159             : 
     160             : #define REGISTER_MAPGEN_FUNC(func) \
     161             :     ScriptFunction::Register<&CMapGeneratorWorker::func, ScriptInterface::ObjectFromCBData<CMapGeneratorWorker>>(rq, #func);
     162             : #define REGISTER_MAPGEN_FUNC_NAME(func, name) \
     163             :     ScriptFunction::Register<&CMapGeneratorWorker::func, ScriptInterface::ObjectFromCBData<CMapGeneratorWorker>>(rq, name);
     164             : 
     165           6 : void CMapGeneratorWorker::InitScriptInterface(const u32 seed)
     166             : {
     167           6 :     m_ScriptInterface->SetCallbackData(static_cast<void*>(this));
     168             : 
     169           6 :     m_ScriptInterface->ReplaceNondeterministicRNG(m_MapGenRNG);
     170           6 :     m_MapGenRNG.seed(seed);
     171             : 
     172             :     // VFS
     173           6 :     JSI_VFS::RegisterScriptFunctions_ReadOnlySimulationMaps(*m_ScriptInterface);
     174             : 
     175             :     // Globalscripts may use VFS script functions
     176           6 :     m_ScriptInterface->LoadGlobalScripts();
     177             : 
     178             :     // File loading
     179          12 :     ScriptRequest rq(m_ScriptInterface);
     180           6 :     REGISTER_MAPGEN_FUNC_NAME(LoadScripts, "LoadLibrary");
     181           6 :     REGISTER_MAPGEN_FUNC_NAME(LoadHeightmap, "LoadHeightmapImage");
     182           6 :     REGISTER_MAPGEN_FUNC(LoadMapTerrain);
     183             : 
     184             :     // Engine constants
     185             : 
     186             :     // Length of one tile of the terrain grid in metres.
     187             :     // Useful to transform footprint sizes to the tilegrid coordinate system.
     188           6 :     m_ScriptInterface->SetGlobal("TERRAIN_TILE_SIZE", static_cast<int>(TERRAIN_TILE_SIZE));
     189             : 
     190             :     // Number of impassable tiles at the map border
     191           6 :     m_ScriptInterface->SetGlobal("MAP_BORDER_WIDTH", static_cast<int>(MAP_EDGE_TILES));
     192           6 : }
     193             : 
     194           0 : void CMapGeneratorWorker::RegisterScriptFunctions_MapGenerator()
     195             : {
     196           0 :     ScriptRequest rq(m_ScriptInterface);
     197             : 
     198             :     // Template functions
     199           0 :     REGISTER_MAPGEN_FUNC(GetTemplate);
     200           0 :     REGISTER_MAPGEN_FUNC(TemplateExists);
     201           0 :     REGISTER_MAPGEN_FUNC(FindTemplates);
     202           0 :     REGISTER_MAPGEN_FUNC(FindActorTemplates);
     203             : 
     204             :     // Progression and profiling
     205           0 :     REGISTER_MAPGEN_FUNC(SetProgress);
     206           0 :     REGISTER_MAPGEN_FUNC(GetMicroseconds);
     207           0 :     REGISTER_MAPGEN_FUNC(ExportMap);
     208           0 : }
     209             : 
     210             : #undef REGISTER_MAPGEN_FUNC
     211             : #undef REGISTER_MAPGEN_FUNC_NAME
     212             : 
     213           0 : int CMapGeneratorWorker::GetProgress()
     214             : {
     215           0 :     std::lock_guard<std::mutex> lock(m_WorkerMutex);
     216           0 :     return m_Progress;
     217             : }
     218             : 
     219           0 : double CMapGeneratorWorker::GetMicroseconds()
     220             : {
     221           0 :     return JS_Now();
     222             : }
     223             : 
     224           0 : Script::StructuredClone CMapGeneratorWorker::GetResults()
     225             : {
     226           0 :     std::lock_guard<std::mutex> lock(m_WorkerMutex);
     227           0 :     return m_MapData;
     228             : }
     229             : 
     230           0 : void CMapGeneratorWorker::ExportMap(JS::HandleValue data)
     231             : {
     232             :     // Copy results
     233           0 :     std::lock_guard<std::mutex> lock(m_WorkerMutex);
     234           0 :     m_MapData = Script::WriteStructuredClone(ScriptRequest(m_ScriptInterface), data);
     235           0 :     m_Progress = 0;
     236           0 : }
     237             : 
     238           0 : void CMapGeneratorWorker::SetProgress(int progress)
     239             : {
     240             :     // Copy data
     241           0 :     std::lock_guard<std::mutex> lock(m_WorkerMutex);
     242             : 
     243           0 :     if (progress >= m_Progress)
     244           0 :         m_Progress = progress;
     245             :     else
     246           0 :         LOGWARNING("The random map script tried to reduce the loading progress from %d to %d", m_Progress, progress);
     247           0 : }
     248             : 
     249           0 : CParamNode CMapGeneratorWorker::GetTemplate(const std::string& templateName)
     250             : {
     251           0 :     const CParamNode& templateRoot = m_TemplateLoader.GetTemplateFileData(templateName).GetOnlyChild();
     252           0 :     if (!templateRoot.IsOk())
     253           0 :         LOGERROR("Invalid template found for '%s'", templateName.c_str());
     254             : 
     255           0 :     return templateRoot;
     256             : }
     257             : 
     258           0 : bool CMapGeneratorWorker::TemplateExists(const std::string& templateName)
     259             : {
     260           0 :     return m_TemplateLoader.TemplateExists(templateName);
     261             : }
     262             : 
     263           0 : std::vector<std::string> CMapGeneratorWorker::FindTemplates(const std::string& path, bool includeSubdirectories)
     264             : {
     265           0 :     return m_TemplateLoader.FindTemplates(path, includeSubdirectories, SIMULATION_TEMPLATES);
     266             : }
     267             : 
     268           0 : std::vector<std::string> CMapGeneratorWorker::FindActorTemplates(const std::string& path, bool includeSubdirectories)
     269             : {
     270           0 :     return m_TemplateLoader.FindTemplates(path, includeSubdirectories, ACTOR_TEMPLATES);
     271             : }
     272             : 
     273          24 : bool CMapGeneratorWorker::LoadScripts(const VfsPath& libraryName)
     274             : {
     275             :     // Ignore libraries that are already loaded
     276          24 :     if (m_LoadedLibraries.find(libraryName) != m_LoadedLibraries.end())
     277           0 :         return true;
     278             : 
     279             :     // Mark this as loaded, to prevent it recursively loading itself
     280          24 :     m_LoadedLibraries.insert(libraryName);
     281             : 
     282          48 :     VfsPath path = VfsPath(L"maps/random/") / libraryName / VfsPath();
     283          48 :     VfsPaths pathnames;
     284             : 
     285             :     // Load all scripts in mapgen directory
     286          24 :     Status ret = vfs::GetPathnames(g_VFS, path, L"*.js", pathnames);
     287          24 :     if (ret == INFO::OK)
     288             :     {
     289         240 :         for (const VfsPath& p : pathnames)
     290             :         {
     291         216 :             LOGMESSAGE("Loading map generator script '%s'", p.string8());
     292             : 
     293         216 :             if (!m_ScriptInterface->LoadGlobalScriptFile(p))
     294             :             {
     295           0 :                 LOGERROR("CMapGeneratorWorker::LoadScripts: Failed to load script '%s'", p.string8());
     296           0 :                 return false;
     297             :             }
     298             :         }
     299             :     }
     300             :     else
     301             :     {
     302             :         // Some error reading directory
     303             :         wchar_t error[200];
     304           0 :         LOGERROR("CMapGeneratorWorker::LoadScripts: Error reading scripts in directory '%s': %s", path.string8(), utf8_from_wstring(StatusDescription(ret, error, ARRAY_SIZE(error))));
     305           0 :         return false;
     306             :     }
     307             : 
     308          24 :     return true;
     309             : }
     310             : 
     311           0 : JS::Value CMapGeneratorWorker::LoadHeightmap(const VfsPath& filename)
     312             : {
     313           0 :     std::vector<u16> heightmap;
     314           0 :     if (LoadHeightmapImageVfs(filename, heightmap) != INFO::OK)
     315             :     {
     316           0 :         LOGERROR("Could not load heightmap file '%s'", filename.string8());
     317           0 :         return JS::UndefinedValue();
     318             :     }
     319             : 
     320           0 :     ScriptRequest rq(m_ScriptInterface);
     321           0 :     JS::RootedValue returnValue(rq.cx);
     322           0 :     Script::ToJSVal(rq, &returnValue, heightmap);
     323           0 :     return returnValue;
     324             : }
     325             : 
     326             : // See CMapReader::UnpackTerrain, CMapReader::ParseTerrain for the reordering
     327           0 : JS::Value CMapGeneratorWorker::LoadMapTerrain(const VfsPath& filename)
     328             : {
     329           0 :     ScriptRequest rq(m_ScriptInterface);
     330             : 
     331           0 :     if (!VfsFileExists(filename))
     332             :     {
     333           0 :         ScriptException::Raise(rq, "Terrain file \"%s\" does not exist!", filename.string8().c_str());
     334           0 :         return JS::UndefinedValue();
     335             :     }
     336             : 
     337           0 :     CFileUnpacker unpacker;
     338           0 :     unpacker.Read(filename, "PSMP");
     339             : 
     340           0 :     if (unpacker.GetVersion() < CMapIO::FILE_READ_VERSION)
     341             :     {
     342           0 :         ScriptException::Raise(rq, "Could not load terrain file \"%s\" too old version!", filename.string8().c_str());
     343           0 :         return JS::UndefinedValue();
     344             :     }
     345             : 
     346             :     // unpack size
     347           0 :     ssize_t patchesPerSide = (ssize_t)unpacker.UnpackSize();
     348           0 :     size_t verticesPerSide = patchesPerSide * PATCH_SIZE + 1;
     349             : 
     350             :     // unpack heightmap
     351           0 :     std::vector<u16> heightmap;
     352           0 :     heightmap.resize(SQR(verticesPerSide));
     353           0 :     unpacker.UnpackRaw(&heightmap[0], SQR(verticesPerSide) * sizeof(u16));
     354             : 
     355             :     // unpack texture names
     356           0 :     size_t textureCount = unpacker.UnpackSize();
     357           0 :     std::vector<std::string> textureNames;
     358           0 :     textureNames.reserve(textureCount);
     359           0 :     for (size_t i = 0; i < textureCount; ++i)
     360             :     {
     361           0 :         CStr texturename;
     362           0 :         unpacker.UnpackString(texturename);
     363           0 :         textureNames.push_back(texturename);
     364             :     }
     365             : 
     366             :     // unpack texture IDs per tile
     367           0 :     ssize_t tilesPerSide = patchesPerSide * PATCH_SIZE;
     368           0 :     std::vector<CMapIO::STileDesc> tiles;
     369           0 :     tiles.resize(size_t(SQR(tilesPerSide)));
     370           0 :     unpacker.UnpackRaw(&tiles[0], sizeof(CMapIO::STileDesc) * tiles.size());
     371             : 
     372             :     // reorder by patches and store and save texture IDs per tile
     373           0 :     std::vector<u16> textureIDs;
     374           0 :     for (ssize_t x = 0; x < tilesPerSide; ++x)
     375             :     {
     376           0 :         size_t patchX = x / PATCH_SIZE;
     377           0 :         size_t offX = x % PATCH_SIZE;
     378           0 :         for (ssize_t y = 0; y < tilesPerSide; ++y)
     379             :         {
     380           0 :             size_t patchY = y / PATCH_SIZE;
     381           0 :             size_t offY = y % PATCH_SIZE;
     382             :             // m_Priority and m_Tex2Index unused
     383           0 :             textureIDs.push_back(tiles[(patchY * patchesPerSide + patchX) * SQR(PATCH_SIZE) + (offY * PATCH_SIZE + offX)].m_Tex1Index);
     384             :         }
     385             :     }
     386             : 
     387           0 :     JS::RootedValue returnValue(rq.cx);
     388             : 
     389           0 :     Script::CreateObject(
     390             :         rq,
     391             :         &returnValue,
     392             :         "height", heightmap,
     393             :         "textureNames", textureNames,
     394             :         "textureIDs", textureIDs);
     395             : 
     396           0 :     return returnValue;
     397             : }
     398             : 
     399             : //////////////////////////////////////////////////////////////////////////////////
     400             : //////////////////////////////////////////////////////////////////////////////////
     401             : 
     402           0 : CMapGenerator::CMapGenerator() : m_Worker(new CMapGeneratorWorker(nullptr))
     403             : {
     404           0 : }
     405             : 
     406           0 : CMapGenerator::~CMapGenerator()
     407             : {
     408           0 :     delete m_Worker;
     409           0 : }
     410             : 
     411           0 : void CMapGenerator::GenerateMap(const VfsPath& scriptFile, const std::string& settings)
     412             : {
     413           0 :     m_Worker->Initialize(scriptFile, settings);
     414           0 : }
     415             : 
     416           0 : int CMapGenerator::GetProgress()
     417             : {
     418           0 :     return m_Worker->GetProgress();
     419             : }
     420             : 
     421           0 : Script::StructuredClone CMapGenerator::GetResults()
     422             : {
     423           0 :     return m_Worker->GetResults();
     424           3 : }

Generated by: LCOV version 1.13