1 /* Copyright (C) 2018 Wildfire Games.
2 * This file is part of 0 A.D.
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.
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.
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/>.
18 #include "precompiled.h"
20 #include "MapGenerator.h"
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 "maths/MathUtil.h"
29 #include "ps/CLogger.h"
30 #include "ps/FileIo.h"
31 #include "ps/Profile.h"
32 #include "ps/scripting/JSInterface_VFS.h"
33 #include "scriptinterface/ScriptRuntime.h"
34 #include "scriptinterface/ScriptConversions.h"
36 // TODO: what's a good default? perhaps based on map size
37 #define RMS_RUNTIME_SIZE 96 * 1024 * 1024
40 MapGeneratorInterruptCallback(JSContext
* UNUSED(cx
))
42 if (SDL_QuitRequested())
44 LOGWARNING("Quit requested!");
51 CMapGeneratorWorker::CMapGeneratorWorker()
53 // If something happens before we initialize, that's a failure
57 CMapGeneratorWorker::~CMapGeneratorWorker()
59 // Wait for thread to end
60 pthread_join(m_WorkerThread
, NULL
);
63 void CMapGeneratorWorker::Initialize(const VfsPath
& scriptFile
, const std::string
& settings
)
65 CScopeLock
lock(m_WorkerMutex
);
67 // Set progress to positive value
69 m_ScriptPath
= scriptFile
;
70 m_Settings
= settings
;
72 // Launch the worker thread
73 int ret
= pthread_create(&m_WorkerThread
, NULL
, &RunThread
, this);
77 void* CMapGeneratorWorker::RunThread(void *data
)
79 debug_SetThreadName("MapGenerator");
80 g_Profiler2
.RegisterCurrentThread("MapGenerator");
82 CMapGeneratorWorker
* self
= static_cast<CMapGeneratorWorker
*>(data
);
84 shared_ptr
<ScriptRuntime
> mapgenRuntime
= ScriptInterface::CreateRuntime(g_ScriptRuntime
, RMS_RUNTIME_SIZE
);
86 // Enable the script to be aborted
87 JS_SetInterruptCallback(mapgenRuntime
->m_rt
, MapGeneratorInterruptCallback
);
89 self
->m_ScriptInterface
= new ScriptInterface("Engine", "MapGenerator", mapgenRuntime
);
91 // Run map generation scripts
92 if (!self
->Run() || self
->m_Progress
> 0)
94 // Don't leave progress in an unknown state, if generator failed, set it to -1
95 CScopeLock
lock(self
->m_WorkerMutex
);
96 self
->m_Progress
= -1;
99 // At this point the random map scripts are done running, so the thread has no further purpose
100 // and can die. The data will be stored in m_MapData already if successful, or m_Progress
101 // will contain an error value on failure.
106 bool CMapGeneratorWorker::Run()
108 // We must destroy the ScriptInterface in the same thread because the JSAPI requires that!
109 // Also we must not be in a request when calling the ScriptInterface destructor, so the autoFree object
110 // must be instantiated before the request (destructors are called in reverse order of instantiation)
112 AutoFree(ScriptInterface
* p
) : m_p(p
) {}
113 ~AutoFree() { SAFE_DELETE(m_p
); }
114 ScriptInterface
* m_p
;
115 } autoFree(m_ScriptInterface
);
117 JSContext
* cx
= m_ScriptInterface
->GetContext();
118 JSAutoRequest
rq(cx
);
120 m_ScriptInterface
->SetCallbackData(static_cast<void*> (this));
122 // Replace RNG with a seeded deterministic function
123 m_ScriptInterface
->ReplaceNondeterministicRNG(m_MapGenRNG
);
126 JSI_VFS::RegisterScriptFunctions_Maps(*m_ScriptInterface
);
127 m_ScriptInterface
->RegisterFunction
<bool, std::wstring
, CMapGeneratorWorker::LoadLibrary
>("LoadLibrary");
128 m_ScriptInterface
->RegisterFunction
<JS::Value
, std::wstring
, CMapGeneratorWorker::LoadHeightmap
>("LoadHeightmapImage");
129 m_ScriptInterface
->RegisterFunction
<JS::Value
, std::string
, CMapGeneratorWorker::LoadMapTerrain
>("LoadMapTerrain");
130 m_ScriptInterface
->RegisterFunction
<void, JS::HandleValue
, CMapGeneratorWorker::ExportMap
>("ExportMap");
131 m_ScriptInterface
->RegisterFunction
<void, int, CMapGeneratorWorker::SetProgress
>("SetProgress");
132 m_ScriptInterface
->RegisterFunction
<CParamNode
, std::string
, CMapGeneratorWorker::GetTemplate
>("GetTemplate");
133 m_ScriptInterface
->RegisterFunction
<bool, std::string
, CMapGeneratorWorker::TemplateExists
>("TemplateExists");
134 m_ScriptInterface
->RegisterFunction
<std::vector
<std::string
>, std::string
, bool, CMapGeneratorWorker::FindTemplates
>("FindTemplates");
135 m_ScriptInterface
->RegisterFunction
<std::vector
<std::string
>, std::string
, bool, CMapGeneratorWorker::FindActorTemplates
>("FindActorTemplates");
136 m_ScriptInterface
->RegisterFunction
<int, CMapGeneratorWorker::GetTerrainTileSize
>("GetTerrainTileSize");
138 // Globalscripts may use VFS script functions
139 m_ScriptInterface
->LoadGlobalScripts();
142 JS::RootedValue
settingsVal(cx
);
143 if (!m_ScriptInterface
->ParseJSON(m_Settings
, &settingsVal
) && settingsVal
.isUndefined())
145 LOGERROR("CMapGeneratorWorker::Run: Failed to parse settings");
149 // Prevent unintentional modifications to the settings object by random map scripts
150 if (!m_ScriptInterface
->FreezeObject(settingsVal
, true))
152 LOGERROR("CMapGeneratorWorker::Run: Failed to deepfreeze settings");
158 if (!m_ScriptInterface
->HasProperty(settingsVal
, "Seed") ||
159 !m_ScriptInterface
->GetProperty(settingsVal
, "Seed", seed
))
160 LOGWARNING("CMapGeneratorWorker::Run: No seed value specified - using 0");
162 m_MapGenRNG
.seed(seed
);
164 // Copy settings to global variable
165 JS::RootedValue
global(cx
, m_ScriptInterface
->GetGlobalObject());
166 if (!m_ScriptInterface
->SetProperty(global
, "g_MapSettings", settingsVal
, true, true))
168 LOGERROR("CMapGeneratorWorker::Run: Failed to define g_MapSettings");
173 LOGMESSAGE("Loading RMS '%s'", m_ScriptPath
.string8());
174 if (!m_ScriptInterface
->LoadGlobalScriptFile(m_ScriptPath
))
176 LOGERROR("CMapGeneratorWorker::Run: Failed to load RMS '%s'", m_ScriptPath
.string8());
183 int CMapGeneratorWorker::GetProgress()
185 CScopeLock
lock(m_WorkerMutex
);
189 shared_ptr
<ScriptInterface::StructuredClone
> CMapGeneratorWorker::GetResults()
191 CScopeLock
lock(m_WorkerMutex
);
195 bool CMapGeneratorWorker::LoadLibrary(ScriptInterface::CxPrivate
* pCxPrivate
, const std::wstring
& name
)
197 CMapGeneratorWorker
* self
= static_cast<CMapGeneratorWorker
*>(pCxPrivate
->pCBData
);
198 return self
->LoadScripts(name
);
201 void CMapGeneratorWorker::ExportMap(ScriptInterface::CxPrivate
* pCxPrivate
, JS::HandleValue data
)
203 CMapGeneratorWorker
* self
= static_cast<CMapGeneratorWorker
*>(pCxPrivate
->pCBData
);
206 CScopeLock
lock(self
->m_WorkerMutex
);
207 self
->m_MapData
= self
->m_ScriptInterface
->WriteStructuredClone(data
);
208 self
->m_Progress
= 0;
211 void CMapGeneratorWorker::SetProgress(ScriptInterface::CxPrivate
* pCxPrivate
, int progress
)
213 CMapGeneratorWorker
* self
= static_cast<CMapGeneratorWorker
*>(pCxPrivate
->pCBData
);
216 CScopeLock
lock(self
->m_WorkerMutex
);
217 self
->m_Progress
= progress
;
220 CParamNode
CMapGeneratorWorker::GetTemplate(ScriptInterface::CxPrivate
* pCxPrivate
, const std::string
& templateName
)
222 CMapGeneratorWorker
* self
= static_cast<CMapGeneratorWorker
*>(pCxPrivate
->pCBData
);
223 const CParamNode
& templateRoot
= self
->m_TemplateLoader
.GetTemplateFileData(templateName
).GetChild("Entity");
224 if (!templateRoot
.IsOk())
225 LOGERROR("Invalid template found for '%s'", templateName
.c_str());
230 bool CMapGeneratorWorker::TemplateExists(ScriptInterface::CxPrivate
* pCxPrivate
, const std::string
& templateName
)
232 CMapGeneratorWorker
* self
= static_cast<CMapGeneratorWorker
*>(pCxPrivate
->pCBData
);
233 return self
->m_TemplateLoader
.TemplateExists(templateName
);
236 std::vector
<std::string
> CMapGeneratorWorker::FindTemplates(ScriptInterface::CxPrivate
* pCxPrivate
, const std::string
& path
, bool includeSubdirectories
)
238 CMapGeneratorWorker
* self
= static_cast<CMapGeneratorWorker
*>(pCxPrivate
->pCBData
);
239 return self
->m_TemplateLoader
.FindTemplates(path
, includeSubdirectories
, SIMULATION_TEMPLATES
);
242 std::vector
<std::string
> CMapGeneratorWorker::FindActorTemplates(ScriptInterface::CxPrivate
* pCxPrivate
, const std::string
& path
, bool includeSubdirectories
)
244 CMapGeneratorWorker
* self
= static_cast<CMapGeneratorWorker
*>(pCxPrivate
->pCBData
);
245 return self
->m_TemplateLoader
.FindTemplates(path
, includeSubdirectories
, ACTOR_TEMPLATES
);
248 int CMapGeneratorWorker::GetTerrainTileSize(ScriptInterface::CxPrivate
* UNUSED(pCxPrivate
))
250 return TERRAIN_TILE_SIZE
;
253 bool CMapGeneratorWorker::LoadScripts(const std::wstring
& libraryName
)
255 // Ignore libraries that are already loaded
256 if (m_LoadedLibraries
.find(libraryName
) != m_LoadedLibraries
.end())
259 // Mark this as loaded, to prevent it recursively loading itself
260 m_LoadedLibraries
.insert(libraryName
);
262 VfsPath path
= L
"maps/random/" + libraryName
+ L
"/";
265 // Load all scripts in mapgen directory
266 Status ret
= vfs::GetPathnames(g_VFS
, path
, L
"*.js", pathnames
);
269 for (const VfsPath
& p
: pathnames
)
271 LOGMESSAGE("Loading map generator script '%s'", p
.string8());
273 if (!m_ScriptInterface
->LoadGlobalScriptFile(p
))
275 LOGERROR("CMapGeneratorWorker::LoadScripts: Failed to load script '%s'", p
.string8());
282 // Some error reading directory
284 LOGERROR("CMapGeneratorWorker::LoadScripts: Error reading scripts in directory '%s': %s", path
.string8(), utf8_from_wstring(StatusDescription(ret
, error
, ARRAY_SIZE(error
))));
291 JS::Value
CMapGeneratorWorker::LoadHeightmap(ScriptInterface::CxPrivate
* pCxPrivate
, const std::wstring
& vfsPath
)
294 if (g_VFS
->GetRealPath(vfsPath
, realPath
) != INFO::OK
)
295 return JS::UndefinedValue();
297 std::vector
<u16
> heightmap
;
298 if (LoadHeightmapImage(realPath
, heightmap
) != INFO::OK
)
300 LOGERROR("Could not load heightmap file '%s'", utf8_from_wstring(vfsPath
).c_str());
301 return JS::UndefinedValue();
304 CMapGeneratorWorker
* self
= static_cast<CMapGeneratorWorker
*>(pCxPrivate
->pCBData
);
305 JSContext
* cx
= self
->m_ScriptInterface
->GetContext();
306 JSAutoRequest
rq(cx
);
307 JS::RootedValue
returnValue(cx
);
308 ToJSVal_vector(cx
, &returnValue
, heightmap
);
312 // See CMapReader::UnpackTerrain, CMapReader::ParseTerrain for the reordering
313 JS::Value
CMapGeneratorWorker::LoadMapTerrain(ScriptInterface::CxPrivate
* pCxPrivate
, const std::string
& filename
)
315 if (!VfsFileExists(filename
))
316 throw PSERROR_File_OpenFailed();
318 CFileUnpacker unpacker
;
319 unpacker
.Read(filename
, "PSMP");
321 if (unpacker
.GetVersion() < CMapIO::FILE_READ_VERSION
)
322 throw PSERROR_File_InvalidVersion();
325 ssize_t patchesPerSide
= (ssize_t
)unpacker
.UnpackSize();
326 size_t verticesPerSide
= patchesPerSide
* PATCH_SIZE
+ 1;
329 std::vector
<u16
> heightmap
;
330 heightmap
.resize(SQR(verticesPerSide
));
331 unpacker
.UnpackRaw(&heightmap
[0], SQR(verticesPerSide
) * sizeof(u16
));
333 // unpack texture names
334 size_t textureCount
= unpacker
.UnpackSize();
335 std::vector
<std::string
> textureNames
;
336 textureNames
.reserve(textureCount
);
337 for (size_t i
= 0; i
< textureCount
; ++i
)
340 unpacker
.UnpackString(texturename
);
341 textureNames
.push_back(texturename
);
344 // unpack texture IDs per tile
345 ssize_t tilesPerSide
= patchesPerSide
* PATCH_SIZE
;
346 std::vector
<CMapIO::STileDesc
> tiles
;
347 tiles
.resize(size_t(SQR(tilesPerSide
)));
348 unpacker
.UnpackRaw(&tiles
[0], sizeof(CMapIO::STileDesc
) * tiles
.size());
350 // reorder by patches and store and save texture IDs per tile
351 std::vector
<u16
> textureIDs
;
352 for (ssize_t x
= 0; x
< tilesPerSide
; ++x
)
354 size_t patchX
= x
/ PATCH_SIZE
;
355 size_t offX
= x
% PATCH_SIZE
;
356 for (ssize_t y
= 0; y
< tilesPerSide
; ++y
)
358 size_t patchY
= y
/ PATCH_SIZE
;
359 size_t offY
= y
% PATCH_SIZE
;
360 // m_Priority and m_Tex2Index unused
361 textureIDs
.push_back(tiles
[(patchY
* patchesPerSide
+ patchX
) * SQR(PATCH_SIZE
) + (offY
* PATCH_SIZE
+ offX
)].m_Tex1Index
);
365 CMapGeneratorWorker
* self
= static_cast<CMapGeneratorWorker
*>(pCxPrivate
->pCBData
);
366 JSContext
* cx
= self
->m_ScriptInterface
->GetContext();
367 JSAutoRequest
rq(cx
);
368 JS::RootedValue
returnValue(cx
);
369 self
->m_ScriptInterface
->Eval("({})", &returnValue
);
370 self
->m_ScriptInterface
->SetProperty(returnValue
, "height", heightmap
);
371 self
->m_ScriptInterface
->SetProperty(returnValue
, "textureNames", textureNames
);
372 self
->m_ScriptInterface
->SetProperty(returnValue
, "textureIDs", textureIDs
);
377 //////////////////////////////////////////////////////////////////////////////////
378 //////////////////////////////////////////////////////////////////////////////////
380 CMapGenerator::CMapGenerator() : m_Worker(new CMapGeneratorWorker())
384 CMapGenerator::~CMapGenerator()
389 void CMapGenerator::GenerateMap(const VfsPath
& scriptFile
, const std::string
& settings
)
391 m_Worker
->Initialize(scriptFile
, settings
);
394 int CMapGenerator::GetProgress()
396 return m_Worker
->GetProgress();
399 shared_ptr
<ScriptInterface::StructuredClone
> CMapGenerator::GetResults()
401 return m_Worker
->GetResults();