Index: ps/trunk/binaries/data/mods/public/globalscripts/Resources.js =================================================================== --- ps/trunk/binaries/data/mods/public/globalscripts/Resources.js (revision 20575) +++ ps/trunk/binaries/data/mods/public/globalscripts/Resources.js (revision 20576) @@ -1,103 +1,103 @@ /** * Since the AI context can't access JSON functions, it gets passed an object * containing the information from `GuiInterface.js::GetSimulationState()`. */ function Resources() { let jsonFiles = []; // Simulation context if (Engine.FindJSONFiles) { jsonFiles = Engine.FindJSONFiles("resources", false); for (let file in jsonFiles) - jsonFiles[file] = "resources/" + jsonFiles[file] + ".json"; + jsonFiles[file] = "simulation/data/resources/" + jsonFiles[file] + ".json"; } // GUI context else if (Engine.BuildDirEntList) jsonFiles = Engine.BuildDirEntList("simulation/data/resources/", "*.json", false); else { error("Resources: JSON functions are not available"); return; } this.resourceData = []; this.resourceDataObj = {}; this.resourceCodes = []; this.resourceNames = {}; for (let filename of jsonFiles) { let data = Engine.ReadJSONFile(filename); if (!data) continue; if (data.code != data.code.toLowerCase()) warn("Resource codes should use lower case: " + data.code); // Treasures are supported for every specified resource if (data.code == "treasure") { error("Encountered resource with reserved keyword: " + data.code); continue; } this.resourceData.push(data); this.resourceDataObj[data.code] = data; this.resourceCodes.push(data.code); this.resourceNames[data.code] = data.name; for (let subres in data.subtypes) this.resourceNames[subres] = data.subtypes[subres]; } // Sort arrays by specified order let resSort = (a, b) => a.order < b.order ? -1 : a.order > b.order ? +1 : 0; this.resourceData.sort(resSort); this.resourceCodes.sort((a, b) => resSort( this.resourceData.find(resource => resource.code == a), this.resourceData.find(resource => resource.code == b) )); deepfreeze(this.resourceData); deepfreeze(this.resourceDataObj); deepfreeze(this.resourceCodes); deepfreeze(this.resourceNames); } /** * Returns the objects defined in the JSON files for all availbale resources, * ordered as defined in these files. */ Resources.prototype.GetResources = function() { return this.resourceData; }; /** * Returns the object defined in the JSON file for the given resource. */ Resources.prototype.GetResource = function(type) { return this.resourceDataObj[type]; }; /** * Returns an array containing all resource codes ordered as defined in the resource files. * For example ["food", "wood", "stone", "metal"]. */ Resources.prototype.GetCodes = function() { return this.resourceCodes; }; /** * Returns an object mapping resource codes to translatable resource names. Includes subtypes. * For example { "food": "Food", "fish": "Fish", "fruit": "Fruit", "metal": "Metal", ... } */ Resources.prototype.GetNames = function() { return this.resourceNames; }; Index: ps/trunk/binaries/data/mods/public/simulation/components/DataTemplateManager.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/DataTemplateManager.js (revision 20575) +++ ps/trunk/binaries/data/mods/public/simulation/components/DataTemplateManager.js (revision 20576) @@ -1,68 +1,68 @@ /** * System component which loads the technology and the aura data files */ function DataTemplateManager() {} DataTemplateManager.prototype.Schema = ""; DataTemplateManager.prototype.Init = function() { this.allTechs = {}; this.allAuras = {}; for (let techName of this.ListAllTechs()) this.GetTechnologyTemplate(techName); for (let auraName of this.ListAllAuras()) this.GetAuraTemplate(auraName); deepfreeze(this.allTechs); deepfreeze(this.allAuras); }; DataTemplateManager.prototype.GetTechnologyTemplate = function(template) { if (!this.allTechs[template]) { - this.allTechs[template] = Engine.ReadJSONFile("technologies/" + template + ".json"); + this.allTechs[template] = Engine.ReadJSONFile("simulation/data/technologies/" + template + ".json"); if (!this.allTechs[template]) error("Failed to load technology \"" + template + "\""); } return this.allTechs[template]; }; DataTemplateManager.prototype.GetAuraTemplate = function(template) { if (!this.allAuras[template]) { - this.allAuras[template] = Engine.ReadJSONFile("auras/" + template + ".json"); + this.allAuras[template] = Engine.ReadJSONFile("simulation/data/auras/" + template + ".json"); if (!this.allAuras[template]) error("Failed to load aura \"" + template + "\""); } return this.allAuras[template]; }; DataTemplateManager.prototype.ListAllTechs = function() { return Engine.FindJSONFiles("technologies", true); }; DataTemplateManager.prototype.ListAllAuras = function() { return Engine.FindJSONFiles("auras", true); }; DataTemplateManager.prototype.GetAllTechs = function() { return this.allTechs; }; DataTemplateManager.prototype.TechFileExists = function(template) { return Engine.DataFileExists("technologies/" + template + ".json"); }; Engine.RegisterSystemComponentType(IID_DataTemplateManager, "DataTemplateManager", DataTemplateManager); Index: ps/trunk/binaries/data/mods/public/simulation/components/SkirmishReplacer.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/SkirmishReplacer.js (revision 20575) +++ ps/trunk/binaries/data/mods/public/simulation/components/SkirmishReplacer.js (revision 20576) @@ -1,107 +1,102 @@ function SkirmishReplacer() {} SkirmishReplacer.prototype.Schema = "" + "" + "" + "" + "" + "" + ""; SkirmishReplacer.prototype.Init = function() { }; SkirmishReplacer.prototype.Serialize = null; // We have no dynamic state to save -//this function gets the replacement entities from the {civ}.json file function getReplacementEntities(civ) { - var rawCivData = Engine.ReadCivJSONFile(civ+".json"); - if (rawCivData && rawCivData.SkirmishReplacements) - return rawCivData.SkirmishReplacements; - warn("SkirmishReplacer.js: no replacements found in '"+civ+".json'"); - return {}; + return Engine.ReadJSONFile("simulation/data/civs/" + civ + ".json").SkirmishReplacements; } SkirmishReplacer.prototype.OnOwnershipChanged = function(msg) { if (msg.to == 0) warn("Skirmish map elements can only be owned by regular players. Please delete entity "+this.entity+" or change the ownership to a non-gaia player."); }; SkirmishReplacer.prototype.ReplaceEntities = function() { var cmpPlayer = QueryOwnerInterface(this.entity); var civ = cmpPlayer.GetCiv(); var replacementEntities = getReplacementEntities(civ); var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); var templateName = cmpTemplateManager.GetCurrentTemplateName(this.entity); let specialFilter = ""; let specialFilterPos = templateName.lastIndexOf("|"); if (specialFilterPos != -1) { specialFilter = templateName.substr(0, specialFilterPos + 1); templateName = templateName.substr(specialFilterPos); } if (templateName in replacementEntities) templateName = replacementEntities[templateName]; else if (this.template && "general" in this.template) templateName = this.template.general; else templateName = ""; if (!templateName || civ == "gaia") { Engine.DestroyEntity(this.entity); return; } templateName = specialFilter + templateName.replace(/\{civ\}/g, civ); var cmpCurPosition = Engine.QueryInterface(this.entity, IID_Position); var replacement = Engine.AddEntity(templateName); if (!replacement) { Engine.DestroyEntity(this.entity); return; } var cmpReplacementPosition = Engine.QueryInterface(replacement, IID_Position); var pos = cmpCurPosition.GetPosition2D(); cmpReplacementPosition.JumpTo(pos.x, pos.y); var rot = cmpCurPosition.GetRotation(); cmpReplacementPosition.SetYRotation(rot.y); var cmpCurOwnership = Engine.QueryInterface(this.entity, IID_Ownership); var cmpReplacementOwnership = Engine.QueryInterface(replacement, IID_Ownership); cmpReplacementOwnership.SetOwner(cmpCurOwnership.GetOwner()); Engine.PostMessage(this.entity, MT_EntityRenamed, { "entity": this.entity, "newentity": replacement }); Engine.DestroyEntity(this.entity); }; /** * Replace this entity with a civ-specific entity * Message is sent right before InitGame() is called, in InitGame.js * Replacement needs to happen early on real games to not confuse the AI */ SkirmishReplacer.prototype.OnSkirmishReplace = function(msg) { this.ReplaceEntities(); }; /** * Replace this entity with a civ-specific entity * This is needed for Atlas, when the entity isn't replaced before the game starts, * so it needs to be replaced on the first turn. */ SkirmishReplacer.prototype.OnUpdate = function(msg) { this.ReplaceEntities(); }; Engine.RegisterComponentType(IID_SkirmishReplacer, "SkirmishReplacer", SkirmishReplacer); Index: ps/trunk/binaries/data/mods/public/simulation/helpers/Player.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/helpers/Player.js (revision 20575) +++ ps/trunk/binaries/data/mods/public/simulation/helpers/Player.js (revision 20576) @@ -1,347 +1,334 @@ /** * Used to create player entities prior to reading the rest of a map, * all other initialization must be done after loading map (terrain/entities). * DO NOT use other components here, as they may fail unpredictably. * settings is the object containing settings for this map. * newPlayers if true will remove old player entities or add new ones until * the new number of player entities is obtained * (used when loading a map or when Atlas changes the number of players). */ function LoadPlayerSettings(settings, newPlayers) { + var playerDefaults = Engine.ReadJSONFile("simulation/data/settings/player_defaults.json").PlayerData; + // Default settings if (!settings) settings = {}; - // Get default player data - var rawData = Engine.ReadJSONFile("settings/player_defaults.json"); - if (!(rawData && rawData.PlayerData)) - throw new Error("Player.js: Error reading player_defaults.json"); - // Add gaia to simplify iteration if (settings.PlayerData && settings.PlayerData[0]) settings.PlayerData.unshift(null); - var playerDefaults = rawData.PlayerData; var playerData = settings.PlayerData; var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); var numPlayers = cmpPlayerManager.GetNumPlayers(); var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); // Remove existing players or add new ones if (newPlayers) { var settingsNumPlayers = 9; // default 8 players + gaia if (playerData) settingsNumPlayers = playerData.length; // includes gaia (see above) else warn("Player.js: Setup has no player data - using defaults"); while (settingsNumPlayers > numPlayers) { // Add player entity to engine var civ = getSetting(playerData, playerDefaults, numPlayers, "Civ"); var template = cmpTemplateManager.TemplateExists("special/player_"+civ) ? "special/player_"+civ : "special/player"; var entID = Engine.AddEntity(template); var cmpPlayer = Engine.QueryInterface(entID, IID_Player); if (!cmpPlayer) throw new Error("Player.js: Error creating player entity " + numPlayers); cmpPlayerManager.AddPlayer(entID); ++numPlayers; } while (settingsNumPlayers < numPlayers) { cmpPlayerManager.RemoveLastPlayer(); --numPlayers; } } // Even when no new player, we must check the template compatibility as player template may be civ dependent for (var i = 0; i < numPlayers; ++i) { var civ = getSetting(playerData, playerDefaults, i, "Civ"); var template = cmpTemplateManager.TemplateExists("special/player_"+civ) ? "special/player_"+civ : "special/player"; var entID = cmpPlayerManager.GetPlayerByID(i); if (cmpTemplateManager.GetCurrentTemplateName(entID) === template) continue; // We need to recreate this player to have the right template entID = Engine.AddEntity(template); cmpPlayerManager.ReplacePlayer(i, entID); } // Initialize the player data for (var i = 0; i < numPlayers; ++i) { let cmpPlayer = QueryPlayerIDInterface(i); cmpPlayer.SetName(getSetting(playerData, playerDefaults, i, "Name")); cmpPlayer.SetCiv(getSetting(playerData, playerDefaults, i, "Civ")); var color = getSetting(playerData, playerDefaults, i, "Color"); cmpPlayer.SetColor(color.r, color.g, color.b); // Special case for gaia if (i == 0) { // Gaia should be its own ally. cmpPlayer.SetAlly(0); // Gaia is everyone's enemy for (var j = 1; j < numPlayers; ++j) cmpPlayer.SetEnemy(j); continue; } // Note: this is not yet implemented but I leave it commented to highlight it's easy // If anyone ever adds handicap. //if (getSetting(playerData, playerDefaults, i, "GatherRateMultiplier") !== undefined) // cmpPlayer.SetGatherRateMultiplier(getSetting(playerData, playerDefaults, i, "GatherRateMultiplier")); if (getSetting(playerData, playerDefaults, i, "PopulationLimit") !== undefined) cmpPlayer.SetMaxPopulation(getSetting(playerData, playerDefaults, i, "PopulationLimit")); if (getSetting(playerData, playerDefaults, i, "Resources") !== undefined) cmpPlayer.SetResourceCounts(getSetting(playerData, playerDefaults, i, "Resources")); if (getSetting(playerData, playerDefaults, i, "StartingTechnologies") !== undefined) cmpPlayer.SetStartingTechnologies(getSetting(playerData, playerDefaults, i, "StartingTechnologies")); if (getSetting(playerData, playerDefaults, i, "DisabledTechnologies") !== undefined) cmpPlayer.SetDisabledTechnologies(getSetting(playerData, playerDefaults, i, "DisabledTechnologies")); let disabledTemplates = []; if (settings.DisabledTemplates !== undefined) disabledTemplates = settings.DisabledTemplates; if (getSetting(playerData, playerDefaults, i, "DisabledTemplates") !== undefined) disabledTemplates = disabledTemplates.concat(getSetting(playerData, playerDefaults, i, "DisabledTemplates")); if (disabledTemplates.length) cmpPlayer.SetDisabledTemplates(disabledTemplates); if (settings.DisableSpies) { cmpPlayer.AddDisabledTechnology("unlock_spies"); cmpPlayer.AddDisabledTemplate("special/spy"); } // If diplomacy explicitly defined, use that; otherwise use teams if (getSetting(playerData, playerDefaults, i, "Diplomacy") !== undefined) cmpPlayer.SetDiplomacy(getSetting(playerData, playerDefaults, i, "Diplomacy")); else { // Init diplomacy var myTeam = getSetting(playerData, playerDefaults, i, "Team"); // Set all but self as enemies as SetTeam takes care of allies for (var j = 0; j < numPlayers; ++j) { if (i == j) cmpPlayer.SetAlly(j); else cmpPlayer.SetEnemy(j); } cmpPlayer.SetTeam(myTeam === undefined ? -1 : myTeam); } - // If formations explicitly defined, use that; otherwise use civ defaults - var formations = getSetting(playerData, playerDefaults, i, "Formations"); - if (formations !== undefined) - cmpPlayer.SetFormations(formations); - else - { - var rawFormations = Engine.ReadCivJSONFile(cmpPlayer.GetCiv()+".json"); - if (!(rawFormations && rawFormations.Formations)) - throw new Error("Player.js: Error reading " + cmpPlayer.GetCiv() + ".json"); - - cmpPlayer.SetFormations(rawFormations.Formations); - } + cmpPlayer.SetFormations( + getSetting(playerData, playerDefaults, i, "Formations") || + Engine.ReadJSONFile("simulation/data/civs/" + cmpPlayer.GetCiv() + ".json").Formations); var startCam = getSetting(playerData, playerDefaults, i, "StartingCamera"); if (startCam !== undefined) cmpPlayer.SetStartingCamera(startCam.Position, startCam.Rotation); } // NOTE: We need to do the team locking here, as otherwise // SetTeam can't ally the players. if (settings.LockTeams) for (var i = 0; i < numPlayers; ++i) { let cmpPlayer = QueryPlayerIDInterface(i); cmpPlayer.SetLockTeams(true); } // Disable the AIIinterface when no AI players are present if (playerData && !playerData.some(v => v && !!v.AI)) Engine.QueryInterface(SYSTEM_ENTITY, IID_AIInterface).Disable(); } // Get a setting if it exists or return default function getSetting(settings, defaults, idx, property) { if (settings && settings[idx] && (property in settings[idx])) return settings[idx][property]; // Use defaults if (defaults && defaults[idx] && (property in defaults[idx])) return defaults[idx][property]; return undefined; } /** * Similar to Engine.QueryInterface but applies to the player entity * that owns the given entity. * iid is typically IID_Player. */ function QueryOwnerInterface(ent, iid = IID_Player) { var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); if (!cmpOwnership) return null; var owner = cmpOwnership.GetOwner(); if (owner == -1) return null; return QueryPlayerIDInterface(owner, iid); } /** * Similar to Engine.QueryInterface but applies to the player entity * with the given ID number. * iid is typically IID_Player. */ function QueryPlayerIDInterface(id, iid = IID_Player) { var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); var playerEnt = cmpPlayerManager.GetPlayerByID(id); if (!playerEnt) return null; return Engine.QueryInterface(playerEnt, iid); } /** * Similar to Engine.QueryInterface but first checks if the entity * mirages the interface. */ function QueryMiragedInterface(ent, iid) { var cmp = Engine.QueryInterface(ent, IID_Mirage); if (cmp && !cmp.Mirages(iid)) return null; else if (!cmp) cmp = Engine.QueryInterface(ent, iid); return cmp; } /** * Similar to Engine.QueryInterface, but checks for all interfaces * implementing a builder list (currently Foundation and Repairable) * TODO Foundation and Repairable could both implement a BuilderList component */ function QueryBuilderListInterface(ent) { return Engine.QueryInterface(ent, IID_Foundation) || Engine.QueryInterface(ent, IID_Repairable); } /** * Returns true if the entity 'target' is owned by an ally of * the owner of 'entity'. */ function IsOwnedByAllyOfEntity(entity, target) { return IsOwnedByEntityHelper(entity, target, "IsAlly"); } function IsOwnedByMutualAllyOfEntity(entity, target) { return IsOwnedByEntityHelper(entity, target, "IsMutualAlly"); } function IsOwnedByEntityHelper(entity, target, check) { // Figure out which player controls us let owner = 0; let cmpOwnership = Engine.QueryInterface(entity, IID_Ownership); if (cmpOwnership) owner = cmpOwnership.GetOwner(); // Figure out which player controls the target entity let targetOwner = 0; let cmpOwnershipTarget = Engine.QueryInterface(target, IID_Ownership); if (cmpOwnershipTarget) targetOwner = cmpOwnershipTarget.GetOwner(); let cmpPlayer = QueryPlayerIDInterface(owner); return cmpPlayer && cmpPlayer[check](targetOwner); } /** * Returns true if the entity 'target' is owned by player */ function IsOwnedByPlayer(player, target) { var cmpOwnershipTarget = Engine.QueryInterface(target, IID_Ownership); return cmpOwnershipTarget && player == cmpOwnershipTarget.GetOwner(); } function IsOwnedByGaia(target) { return IsOwnedByPlayer(0, target); } /** * Returns true if the entity 'target' is owned by an ally of player */ function IsOwnedByAllyOfPlayer(player, target) { return IsOwnedByHelper(player, target, "IsAlly"); } function IsOwnedByMutualAllyOfPlayer(player, target) { return IsOwnedByHelper(player, target, "IsMutualAlly"); } function IsOwnedByNeutralOfPlayer(player,target) { return IsOwnedByHelper(player, target, "IsNeutral"); } function IsOwnedByEnemyOfPlayer(player, target) { return IsOwnedByHelper(player, target, "IsEnemy"); } function IsOwnedByHelper(player, target, check) { let targetOwner = 0; let cmpOwnershipTarget = Engine.QueryInterface(target, IID_Ownership); if (cmpOwnershipTarget) targetOwner = cmpOwnershipTarget.GetOwner(); let cmpPlayer = QueryPlayerIDInterface(player); return cmpPlayer && cmpPlayer[check](targetOwner); } Engine.RegisterGlobal("LoadPlayerSettings", LoadPlayerSettings); Engine.RegisterGlobal("QueryOwnerInterface", QueryOwnerInterface); Engine.RegisterGlobal("QueryPlayerIDInterface", QueryPlayerIDInterface); Engine.RegisterGlobal("QueryMiragedInterface", QueryMiragedInterface); Engine.RegisterGlobal("QueryBuilderListInterface", QueryBuilderListInterface); Engine.RegisterGlobal("IsOwnedByAllyOfEntity", IsOwnedByAllyOfEntity); Engine.RegisterGlobal("IsOwnedByMutualAllyOfEntity", IsOwnedByMutualAllyOfEntity); Engine.RegisterGlobal("IsOwnedByPlayer", IsOwnedByPlayer); Engine.RegisterGlobal("IsOwnedByGaia", IsOwnedByGaia); Engine.RegisterGlobal("IsOwnedByAllyOfPlayer", IsOwnedByAllyOfPlayer); Engine.RegisterGlobal("IsOwnedByMutualAllyOfPlayer", IsOwnedByMutualAllyOfPlayer); Engine.RegisterGlobal("IsOwnedByNeutralOfPlayer", IsOwnedByNeutralOfPlayer); Engine.RegisterGlobal("IsOwnedByEnemyOfPlayer", IsOwnedByEnemyOfPlayer); Index: ps/trunk/source/graphics/MapGenerator.cpp =================================================================== --- ps/trunk/source/graphics/MapGenerator.cpp (revision 20575) +++ ps/trunk/source/graphics/MapGenerator.cpp (revision 20576) @@ -1,289 +1,289 @@ /* Copyright (C) 2017 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 . */ #include "precompiled.h" #include "MapGenerator.h" #include "graphics/Terrain.h" #include "lib/timer.h" #include "ps/CLogger.h" #include "ps/Profile.h" #include "ps/scripting/JSInterface_VFS.h" // TODO: what's a good default? perhaps based on map size #define RMS_RUNTIME_SIZE 96 * 1024 * 1024 CMapGeneratorWorker::CMapGeneratorWorker() { // If something happens before we initialize, that's a failure m_Progress = -1; } 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); ENSURE(ret == 0); } void* CMapGeneratorWorker::RunThread(void *data) { debug_SetThreadName("MapGenerator"); g_Profiler2.RegisterCurrentThread("MapGenerator"); CMapGeneratorWorker* self = static_cast(data); self->m_ScriptInterface = new ScriptInterface("Engine", "MapGenerator", ScriptInterface::CreateRuntime(g_ScriptRuntime, RMS_RUNTIME_SIZE)); // Run map generation scripts if (!self->Run() || self->m_Progress > 0) { // Don't leave progress in an unknown state, if generator failed, set it to -1 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. return NULL; } bool CMapGeneratorWorker::Run() { // We must destroy the ScriptInterface in the same thread because the JSAPI requires that! // Also we must not be in a request when calling the ScriptInterface destructor, so the autoFree object // must be instantiated before the request (destructors are called in reverse order of instantiation) struct AutoFree { AutoFree(ScriptInterface* p) : m_p(p) {} ~AutoFree() { SAFE_DELETE(m_p); } ScriptInterface* m_p; } autoFree(m_ScriptInterface); JSContext* cx = m_ScriptInterface->GetContext(); JSAutoRequest rq(cx); m_ScriptInterface->SetCallbackData(static_cast (this)); // Replace RNG with a seeded deterministic function m_ScriptInterface->ReplaceNondeterministicRNG(m_MapGenRNG); m_ScriptInterface->LoadGlobalScripts(); // Functions for RMS - JSI_VFS::RegisterReadOnlyScriptFunctions(*m_ScriptInterface); + JSI_VFS::RegisterScriptFunctions_Maps(*m_ScriptInterface); m_ScriptInterface->RegisterFunction("LoadLibrary"); m_ScriptInterface->RegisterFunction("ExportMap"); m_ScriptInterface->RegisterFunction("SetProgress"); 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"); m_ScriptInterface->RegisterFunction("GetTerrainTileSize"); // Parse settings JS::RootedValue settingsVal(cx); if (!m_ScriptInterface->ParseJSON(m_Settings, &settingsVal) && settingsVal.isUndefined()) { LOGERROR("CMapGeneratorWorker::Run: Failed to parse settings"); return false; } // Prevent unintentional modifications to the settings object by random map scripts if (!m_ScriptInterface->FreezeObject(settingsVal, true)) { LOGERROR("CMapGeneratorWorker::Run: Failed to deepfreeze settings"); return false; } // Init RNG seed u32 seed = 0; if (!m_ScriptInterface->HasProperty(settingsVal, "Seed") || !m_ScriptInterface->GetProperty(settingsVal, "Seed", seed)) LOGWARNING("CMapGeneratorWorker::Run: No seed value specified - using 0"); m_MapGenRNG.seed(seed); // Copy settings to global variable JS::RootedValue global(cx, m_ScriptInterface->GetGlobalObject()); if (!m_ScriptInterface->SetProperty(global, "g_MapSettings", settingsVal, true, true)) { LOGERROR("CMapGeneratorWorker::Run: Failed to define g_MapSettings"); return false; } // Load RMS LOGMESSAGE("Loading RMS '%s'", m_ScriptPath.string8()); if (!m_ScriptInterface->LoadGlobalScriptFile(m_ScriptPath)) { LOGERROR("CMapGeneratorWorker::Run: Failed to load RMS '%s'", m_ScriptPath.string8()); return false; } return true; } int CMapGeneratorWorker::GetProgress() { CScopeLock lock(m_WorkerMutex); return m_Progress; } shared_ptr CMapGeneratorWorker::GetResults() { CScopeLock lock(m_WorkerMutex); return m_MapData; } bool CMapGeneratorWorker::LoadLibrary(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& name) { CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData); return self->LoadScripts(name); } void CMapGeneratorWorker::ExportMap(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue data) { CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData); // Copy results CScopeLock lock(self->m_WorkerMutex); self->m_MapData = self->m_ScriptInterface->WriteStructuredClone(data); self->m_Progress = 0; } void CMapGeneratorWorker::SetProgress(ScriptInterface::CxPrivate* pCxPrivate, int progress) { CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData); // Copy data CScopeLock lock(self->m_WorkerMutex); self->m_Progress = progress; } CParamNode CMapGeneratorWorker::GetTemplate(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName) { CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData); const CParamNode& templateRoot = self->m_TemplateLoader.GetTemplateFileData(templateName).GetChild("Entity"); if (!templateRoot.IsOk()) LOGERROR("Invalid template found for '%s'", templateName.c_str()); return templateRoot; } bool CMapGeneratorWorker::TemplateExists(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName) { CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData); return self->m_TemplateLoader.TemplateExists(templateName); } std::vector CMapGeneratorWorker::FindTemplates(ScriptInterface::CxPrivate* pCxPrivate, const std::string& path, bool includeSubdirectories) { CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData); return self->m_TemplateLoader.FindTemplates(path, includeSubdirectories, SIMULATION_TEMPLATES); } std::vector CMapGeneratorWorker::FindActorTemplates(ScriptInterface::CxPrivate* pCxPrivate, const std::string& path, bool includeSubdirectories) { CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData); return self->m_TemplateLoader.FindTemplates(path, includeSubdirectories, ACTOR_TEMPLATES); } int CMapGeneratorWorker::GetTerrainTileSize(ScriptInterface::CxPrivate* UNUSED(pCxPrivate)) { return TERRAIN_TILE_SIZE; } bool CMapGeneratorWorker::LoadScripts(const std::wstring& libraryName) { // Ignore libraries that are already loaded if (m_LoadedLibraries.find(libraryName) != m_LoadedLibraries.end()) return true; // Mark this as loaded, to prevent it recursively loading itself m_LoadedLibraries.insert(libraryName); VfsPath path = L"maps/random/" + libraryName + L"/"; VfsPaths pathnames; // Load all scripts in mapgen directory Status ret = vfs::GetPathnames(g_VFS, path, L"*.js", pathnames); if (ret == INFO::OK) { for (const VfsPath& p : pathnames) { LOGMESSAGE("Loading map generator script '%s'", p.string8()); if (!m_ScriptInterface->LoadGlobalScriptFile(p)) { LOGERROR("CMapGeneratorWorker::LoadScripts: Failed to load script '%s'", p.string8()); return false; } } } else { // Some error reading directory wchar_t error[200]; LOGERROR("CMapGeneratorWorker::LoadScripts: Error reading scripts in directory '%s': %s", path.string8(), utf8_from_wstring(StatusDescription(ret, error, ARRAY_SIZE(error)))); return false; } 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 CMapGenerator::GetResults() { return m_Worker->GetResults(); } Index: ps/trunk/source/gui/scripting/ScriptFunctions.cpp =================================================================== --- ps/trunk/source/gui/scripting/ScriptFunctions.cpp (revision 20575) +++ ps/trunk/source/gui/scripting/ScriptFunctions.cpp (revision 20576) @@ -1,71 +1,70 @@ /* Copyright (C) 2017 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 . */ #include "precompiled.h" #include "scriptinterface/ScriptInterface.h" #include "graphics/scripting/JSInterface_GameView.h" #include "gui/IGUIObject.h" #include "gui/scripting/JSInterface_GUIManager.h" #include "gui/scripting/JSInterface_GUITypes.h" #include "i18n/scripting/JSInterface_L10n.h" #include "lobby/scripting/JSInterface_Lobby.h" #include "network/scripting/JSInterface_Network.h" #include "ps/scripting/JSInterface_ConfigDB.h" #include "ps/scripting/JSInterface_Console.h" #include "ps/scripting/JSInterface_Debug.h" #include "ps/scripting/JSInterface_Game.h" #include "ps/scripting/JSInterface_Main.h" #include "ps/scripting/JSInterface_Mod.h" #include "ps/scripting/JSInterface_SavedGame.h" #include "ps/scripting/JSInterface_VFS.h" #include "ps/scripting/JSInterface_VisualReplay.h" #include "renderer/scripting/JSInterface_Renderer.h" #include "simulation2/scripting/JSInterface_Simulation.h" #include "soundmanager/scripting/JSInterface_Sound.h" /* * This file defines a set of functions that are available to GUI scripts, to allow * interaction with the rest of the engine. * Functions are exposed to scripts within the global object 'Engine', so * scripts should call "Engine.FunctionName(...)" etc. */ void GuiScriptingInit(ScriptInterface& scriptInterface) { JSI_IGUIObject::init(scriptInterface); JSI_GUITypes::init(scriptInterface); JSI_ConfigDB::RegisterScriptFunctions(scriptInterface); JSI_Console::RegisterScriptFunctions(scriptInterface); JSI_Debug::RegisterScriptFunctions(scriptInterface); JSI_GUIManager::RegisterScriptFunctions(scriptInterface); JSI_Game::RegisterScriptFunctions(scriptInterface); JSI_GameView::RegisterScriptFunctions(scriptInterface); JSI_L10n::RegisterScriptFunctions(scriptInterface); JSI_Lobby::RegisterScriptFunctions(scriptInterface); JSI_Main::RegisterScriptFunctions(scriptInterface); JSI_Mod::RegisterScriptFunctions(scriptInterface); JSI_Network::RegisterScriptFunctions(scriptInterface); JSI_Renderer::RegisterScriptFunctions(scriptInterface); JSI_SavedGame::RegisterScriptFunctions(scriptInterface); JSI_Simulation::RegisterScriptFunctions(scriptInterface); JSI_Sound::RegisterScriptFunctions(scriptInterface); - JSI_VFS::RegisterReadOnlyScriptFunctions(scriptInterface); - JSI_VFS::RegisterWriteScriptFunctions(scriptInterface); + JSI_VFS::RegisterScriptFunctions_GUI(scriptInterface); JSI_VisualReplay::RegisterScriptFunctions(scriptInterface); } Index: ps/trunk/source/ps/scripting/JSInterface_VFS.cpp =================================================================== --- ps/trunk/source/ps/scripting/JSInterface_VFS.cpp (revision 20575) +++ ps/trunk/source/ps/scripting/JSInterface_VFS.cpp (revision 20576) @@ -1,220 +1,271 @@ /* Copyright (C) 2017 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 . */ #include "precompiled.h" #include #include "ps/CLogger.h" #include "ps/CStr.h" #include "ps/Filesystem.h" #include "scriptinterface/ScriptVal.h" #include "scriptinterface/ScriptInterface.h" #include "ps/scripting/JSInterface_VFS.h" #include "lib/file/vfs/vfs_util.h" +// Only allow engine compartments to read files they may be concerned about. +#define PathRestriction_GUI {L""} +#define PathRestriction_Simulation {L"simulation/"} +#define PathRestriction_Maps {L"simulation/", L"maps/"} + // shared error handling code #define JS_CHECK_FILE_ERR(err)\ /* this is liable to happen often, so don't complain */\ if (err == ERR::VFS_FILE_NOT_FOUND)\ {\ return 0; \ }\ /* unknown failure. We output an error message. */\ else if (err < 0)\ LOGERROR("Unknown failure in VFS %i", err ); /* else: success */ // state held across multiple BuildDirEntListCB calls; init by BuildDirEntList. struct BuildDirEntListState { JSContext* cx; JS::PersistentRootedObject filename_array; int cur_idx; BuildDirEntListState(JSContext* cx_) : cx(cx_), filename_array(cx, JS_NewArrayObject(cx, JS::HandleValueArray::empty())), cur_idx(0) { } }; // called for each matching directory entry; add its full pathname to array. static Status BuildDirEntListCB(const VfsPath& pathname, const CFileInfo& UNUSED(fileINfo), uintptr_t cbData) { BuildDirEntListState* s = (BuildDirEntListState*)cbData; JSAutoRequest rq(s->cx); JS::RootedObject filenameArrayObj(s->cx, s->filename_array); JS::RootedValue val(s->cx); ScriptInterface::ToJSVal( s->cx, &val, CStrW(pathname.string()) ); JS_SetElement(s->cx, filenameArrayObj, s->cur_idx++, val); return INFO::OK; } // Return an array of pathname strings, one for each matching entry in the // specified directory. // // pathnames = buildDirEntList(start_path [, filter_string [, recursive ] ]); // directory: VFS path // filter_string: default "" matches everything; otherwise, see vfs_next_dirent. // recurse: should subdirectories be included in the search? default false. // // note: full pathnames of each file/subdirectory are returned, // ready for use as a "filename" for the other functions. JS::Value JSI_VFS::BuildDirEntList(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& path, const std::wstring& filterStr, bool recurse) { // convert to const wchar_t*; if there's no filter, pass 0 for speed // (interpreted as: "accept all files without comparing"). const wchar_t* filter = 0; if (!filterStr.empty()) filter = filterStr.c_str(); int flags = recurse ? vfs::DIR_RECURSIVE : 0; JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); JSAutoRequest rq(cx); // build array in the callback function BuildDirEntListState state(cx); vfs::ForEachFile(g_VFS, path, BuildDirEntListCB, (uintptr_t)&state, filter, flags); return OBJECT_TO_JSVAL(state.filename_array); } // Return true iff the file exits bool JSI_VFS::FileExists(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const CStrW& filename) { return (g_VFS->GetFileInfo(filename, 0) == INFO::OK); } // Return time [seconds since 1970] of the last modification to the specified file. double JSI_VFS::GetFileMTime(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& filename) { CFileInfo fileInfo; Status err = g_VFS->GetFileInfo(filename, &fileInfo); JS_CHECK_FILE_ERR(err); return (double)fileInfo.MTime(); } // Return current size of file. unsigned int JSI_VFS::GetFileSize(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& filename) { CFileInfo fileInfo; Status err = g_VFS->GetFileInfo(filename, &fileInfo); JS_CHECK_FILE_ERR(err); return (unsigned int)fileInfo.Size(); } // Return file contents in a string. Assume file is UTF-8 encoded text. JS::Value JSI_VFS::ReadFile(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& filename) { JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); JSAutoRequest rq(cx); CVFSFile file; if (file.Load(g_VFS, filename) != PSRETURN_OK) return JS::NullValue(); CStr contents = file.DecodeUTF8(); // assume it's UTF-8 // Fix CRLF line endings. (This function will only ever be used on text files.) contents.Replace("\r\n", "\n"); // Decode as UTF-8 JS::RootedValue ret(cx); ScriptInterface::ToJSVal(cx, &ret, contents.FromUTF8()); return ret; } // Return file contents as an array of lines. Assume file is UTF-8 encoded text. JS::Value JSI_VFS::ReadFileLines(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& filename) { JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); JSAutoRequest rq(cx); CVFSFile file; if (file.Load(g_VFS, filename) != PSRETURN_OK) return JSVAL_NULL; CStr contents = file.DecodeUTF8(); // assume it's UTF-8 // Fix CRLF line endings. (This function will only ever be used on text files.) contents.Replace("\r\n", "\n"); // split into array of strings (one per line) std::stringstream ss(contents); JS::RootedObject line_array(cx, JS_NewArrayObject(cx, JS::HandleValueArray::empty())); std::string line; int cur_line = 0; while (std::getline(ss, line)) { // Decode each line as UTF-8 JS::RootedValue val(cx); ScriptInterface::ToJSVal(cx, &val, CStr(line).FromUTF8()); JS_SetElement(cx, line_array, cur_line++, val); } return JS::ObjectValue(*line_array); } -JS::Value JSI_VFS::ReadJSONFile(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& filePath) +JS::Value JSI_VFS::ReadJSONFile(ScriptInterface::CxPrivate* pCxPrivate, const std::vector& validPaths, const CStrW& filePath) { + if (!PathRestrictionMet(pCxPrivate, validPaths, filePath)) + return JS::NullValue(); + JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); JSAutoRequest rq(cx); JS::RootedValue out(cx); pCxPrivate->pScriptInterface->ReadJSONFile(filePath, &out); return out; } void JSI_VFS::WriteJSONFile(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& filePath, JS::HandleValue val1) { JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); JSAutoRequest rq(cx); // TODO: This is a workaround because we need to pass a MutableHandle to StringifyJSON. JS::RootedValue val(cx, val1); std::string str(pCxPrivate->pScriptInterface->StringifyJSON(&val, false)); VfsPath path(filePath); WriteBuffer buf; buf.Append(str.c_str(), str.length()); g_VFS->CreateFile(path, buf.Data(), buf.Size()); } -void JSI_VFS::RegisterReadOnlyScriptFunctions(const ScriptInterface& scriptInterface) +bool JSI_VFS::PathRestrictionMet(ScriptInterface::CxPrivate* pCxPrivate, const std::vector& validPaths, const CStrW& filePath) +{ + for (const CStrW& validPath : validPaths) + if (filePath.find(validPath) == 0) + return true; + + CStrW allowedPaths; + for (std::size_t i = 0; i < validPaths.size(); ++i) + { + if (i != 0) + allowedPaths += L", "; + + allowedPaths += L"\"" + validPaths[i] + L"\""; + } + + JS_ReportError( + pCxPrivate->pScriptInterface->GetContext(), + "This part of the engine may only read from %s!", + utf8_from_wstring(allowedPaths).c_str()); + + return false; +} + +#define VFS_ScriptFunctions(context)\ +JS::Value Script_ReadJSONFile_##context(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& filePath)\ +{\ + return JSI_VFS::ReadJSONFile(pCxPrivate, PathRestriction_##context, filePath);\ +} + +VFS_ScriptFunctions(GUI); +VFS_ScriptFunctions(Simulation); +VFS_ScriptFunctions(Maps); + +#undef VFS_ScriptFunctions + +void JSI_VFS::RegisterScriptFunctions_GUI(const ScriptInterface& scriptInterface) { scriptInterface.RegisterFunction("BuildDirEntList"); scriptInterface.RegisterFunction("FileExists"); scriptInterface.RegisterFunction("GetFileMTime"); scriptInterface.RegisterFunction("GetFileSize"); scriptInterface.RegisterFunction("ReadFile"); scriptInterface.RegisterFunction("ReadFileLines"); - scriptInterface.RegisterFunction("ReadJSONFile"); + scriptInterface.RegisterFunction("ReadJSONFile"); + scriptInterface.RegisterFunction("WriteJSONFile"); } -void JSI_VFS::RegisterWriteScriptFunctions(const ScriptInterface& scriptInterface) +void JSI_VFS::RegisterScriptFunctions_Simulation(const ScriptInterface& scriptInterface) { - scriptInterface.RegisterFunction("WriteJSONFile"); + scriptInterface.RegisterFunction("ReadJSONFile"); +} + +void JSI_VFS::RegisterScriptFunctions_Maps(const ScriptInterface& scriptInterface) +{ + scriptInterface.RegisterFunction("BuildDirEntList"); + scriptInterface.RegisterFunction("FileExists"); + scriptInterface.RegisterFunction("ReadJSONFile"); } Index: ps/trunk/source/ps/scripting/JSInterface_VFS.h =================================================================== --- ps/trunk/source/ps/scripting/JSInterface_VFS.h (revision 20575) +++ ps/trunk/source/ps/scripting/JSInterface_VFS.h (revision 20576) @@ -1,68 +1,72 @@ /* Copyright (C) 2017 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 . */ // JSInterface_VFS.h // // The JavaScript wrapper around useful snippets of the VFS #ifndef INCLUDED_JSI_VFS #define INCLUDED_JSI_VFS #include "scriptinterface/ScriptInterface.h" // these are registered in ScriptFunctions.cpp, hence the need for a header. namespace JSI_VFS { // Return an array of pathname strings, one for each matching entry in the // specified directory. // // pathnames = buildDirEntList(start_path [, filter_string [, recursive ] ]); // directory: VFS path // filter_string: see match_wildcard; "" matches everything. // recurse: should subdirectories be included in the search? default false. // // note: full pathnames of each file/subdirectory are returned, // ready for use as a "filename" for the other functions. JS::Value BuildDirEntList(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& path, const std::wstring& filterStr, bool recurse); // Return true iff the file exists bool FileExists(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& filename); // Return time [seconds since 1970] of the last modification to the specified file. double GetFileMTime(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& filename); // Return current size of file. unsigned int GetFileSize(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& filename); // Return file contents in a string. JS::Value ReadFile(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& filename); // Return file contents as an array of lines. JS::Value ReadFileLines(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& filename); // Return file contents parsed as a JS Object - JS::Value ReadJSONFile(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& filePath); + JS::Value ReadJSONFile(ScriptInterface::CxPrivate* pCxPrivate, const std::vector& validPaths, const CStrW& filePath); // Save given JS Object to a JSON file void WriteJSONFile(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& filePath, JS::HandleValue val1); - void RegisterReadOnlyScriptFunctions(const ScriptInterface& scriptInterface); - void RegisterWriteScriptFunctions(const ScriptInterface& scriptInterface); + // Tests whether the current script context is allowed to read from the given directory + bool PathRestrictionMet(ScriptInterface::CxPrivate* pCxPrivate, const std::vector& validPaths, const CStrW& filePath); + + void RegisterScriptFunctions_GUI(const ScriptInterface& scriptInterface); + void RegisterScriptFunctions_Simulation(const ScriptInterface& scriptInterface); + void RegisterScriptFunctions_Maps(const ScriptInterface& scriptInterface); } #endif Index: ps/trunk/source/simulation2/system/ComponentManager.cpp =================================================================== --- ps/trunk/source/simulation2/system/ComponentManager.cpp (revision 20575) +++ ps/trunk/source/simulation2/system/ComponentManager.cpp (revision 20576) @@ -1,1250 +1,1228 @@ /* Copyright (C) 2017 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 . */ #include "precompiled.h" #include "ComponentManager.h" #include "DynamicSubscription.h" #include "IComponent.h" #include "ParamNode.h" #include "SimContext.h" #include "simulation2/MessageTypes.h" #include "simulation2/components/ICmpTemplateManager.h" #include "lib/utf8.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" +#include "ps/scripting/JSInterface_VFS.h" /** * Used for script-only message types. */ class CMessageScripted : public CMessage { public: virtual int GetType() const { return mtid; } virtual const char* GetScriptHandlerName() const { return handlerName.c_str(); } virtual const char* GetScriptGlobalHandlerName() const { return globalHandlerName.c_str(); } virtual JS::Value ToJSVal(const ScriptInterface& UNUSED(scriptInterface)) const { return msg.get(); } CMessageScripted(const ScriptInterface& scriptInterface, int mtid, const std::string& name, JS::HandleValue msg) : mtid(mtid), handlerName("On" + name), globalHandlerName("OnGlobal" + name), msg(scriptInterface.GetJSRuntime(), msg) { } int mtid; std::string handlerName; std::string globalHandlerName; JS::PersistentRootedValue msg; }; CComponentManager::CComponentManager(CSimContext& context, shared_ptr rt, bool skipScriptFunctions) : m_NextScriptComponentTypeId(CID__LastNative), m_ScriptInterface("Engine", "Simulation", rt), m_SimContext(context), m_CurrentlyHotloading(false) { context.SetComponentManager(this); m_ScriptInterface.SetCallbackData(static_cast (this)); m_ScriptInterface.ReplaceNondeterministicRNG(m_RNG); m_ScriptInterface.LoadGlobalScripts(); // For component script tests, the test system sets up its own scripted implementation of // these functions, so we skip registering them here in those cases if (!skipScriptFunctions) { + JSI_VFS::RegisterScriptFunctions_Simulation(m_ScriptInterface); m_ScriptInterface.RegisterFunction ("RegisterComponentType"); m_ScriptInterface.RegisterFunction ("RegisterSystemComponentType"); m_ScriptInterface.RegisterFunction ("ReRegisterComponentType"); m_ScriptInterface.RegisterFunction ("RegisterInterface"); m_ScriptInterface.RegisterFunction ("RegisterMessageType"); m_ScriptInterface.RegisterFunction ("RegisterGlobal"); m_ScriptInterface.RegisterFunction ("QueryInterface"); m_ScriptInterface.RegisterFunction, int, CComponentManager::Script_GetEntitiesWithInterface> ("GetEntitiesWithInterface"); m_ScriptInterface.RegisterFunction, int, CComponentManager::Script_GetComponentsWithInterface> ("GetComponentsWithInterface"); m_ScriptInterface.RegisterFunction ("PostMessage"); m_ScriptInterface.RegisterFunction ("BroadcastMessage"); m_ScriptInterface.RegisterFunction ("AddEntity"); m_ScriptInterface.RegisterFunction ("AddLocalEntity"); m_ScriptInterface.RegisterFunction ("DestroyEntity"); m_ScriptInterface.RegisterFunction ("FlushDestroyedEntities"); m_ScriptInterface.RegisterFunction ("DataFileExists"); - m_ScriptInterface.RegisterFunction ("ReadJSONFile"); - m_ScriptInterface.RegisterFunction ("ReadCivJSONFile"); m_ScriptInterface.RegisterFunction, std::wstring, bool, CComponentManager::Script_FindJSONFiles> ("FindJSONFiles"); } // Define MT_*, IID_* as script globals, and store their names #define MESSAGE(name) m_ScriptInterface.SetGlobal("MT_" #name, (int)MT_##name); #define INTERFACE(name) \ m_ScriptInterface.SetGlobal("IID_" #name, (int)IID_##name); \ m_InterfaceIdsByName[#name] = IID_##name; #define COMPONENT(name) #include "simulation2/TypeList.h" #undef MESSAGE #undef INTERFACE #undef COMPONENT m_ScriptInterface.SetGlobal("INVALID_ENTITY", (int)INVALID_ENTITY); m_ScriptInterface.SetGlobal("SYSTEM_ENTITY", (int)SYSTEM_ENTITY); m_ComponentsByInterface.resize(IID__LastNative); ResetState(); } CComponentManager::~CComponentManager() { ResetState(); } void CComponentManager::LoadComponentTypes() { #define MESSAGE(name) \ RegisterMessageType(MT_##name, #name); #define INTERFACE(name) \ extern void RegisterComponentInterface_##name(ScriptInterface&); \ RegisterComponentInterface_##name(m_ScriptInterface); #define COMPONENT(name) \ extern void RegisterComponentType_##name(CComponentManager&); \ m_CurrentComponent = CID_##name; \ RegisterComponentType_##name(*this); #include "simulation2/TypeList.h" m_CurrentComponent = CID__Invalid; #undef MESSAGE #undef INTERFACE #undef COMPONENT } bool CComponentManager::LoadScript(const VfsPath& filename, bool hotload) { m_CurrentlyHotloading = hotload; CVFSFile file; PSRETURN loadOk = file.Load(g_VFS, filename); if (loadOk != PSRETURN_OK) // VFS will log the failed file and the reason return false; std::string content = file.DecodeUTF8(); // assume it's UTF-8 bool ok = m_ScriptInterface.LoadScript(filename, content); return ok; } void CComponentManager::Script_RegisterComponentType_Common(ScriptInterface::CxPrivate* pCxPrivate, int iid, const std::string& cname, JS::HandleValue ctor, bool reRegister, bool systemComponent) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); JSContext* cx = componentManager->m_ScriptInterface.GetContext(); JSAutoRequest rq(cx); // Find the C++ component that wraps the interface int cidWrapper = componentManager->GetScriptWrapper(iid); if (cidWrapper == CID__Invalid) { componentManager->m_ScriptInterface.ReportError("Invalid interface id"); return; } const ComponentType& ctWrapper = componentManager->m_ComponentTypesById[cidWrapper]; bool mustReloadComponents = false; // for hotloading ComponentTypeId cid = componentManager->LookupCID(cname); if (cid == CID__Invalid) { if (reRegister) { std::string msg("ReRegistering component type that was not registered before '"+cname+"'"); componentManager->m_ScriptInterface.ReportError(msg.c_str()); return; } // Allocate a new cid number cid = componentManager->m_NextScriptComponentTypeId++; componentManager->m_ComponentTypeIdsByName[cname] = cid; if (systemComponent) componentManager->MarkScriptedComponentForSystemEntity(cid); } else { // Component type is already loaded, so do hotloading: if (!componentManager->m_CurrentlyHotloading && !reRegister) { std::string msg("Registering component type with already-registered name '"+cname+"'"); componentManager->m_ScriptInterface.ReportError(msg.c_str()); return; } const ComponentType& ctPrevious = componentManager->m_ComponentTypesById[cid]; // We can only replace scripted component types, not native ones if (ctPrevious.type != CT_Script) { std::string msg("Loading script component type with same name '"+cname+"' as native component"); componentManager->m_ScriptInterface.ReportError(msg.c_str()); return; } // We don't support changing the IID of a component type (it would require fiddling // around with m_ComponentsByInterface and being careful to guarantee uniqueness per entity) if (ctPrevious.iid != iid) { // ...though it only matters if any components exist with this type if (!componentManager->m_ComponentsByTypeId[cid].empty()) { componentManager->m_ScriptInterface.ReportError("Hotloading script component type mustn't change interface ID"); return; } } // Remove the old component type's message subscriptions std::map >::iterator it; for (it = componentManager->m_LocalMessageSubscriptions.begin(); it != componentManager->m_LocalMessageSubscriptions.end(); ++it) { std::vector& types = it->second; std::vector::iterator ctit = find(types.begin(), types.end(), cid); if (ctit != types.end()) types.erase(ctit); } for (it = componentManager->m_GlobalMessageSubscriptions.begin(); it != componentManager->m_GlobalMessageSubscriptions.end(); ++it) { std::vector& types = it->second; std::vector::iterator ctit = find(types.begin(), types.end(), cid); if (ctit != types.end()) types.erase(ctit); } mustReloadComponents = true; } std::string schema = ""; { JS::RootedValue prototype(cx); if (componentManager->m_ScriptInterface.GetProperty(ctor, "prototype", &prototype) && componentManager->m_ScriptInterface.HasProperty(prototype, "Schema")) { componentManager->m_ScriptInterface.GetProperty(prototype, "Schema", schema); } } // Construct a new ComponentType, using the wrapper's alloc functions ComponentType ct( CT_Script, iid, ctWrapper.alloc, ctWrapper.dealloc, cname, schema, DefPersistentRooted(cx, ctor) ); componentManager->m_ComponentTypesById[cid] = std::move(ct); componentManager->m_CurrentComponent = cid; // needed by Subscribe // Find all the ctor prototype's On* methods, and subscribe to the appropriate messages: JS::RootedValue protoVal(cx); if (!componentManager->m_ScriptInterface.GetProperty(ctor, "prototype", &protoVal)) return; // error std::vector methods; JS::RootedObject proto(cx); if (!protoVal.isObjectOrNull()) return; // error proto = protoVal.toObjectOrNull(); if (!componentManager->m_ScriptInterface.EnumeratePropertyNamesWithPrefix(protoVal, "On", methods)) return; // error for (std::vector::const_iterator it = methods.begin(); it != methods.end(); ++it) { std::string name = (*it).substr(2); // strip the "On" prefix // Handle "OnGlobalFoo" functions specially bool isGlobal = false; if (name.substr(0, 6) == "Global") { isGlobal = true; name = name.substr(6); } std::map::const_iterator mit = componentManager->m_MessageTypeIdsByName.find(name); if (mit == componentManager->m_MessageTypeIdsByName.end()) { std::string msg("Registered component has unrecognised '" + *it + "' message handler method"); componentManager->m_ScriptInterface.ReportError(msg.c_str()); return; } if (isGlobal) componentManager->SubscribeGloballyToMessageType(mit->second); else componentManager->SubscribeToMessageType(mit->second); } componentManager->m_CurrentComponent = CID__Invalid; if (mustReloadComponents) { // For every script component with this cid, we need to switch its // prototype from the old constructor's prototype property to the new one's const std::map& comps = componentManager->m_ComponentsByTypeId[cid]; std::map::const_iterator eit = comps.begin(); for (; eit != comps.end(); ++eit) { JS::RootedValue instance(cx, eit->second->GetJSInstance()); if (!instance.isNull()) { componentManager->m_ScriptInterface.SetPrototype(instance, protoVal); } } } } void CComponentManager::Script_RegisterComponentType(ScriptInterface::CxPrivate* pCxPrivate, int iid, const std::string& cname, JS::HandleValue ctor) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); componentManager->Script_RegisterComponentType_Common(pCxPrivate, iid, cname, ctor, false, false); componentManager->m_ScriptInterface.SetGlobal(cname.c_str(), ctor, componentManager->m_CurrentlyHotloading); } void CComponentManager::Script_RegisterSystemComponentType(ScriptInterface::CxPrivate* pCxPrivate, int iid, const std::string& cname, JS::HandleValue ctor) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); componentManager->Script_RegisterComponentType_Common(pCxPrivate, iid, cname, ctor, false, true); componentManager->m_ScriptInterface.SetGlobal(cname.c_str(), ctor, componentManager->m_CurrentlyHotloading); } void CComponentManager::Script_ReRegisterComponentType(ScriptInterface::CxPrivate* pCxPrivate, int iid, const std::string& cname, JS::HandleValue ctor) { Script_RegisterComponentType_Common(pCxPrivate, iid, cname, ctor, true, false); } void CComponentManager::Script_RegisterInterface(ScriptInterface::CxPrivate* pCxPrivate, const std::string& name) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); std::map::iterator it = componentManager->m_InterfaceIdsByName.find(name); if (it != componentManager->m_InterfaceIdsByName.end()) { // Redefinitions are fine (and just get ignored) when hotloading; otherwise // they're probably unintentional and should be reported if (!componentManager->m_CurrentlyHotloading) { std::string msg("Registering interface with already-registered name '"+name+"'"); componentManager->m_ScriptInterface.ReportError(msg.c_str()); } return; } // IIDs start at 1, so size+1 is the next unused one size_t id = componentManager->m_InterfaceIdsByName.size() + 1; componentManager->m_InterfaceIdsByName[name] = (InterfaceId)id; componentManager->m_ComponentsByInterface.resize(id+1); // add one so we can index by InterfaceId componentManager->m_ScriptInterface.SetGlobal(("IID_" + name).c_str(), (int)id); } void CComponentManager::Script_RegisterMessageType(ScriptInterface::CxPrivate* pCxPrivate, const std::string& name) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); std::map::iterator it = componentManager->m_MessageTypeIdsByName.find(name); if (it != componentManager->m_MessageTypeIdsByName.end()) { // Redefinitions are fine (and just get ignored) when hotloading; otherwise // they're probably unintentional and should be reported if (!componentManager->m_CurrentlyHotloading) { std::string msg("Registering message type with already-registered name '"+name+"'"); componentManager->m_ScriptInterface.ReportError(msg.c_str()); } return; } // MTIDs start at 1, so size+1 is the next unused one size_t id = componentManager->m_MessageTypeIdsByName.size() + 1; componentManager->RegisterMessageType((MessageTypeId)id, name.c_str()); componentManager->m_ScriptInterface.SetGlobal(("MT_" + name).c_str(), (int)id); } void CComponentManager::Script_RegisterGlobal(ScriptInterface::CxPrivate* pCxPrivate, const std::string& name, JS::HandleValue value) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); // Set the value, and accept duplicates only if hotloading (otherwise it's an error, // in order to detect accidental duplicate definitions of globals) componentManager->m_ScriptInterface.SetGlobal(name.c_str(), value, componentManager->m_CurrentlyHotloading); } IComponent* CComponentManager::Script_QueryInterface(ScriptInterface::CxPrivate* pCxPrivate, int ent, int iid) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); IComponent* component = componentManager->QueryInterface((entity_id_t)ent, iid); return component; } std::vector CComponentManager::Script_GetEntitiesWithInterface(ScriptInterface::CxPrivate* pCxPrivate, int iid) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); std::vector ret; const InterfaceListUnordered& ents = componentManager->GetEntitiesWithInterfaceUnordered(iid); for (InterfaceListUnordered::const_iterator it = ents.begin(); it != ents.end(); ++it) if (!ENTITY_IS_LOCAL(it->first)) ret.push_back(it->first); std::sort(ret.begin(), ret.end()); return ret; } std::vector CComponentManager::Script_GetComponentsWithInterface(ScriptInterface::CxPrivate* pCxPrivate, int iid) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); std::vector ret; InterfaceList ents = componentManager->GetEntitiesWithInterface(iid); for (InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it) ret.push_back(it->second); // TODO: maybe we should exclude local entities return ret; } CMessage* CComponentManager::ConstructMessage(int mtid, JS::HandleValue data) { if (mtid == MT__Invalid || mtid > (int)m_MessageTypeIdsByName.size()) // (IDs start at 1 so use '>' here) LOGERROR("PostMessage with invalid message type ID '%d'", mtid); if (mtid < MT__LastNative) { return CMessageFromJSVal(mtid, m_ScriptInterface, data); } else { return new CMessageScripted(m_ScriptInterface, mtid, m_MessageTypeNamesById[mtid], data); } } void CComponentManager::Script_PostMessage(ScriptInterface::CxPrivate* pCxPrivate, int ent, int mtid, JS::HandleValue data) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); CMessage* msg = componentManager->ConstructMessage(mtid, data); if (!msg) return; // error componentManager->PostMessage(ent, *msg); delete msg; } void CComponentManager::Script_BroadcastMessage(ScriptInterface::CxPrivate* pCxPrivate, int mtid, JS::HandleValue data) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); CMessage* msg = componentManager->ConstructMessage(mtid, data); if (!msg) return; // error componentManager->BroadcastMessage(*msg); delete msg; } int CComponentManager::Script_AddEntity(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); std::wstring name(templateName.begin(), templateName.end()); // TODO: should validate the string to make sure it doesn't contain scary characters // that will let it access non-component-template files entity_id_t ent = componentManager->AddEntity(name, componentManager->AllocateNewEntity()); return (int)ent; } int CComponentManager::Script_AddLocalEntity(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); std::wstring name(templateName.begin(), templateName.end()); // TODO: should validate the string to make sure it doesn't contain scary characters // that will let it access non-component-template files entity_id_t ent = componentManager->AddEntity(name, componentManager->AllocateNewLocalEntity()); return (int)ent; } void CComponentManager::Script_DestroyEntity(ScriptInterface::CxPrivate* pCxPrivate, int ent) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); componentManager->DestroyComponentsSoon(ent); } void CComponentManager::Script_FlushDestroyedEntities(ScriptInterface::CxPrivate *pCxPrivate) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); componentManager->FlushDestroyedComponents(); } void CComponentManager::ResetState() { // Delete all dynamic message subscriptions m_DynamicMessageSubscriptionsNonsync.clear(); m_DynamicMessageSubscriptionsNonsyncByComponent.clear(); // Delete all IComponents std::map >::iterator iit = m_ComponentsByTypeId.begin(); for (; iit != m_ComponentsByTypeId.end(); ++iit) { std::map::iterator eit = iit->second.begin(); for (; eit != iit->second.end(); ++eit) { eit->second->Deinit(); m_ComponentTypesById[iit->first].dealloc(eit->second); } } std::vector >::iterator ifcit = m_ComponentsByInterface.begin(); for (; ifcit != m_ComponentsByInterface.end(); ++ifcit) ifcit->clear(); m_ComponentsByTypeId.clear(); // Delete all SEntityComponentCaches std::unordered_map::iterator ccit = m_ComponentCaches.begin(); for (; ccit != m_ComponentCaches.end(); ++ccit) free(ccit->second); m_ComponentCaches.clear(); m_SystemEntity = CEntityHandle(); m_DestructionQueue.clear(); // Reset IDs m_NextEntityId = SYSTEM_ENTITY + 1; m_NextLocalEntityId = FIRST_LOCAL_ENTITY; } void CComponentManager::SetRNGSeed(u32 seed) { m_RNG.seed(seed); } void CComponentManager::RegisterComponentType(InterfaceId iid, ComponentTypeId cid, AllocFunc alloc, DeallocFunc dealloc, const char* name, const std::string& schema) { ComponentType c(CT_Native, iid, alloc, dealloc, name, schema, DefPersistentRooted()); m_ComponentTypesById.insert(std::make_pair(cid, std::move(c))); m_ComponentTypeIdsByName[name] = cid; } void CComponentManager::RegisterComponentTypeScriptWrapper(InterfaceId iid, ComponentTypeId cid, AllocFunc alloc, DeallocFunc dealloc, const char* name, const std::string& schema) { ComponentType c(CT_ScriptWrapper, iid, alloc, dealloc, name, schema, DefPersistentRooted()); m_ComponentTypesById.insert(std::make_pair(cid, std::move(c))); m_ComponentTypeIdsByName[name] = cid; // TODO: merge with RegisterComponentType } void CComponentManager::MarkScriptedComponentForSystemEntity(CComponentManager::ComponentTypeId cid) { m_ScriptedSystemComponents.push_back(cid); } void CComponentManager::RegisterMessageType(MessageTypeId mtid, const char* name) { m_MessageTypeIdsByName[name] = mtid; m_MessageTypeNamesById[mtid] = name; } void CComponentManager::SubscribeToMessageType(MessageTypeId mtid) { // TODO: verify mtid ENSURE(m_CurrentComponent != CID__Invalid); std::vector& types = m_LocalMessageSubscriptions[mtid]; types.push_back(m_CurrentComponent); std::sort(types.begin(), types.end()); // TODO: just sort once at the end of LoadComponents } void CComponentManager::SubscribeGloballyToMessageType(MessageTypeId mtid) { // TODO: verify mtid ENSURE(m_CurrentComponent != CID__Invalid); std::vector& types = m_GlobalMessageSubscriptions[mtid]; types.push_back(m_CurrentComponent); std::sort(types.begin(), types.end()); // TODO: just sort once at the end of LoadComponents } void CComponentManager::FlattenDynamicSubscriptions() { std::map::iterator it; for (it = m_DynamicMessageSubscriptionsNonsync.begin(); it != m_DynamicMessageSubscriptionsNonsync.end(); ++it) { it->second.Flatten(); } } void CComponentManager::DynamicSubscriptionNonsync(MessageTypeId mtid, IComponent* component, bool enable) { if (enable) { bool newlyInserted = m_DynamicMessageSubscriptionsNonsyncByComponent[component].insert(mtid).second; if (newlyInserted) m_DynamicMessageSubscriptionsNonsync[mtid].Add(component); } else { size_t numRemoved = m_DynamicMessageSubscriptionsNonsyncByComponent[component].erase(mtid); if (numRemoved) m_DynamicMessageSubscriptionsNonsync[mtid].Remove(component); } } void CComponentManager::RemoveComponentDynamicSubscriptions(IComponent* component) { std::map >::iterator it = m_DynamicMessageSubscriptionsNonsyncByComponent.find(component); if (it == m_DynamicMessageSubscriptionsNonsyncByComponent.end()) return; std::set::iterator mtit; for (mtit = it->second.begin(); mtit != it->second.end(); ++mtit) { m_DynamicMessageSubscriptionsNonsync[*mtit].Remove(component); // Need to flatten the subscription lists immediately to avoid dangling IComponent* references m_DynamicMessageSubscriptionsNonsync[*mtit].Flatten(); } m_DynamicMessageSubscriptionsNonsyncByComponent.erase(it); } CComponentManager::ComponentTypeId CComponentManager::LookupCID(const std::string& cname) const { std::map::const_iterator it = m_ComponentTypeIdsByName.find(cname); if (it == m_ComponentTypeIdsByName.end()) return CID__Invalid; return it->second; } std::string CComponentManager::LookupComponentTypeName(ComponentTypeId cid) const { std::map::const_iterator it = m_ComponentTypesById.find(cid); if (it == m_ComponentTypesById.end()) return ""; return it->second.name; } CComponentManager::ComponentTypeId CComponentManager::GetScriptWrapper(InterfaceId iid) { if (iid >= IID__LastNative && iid <= (int)m_InterfaceIdsByName.size()) // use <= since IDs start at 1 return CID_UnknownScript; std::map::const_iterator it = m_ComponentTypesById.begin(); for (; it != m_ComponentTypesById.end(); ++it) if (it->second.iid == iid && it->second.type == CT_ScriptWrapper) return it->first; std::map::const_iterator iiit = m_InterfaceIdsByName.begin(); for (; iiit != m_InterfaceIdsByName.end(); ++iiit) if (iiit->second == iid) { LOGERROR("No script wrapper found for interface id %d '%s'", iid, iiit->first.c_str()); return CID__Invalid; } LOGERROR("No script wrapper found for interface id %d", iid); return CID__Invalid; } entity_id_t CComponentManager::AllocateNewEntity() { entity_id_t id = m_NextEntityId++; // TODO: check for overflow return id; } entity_id_t CComponentManager::AllocateNewLocalEntity() { entity_id_t id = m_NextLocalEntityId++; // TODO: check for overflow return id; } entity_id_t CComponentManager::AllocateNewEntity(entity_id_t preferredId) { // TODO: ensure this ID hasn't been allocated before // (this might occur with broken map files) // Trying to actually add two entities with the same id will fail in AddEntitiy entity_id_t id = preferredId; // Ensure this ID won't be allocated again if (id >= m_NextEntityId) m_NextEntityId = id+1; // TODO: check for overflow return id; } bool CComponentManager::AddComponent(CEntityHandle ent, ComponentTypeId cid, const CParamNode& paramNode) { IComponent* component = ConstructComponent(ent, cid); if (!component) return false; component->Init(paramNode); return true; } void CComponentManager::AddSystemComponents(bool skipScriptedComponents, bool skipAI) { CParamNode noParam; AddComponent(m_SystemEntity, CID_TemplateManager, noParam); AddComponent(m_SystemEntity, CID_CinemaManager, noParam); AddComponent(m_SystemEntity, CID_CommandQueue, noParam); AddComponent(m_SystemEntity, CID_ObstructionManager, noParam); AddComponent(m_SystemEntity, CID_ParticleManager, noParam); AddComponent(m_SystemEntity, CID_Pathfinder, noParam); AddComponent(m_SystemEntity, CID_ProjectileManager, noParam); AddComponent(m_SystemEntity, CID_RangeManager, noParam); AddComponent(m_SystemEntity, CID_SoundManager, noParam); AddComponent(m_SystemEntity, CID_Terrain, noParam); AddComponent(m_SystemEntity, CID_TerritoryManager, noParam); AddComponent(m_SystemEntity, CID_UnitRenderer, noParam); AddComponent(m_SystemEntity, CID_WaterManager, noParam); // Add scripted system components: if (!skipScriptedComponents) { for (uint32_t i = 0; i < m_ScriptedSystemComponents.size(); ++i) AddComponent(m_SystemEntity, m_ScriptedSystemComponents[i], noParam); if (!skipAI) AddComponent(m_SystemEntity, CID_AIManager, noParam); } } IComponent* CComponentManager::ConstructComponent(CEntityHandle ent, ComponentTypeId cid) { JSContext* cx = m_ScriptInterface.GetContext(); JSAutoRequest rq(cx); std::map::const_iterator it = m_ComponentTypesById.find(cid); if (it == m_ComponentTypesById.end()) { LOGERROR("Invalid component id %d", cid); return NULL; } const ComponentType& ct = it->second; ENSURE((size_t)ct.iid < m_ComponentsByInterface.size()); boost::unordered_map& emap1 = m_ComponentsByInterface[ct.iid]; if (emap1.find(ent.GetId()) != emap1.end()) { LOGERROR("Multiple components for interface %d", ct.iid); return NULL; } std::map& emap2 = m_ComponentsByTypeId[cid]; // If this is a scripted component, construct the appropriate JS object first JS::RootedValue obj(cx); if (ct.type == CT_Script) { m_ScriptInterface.CallConstructor(ct.ctor.get(), JS::HandleValueArray::empty(), &obj); if (obj.isNull()) { LOGERROR("Script component constructor failed"); return NULL; } } // Construct the new component IComponent* component = ct.alloc(m_ScriptInterface, obj); ENSURE(component); component->SetEntityHandle(ent); component->SetSimContext(m_SimContext); // Store a reference to the new component emap1.insert(std::make_pair(ent.GetId(), component)); emap2.insert(std::make_pair(ent.GetId(), component)); // TODO: We need to more careful about this - if an entity is constructed by a component // while we're iterating over all components, this will invalidate the iterators and everything // will break. // We probably need some kind of delayed addition, so they get pushed onto a queue and then // inserted into the world later on. (Be careful about immediation deletion in that case, too.) SEntityComponentCache* cache = ent.GetComponentCache(); ENSURE(cache != NULL && ct.iid < (int)cache->numInterfaces && cache->interfaces[ct.iid] == NULL); cache->interfaces[ct.iid] = component; return component; } void CComponentManager::AddMockComponent(CEntityHandle ent, InterfaceId iid, IComponent& component) { // Just add it into the by-interface map, not the by-component-type map, // so it won't be considered for messages or deletion etc boost::unordered_map& emap1 = m_ComponentsByInterface.at(iid); if (emap1.find(ent.GetId()) != emap1.end()) debug_warn(L"Multiple components for interface"); emap1.insert(std::make_pair(ent.GetId(), &component)); SEntityComponentCache* cache = ent.GetComponentCache(); ENSURE(cache != NULL && iid < (int)cache->numInterfaces && cache->interfaces[iid] == NULL); cache->interfaces[iid] = &component; } CEntityHandle CComponentManager::AllocateEntityHandle(entity_id_t ent) { // Interface IDs start at 1, and SEntityComponentCache is defined with a 1-sized array, // so we need space for an extra m_InterfaceIdsByName.size() items SEntityComponentCache* cache = (SEntityComponentCache*)calloc(1, sizeof(SEntityComponentCache) + sizeof(IComponent*) * m_InterfaceIdsByName.size()); ENSURE(cache != NULL); cache->numInterfaces = m_InterfaceIdsByName.size() + 1; ENSURE(m_ComponentCaches.find(ent) == m_ComponentCaches.end()); m_ComponentCaches[ent] = cache; return CEntityHandle(ent, cache); } CEntityHandle CComponentManager::LookupEntityHandle(entity_id_t ent, bool allowCreate) { std::unordered_map::iterator it; it = m_ComponentCaches.find(ent); if (it == m_ComponentCaches.end()) { if (allowCreate) return AllocateEntityHandle(ent); else return CEntityHandle(ent, NULL); } else return CEntityHandle(ent, it->second); } void CComponentManager::InitSystemEntity() { ENSURE(m_SystemEntity.GetId() == INVALID_ENTITY); m_SystemEntity = AllocateEntityHandle(SYSTEM_ENTITY); m_SimContext.SetSystemEntity(m_SystemEntity); } entity_id_t CComponentManager::AddEntity(const std::wstring& templateName, entity_id_t ent) { ICmpTemplateManager *cmpTemplateManager = static_cast (QueryInterface(SYSTEM_ENTITY, IID_TemplateManager)); if (!cmpTemplateManager) { debug_warn(L"No ICmpTemplateManager loaded"); return INVALID_ENTITY; } const CParamNode* tmpl = cmpTemplateManager->LoadTemplate(ent, utf8_from_wstring(templateName)); if (!tmpl) return INVALID_ENTITY; // LoadTemplate will have reported the error // This also ensures that ent does not exist CEntityHandle handle = AllocateEntityHandle(ent); // Construct a component for each child of the root element const CParamNode::ChildrenMap& tmplChilds = tmpl->GetChildren(); for (CParamNode::ChildrenMap::const_iterator it = tmplChilds.begin(); it != tmplChilds.end(); ++it) { // Ignore attributes on the root element if (it->first.length() && it->first[0] == '@') continue; CComponentManager::ComponentTypeId cid = LookupCID(it->first); if (cid == CID__Invalid) { LOGERROR("Unrecognised component type name '%s' in entity template '%s'", it->first, utf8_from_wstring(templateName)); return INVALID_ENTITY; } if (!AddComponent(handle, cid, it->second)) { LOGERROR("Failed to construct component type name '%s' in entity template '%s'", it->first, utf8_from_wstring(templateName)); return INVALID_ENTITY; } // TODO: maybe we should delete already-constructed components if one of them fails? } CMessageCreate msg(ent); PostMessage(ent, msg); return ent; } void CComponentManager::DestroyComponentsSoon(entity_id_t ent) { m_DestructionQueue.push_back(ent); } void CComponentManager::FlushDestroyedComponents() { PROFILE2("Flush Destroyed Components"); while (!m_DestructionQueue.empty()) { // Make a copy of the destruction queue, so that the iterators won't be invalidated if the // CMessageDestroy handlers try to destroy more entities themselves std::vector queue; queue.swap(m_DestructionQueue); for (std::vector::iterator it = queue.begin(); it != queue.end(); ++it) { entity_id_t ent = *it; CEntityHandle handle = LookupEntityHandle(ent); CMessageDestroy msg(ent); PostMessage(ent, msg); // Flatten all the dynamic subscriptions to ensure there are no dangling // references in the 'removed' lists to components we're going to delete // Some components may have dynamically unsubscribed following the Destroy message FlattenDynamicSubscriptions(); // Destroy the components, and remove from m_ComponentsByTypeId: std::map >::iterator iit = m_ComponentsByTypeId.begin(); for (; iit != m_ComponentsByTypeId.end(); ++iit) { std::map::iterator eit = iit->second.find(ent); if (eit != iit->second.end()) { eit->second->Deinit(); RemoveComponentDynamicSubscriptions(eit->second); m_ComponentTypesById[iit->first].dealloc(eit->second); iit->second.erase(ent); handle.GetComponentCache()->interfaces[m_ComponentTypesById[iit->first].iid] = NULL; } } free(handle.GetComponentCache()); m_ComponentCaches.erase(ent); // Remove from m_ComponentsByInterface std::vector >::iterator ifcit = m_ComponentsByInterface.begin(); for (; ifcit != m_ComponentsByInterface.end(); ++ifcit) { ifcit->erase(ent); } } } } IComponent* CComponentManager::QueryInterface(entity_id_t ent, InterfaceId iid) const { if ((size_t)iid >= m_ComponentsByInterface.size()) { // Invalid iid return NULL; } boost::unordered_map::const_iterator eit = m_ComponentsByInterface[iid].find(ent); if (eit == m_ComponentsByInterface[iid].end()) { // This entity doesn't implement this interface return NULL; } return eit->second; } CComponentManager::InterfaceList CComponentManager::GetEntitiesWithInterface(InterfaceId iid) const { std::vector > ret; if ((size_t)iid >= m_ComponentsByInterface.size()) { // Invalid iid return ret; } ret.reserve(m_ComponentsByInterface[iid].size()); boost::unordered_map::const_iterator it = m_ComponentsByInterface[iid].begin(); for (; it != m_ComponentsByInterface[iid].end(); ++it) ret.push_back(*it); std::sort(ret.begin(), ret.end()); // lexicographic pair comparison means this'll sort by entity ID return ret; } static CComponentManager::InterfaceListUnordered g_EmptyEntityMap; const CComponentManager::InterfaceListUnordered& CComponentManager::GetEntitiesWithInterfaceUnordered(InterfaceId iid) const { if ((size_t)iid >= m_ComponentsByInterface.size()) { // Invalid iid return g_EmptyEntityMap; } return m_ComponentsByInterface[iid]; } void CComponentManager::PostMessage(entity_id_t ent, const CMessage& msg) { PROFILE2_IFSPIKE("Post Message", 0.0005); PROFILE2_ATTR("%s", msg.GetScriptHandlerName()); // Send the message to components of ent, that subscribed locally to this message std::map >::const_iterator it; it = m_LocalMessageSubscriptions.find(msg.GetType()); if (it != m_LocalMessageSubscriptions.end()) { std::vector::const_iterator ctit = it->second.begin(); for (; ctit != it->second.end(); ++ctit) { // Find the component instances of this type (if any) std::map >::const_iterator emap = m_ComponentsByTypeId.find(*ctit); if (emap == m_ComponentsByTypeId.end()) continue; // Send the message to all of them std::map::const_iterator eit = emap->second.find(ent); if (eit != emap->second.end()) eit->second->HandleMessage(msg, false); } } SendGlobalMessage(ent, msg); } void CComponentManager::BroadcastMessage(const CMessage& msg) { // Send the message to components of all entities that subscribed locally to this message std::map >::const_iterator it; it = m_LocalMessageSubscriptions.find(msg.GetType()); if (it != m_LocalMessageSubscriptions.end()) { std::vector::const_iterator ctit = it->second.begin(); for (; ctit != it->second.end(); ++ctit) { // Find the component instances of this type (if any) std::map >::const_iterator emap = m_ComponentsByTypeId.find(*ctit); if (emap == m_ComponentsByTypeId.end()) continue; // Send the message to all of them std::map::const_iterator eit = emap->second.begin(); for (; eit != emap->second.end(); ++eit) eit->second->HandleMessage(msg, false); } } SendGlobalMessage(INVALID_ENTITY, msg); } void CComponentManager::SendGlobalMessage(entity_id_t ent, const CMessage& msg) { PROFILE2_IFSPIKE("SendGlobalMessage", 0.001); PROFILE2_ATTR("%s", msg.GetScriptHandlerName()); // (Common functionality for PostMessage and BroadcastMessage) // Send the message to components of all entities that subscribed globally to this message std::map >::const_iterator it; it = m_GlobalMessageSubscriptions.find(msg.GetType()); if (it != m_GlobalMessageSubscriptions.end()) { std::vector::const_iterator ctit = it->second.begin(); for (; ctit != it->second.end(); ++ctit) { // Special case: Messages for local entities shouldn't be sent to script // components that subscribed globally, so that we don't have to worry about // them accidentally picking up non-network-synchronised data. if (ENTITY_IS_LOCAL(ent)) { std::map::const_iterator it = m_ComponentTypesById.find(*ctit); if (it != m_ComponentTypesById.end() && it->second.type == CT_Script) continue; } // Find the component instances of this type (if any) std::map >::const_iterator emap = m_ComponentsByTypeId.find(*ctit); if (emap == m_ComponentsByTypeId.end()) continue; // Send the message to all of them std::map::const_iterator eit = emap->second.begin(); for (; eit != emap->second.end(); ++eit) eit->second->HandleMessage(msg, true); } } // Send the message to component instances that dynamically subscribed to this message std::map::iterator dit = m_DynamicMessageSubscriptionsNonsync.find(msg.GetType()); if (dit != m_DynamicMessageSubscriptionsNonsync.end()) { dit->second.Flatten(); const std::vector& dynamic = dit->second.GetComponents(); for (size_t i = 0; i < dynamic.size(); i++) dynamic[i]->HandleMessage(msg, false); } } std::string CComponentManager::GenerateSchema() const { std::string numericOperation = "" "" "" "add" "mul" "" "" ""; std::string schema = "" "" "" + numericOperation + "" "" "0" + numericOperation + "" "" "0" + numericOperation + "" "" "" "" "" "" "" "" "" "" "" "" ""; std::map > interfaceComponentTypes; std::vector componentTypes; for (std::map::const_iterator it = m_ComponentTypesById.begin(); it != m_ComponentTypesById.end(); ++it) { schema += "" "" "" + it->second.schema + "" "" ""; interfaceComponentTypes[it->second.iid].push_back(it->second.name); componentTypes.push_back(it->second.name); } // Declare the implementation of each interface, for documentation for (std::map::const_iterator it = m_InterfaceIdsByName.begin(); it != m_InterfaceIdsByName.end(); ++it) { schema += ""; std::vector& cts = interfaceComponentTypes[it->second]; for (size_t i = 0; i < cts.size(); ++i) schema += ""; schema += ""; } // List all the component types, in alphabetical order (to match the reordering performed by CParamNode). // (We do it this way, rather than ing all the interface definitions (which would additionally perform // a check that we don't use multiple component types of the same interface in one file), because libxml2 gives // useless error messages in the latter case; this way lets it report the real error.) std::sort(componentTypes.begin(), componentTypes.end()); schema += "" "" ""; for (std::vector::const_iterator it = componentTypes.begin(); it != componentTypes.end(); ++it) schema += ""; schema += "" ""; schema += ""; return schema; } bool CComponentManager::Script_DataFileExists(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& fileName) { VfsPath path = VfsPath(L"simulation/data") / fileName; return VfsFileExists(path); } -JS::Value CComponentManager::Script_ReadJSONFile(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& fileName) -{ - return ReadJSONFile(pCxPrivate, L"simulation/data", fileName); -} - -JS::Value CComponentManager::Script_ReadCivJSONFile(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& fileName) -{ - return ReadJSONFile(pCxPrivate, L"simulation/data/civs", fileName); -} - -JS::Value CComponentManager::ReadJSONFile(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& filePath, const std::wstring& fileName) -{ - CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); - JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); - JSAutoRequest rq(cx); - - VfsPath path = VfsPath(filePath) / fileName; - JS::RootedValue out(cx); - componentManager->GetScriptInterface().ReadJSONFile(path, &out); - return out; -} - Status CComponentManager::FindJSONFilesCallback(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData) { FindJSONFilesCallbackData* data = (FindJSONFilesCallbackData*)cbData; VfsPath pathstem = pathname.ChangeExtension(L""); // Strip the root from the path std::wstring name = pathstem.string().substr(data->path.string().length()); data->templates.push_back(std::string(name.begin(), name.end())); return INFO::OK; } std::vector CComponentManager::Script_FindJSONFiles(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& subPath, bool recursive) { FindJSONFilesCallbackData cbData; cbData.path = VfsPath(L"simulation/data/" + subPath + L"/"); int dir_flags = 0; if (recursive) { dir_flags = vfs::DIR_RECURSIVE; } // Find all simulation/data/{subPath}/*.json recursively Status ret = vfs::ForEachFile(g_VFS, cbData.path, FindJSONFilesCallback, (uintptr_t)&cbData, L"*.json", dir_flags); if (ret != INFO::OK) { // Some error reading directory wchar_t error[200]; LOGERROR("Error reading directory '%s': %s", cbData.path.string8(), utf8_from_wstring(StatusDescription(ret, error, ARRAY_SIZE(error)))); } return cbData.templates; } Index: ps/trunk/source/simulation2/system/ComponentManager.h =================================================================== --- ps/trunk/source/simulation2/system/ComponentManager.h (revision 20575) +++ ps/trunk/source/simulation2/system/ComponentManager.h (revision 20576) @@ -1,393 +1,390 @@ /* Copyright (C) 2017 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 . */ #ifndef INCLUDED_COMPONENTMANAGER #define INCLUDED_COMPONENTMANAGER #include "Entity.h" #include "Components.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptVal.h" #include "simulation2/helpers/Player.h" #include "ps/Filesystem.h" #include #include #include #include class IComponent; class CParamNode; class CMessage; class CSimContext; class CDynamicSubscription; class CComponentManager { NONCOPYABLE(CComponentManager); public: // We can't use EInterfaceId/etc directly, since scripts dynamically generate new IDs // and casting arbitrary ints to enums is undefined behaviour, so use 'int' typedefs typedef int InterfaceId; typedef int ComponentTypeId; typedef int MessageTypeId; private: // Component allocation types typedef IComponent* (*AllocFunc)(const ScriptInterface& scriptInterface, JS::HandleValue ctor); typedef void (*DeallocFunc)(IComponent*); // ComponentTypes come in three types: // Native: normal C++ component // ScriptWrapper: C++ component that wraps a JS component implementation // Script: a ScriptWrapper linked to a specific JS component implementation enum EComponentTypeType { CT_Native, CT_ScriptWrapper, CT_Script }; // Representation of a component type, to be used when instantiating components struct ComponentType { EComponentTypeType type; InterfaceId iid; AllocFunc alloc; DeallocFunc dealloc; std::string name; std::string schema; // RelaxNG fragment DefPersistentRooted ctor; // only valid if type == CT_Script // TODO: Constructor, move assignment operator and move constructor only have to be // explicitly defined for Visual Studio. VS2013 is still behind on C++11 support // What's missing is what they call "Rvalue references v3.0", see // https://msdn.microsoft.com/en-us/library/hh567368.aspx#rvref ComponentType() {} ComponentType (EComponentTypeType type, InterfaceId iid, AllocFunc alloc, DeallocFunc dealloc, std::string name, std::string schema, DefPersistentRooted ctor) : type(type), iid(iid), alloc(alloc), dealloc(dealloc), name(name), schema(schema), ctor(std::move(ctor)) { } ComponentType& operator= (ComponentType&& other) { type = std::move(other.type); iid = std::move(other.iid); alloc = std::move(other.alloc); dealloc = std::move(other.dealloc); name = std::move(other.name); schema = std::move(other.schema); ctor = std::move(other.ctor); return *this; } ComponentType(ComponentType&& other) { type = std::move(other.type); iid = std::move(other.iid); alloc = std::move(other.alloc); dealloc = std::move(other.dealloc); name = std::move(other.name); schema = std::move(other.schema); ctor = std::move(other.ctor); } }; struct FindJSONFilesCallbackData { VfsPath path; std::vector templates; }; public: CComponentManager(CSimContext&, shared_ptr rt, bool skipScriptFunctions = false); ~CComponentManager(); void LoadComponentTypes(); /** * Load a script and execute it in a new function scope. * @param filename VFS path to load * @param hotload set to true if this script has been loaded before, and redefinitions of * existing components should not be considered errors */ bool LoadScript(const VfsPath& filename, bool hotload = false); void RegisterMessageType(MessageTypeId mtid, const char* name); void RegisterComponentType(InterfaceId, ComponentTypeId, AllocFunc, DeallocFunc, const char*, const std::string& schema); void RegisterComponentTypeScriptWrapper(InterfaceId, ComponentTypeId, AllocFunc, DeallocFunc, const char*, const std::string& schema); void MarkScriptedComponentForSystemEntity(CComponentManager::ComponentTypeId cid); /** * Subscribe the current component type to the given message type. * Each component's HandleMessage will be called on any BroadcastMessage of this message type, * or on any PostMessage of this type targeted at the component's entity. * Must only be called by a component type's ClassInit. */ void SubscribeToMessageType(MessageTypeId mtid); /** * Subscribe the current component type to all messages of the given message type. * Each component's HandleMessage will be called on any BroadcastMessage or PostMessage of this message type, * regardless of the entity. * Must only be called by a component type's ClassInit. */ void SubscribeGloballyToMessageType(MessageTypeId mtid); /** * Subscribe the given component instance to all messages of the given message type. * The component's HandleMessage will be called on any BroadcastMessage or PostMessage of * this message type, regardless of the entity. * * This can be called at any time (including inside the HandleMessage callback for this message type). * * The component type must not have statically subscribed to this message type in its ClassInit. * * The subscription status is not saved or network-synchronised. Components must remember to * resubscribe in their Deserialize methods if they still want the message. * * This is primarily intended for Interpolate and RenderSubmit messages, to avoid the cost of * sending the message to components that do not currently need to do any rendering. */ void DynamicSubscriptionNonsync(MessageTypeId mtid, IComponent* component, bool enabled); /** * @param cname Requested component type name (not including any "CID" or "CCmp" prefix) * @return The component type id, or CID__Invalid if not found */ ComponentTypeId LookupCID(const std::string& cname) const; /** * @return The name of the given component type, or "" if not found */ std::string LookupComponentTypeName(ComponentTypeId cid) const; /** * Set up an empty SYSTEM_ENTITY. Must be called after ResetState() and before GetSystemEntity(). */ void InitSystemEntity(); /** * Returns a CEntityHandle with id SYSTEM_ENTITY. */ CEntityHandle GetSystemEntity() { ASSERT(m_SystemEntity.GetId() == SYSTEM_ENTITY); return m_SystemEntity; } /** * Returns a CEntityHandle with id @p ent. * If @p allowCreate is true and there is no existing CEntityHandle, a new handle will be allocated. */ CEntityHandle LookupEntityHandle(entity_id_t ent, bool allowCreate = false); /** * Returns a new entity ID that has never been used before. * This affects the simulation state so it must only be called in network-synchronised ways. */ entity_id_t AllocateNewEntity(); /** * Returns a new local entity ID that has never been used before. * This entity will not be synchronised over the network, stored in saved games, etc. */ entity_id_t AllocateNewLocalEntity(); /** * Returns a new entity ID that has never been used before. * If possible, returns preferredId, and ensures this ID won't be allocated again. * This affects the simulation state so it must only be called in network-synchronised ways. */ entity_id_t AllocateNewEntity(entity_id_t preferredId); /** * Constructs a component of type 'cid', initialised with data 'paramNode', * and attaches it to entity 'ent'. * * @return true on success; false on failure, and logs an error message */ bool AddComponent(CEntityHandle ent, ComponentTypeId cid, const CParamNode& paramNode); /** * Add all system components to the system entity (skip the scripted components or the AI components on demand) */ void AddSystemComponents(bool skipScriptedComponents, bool skipAI); /** * Adds an externally-created component, so that it is returned by QueryInterface * but does not get destroyed and does not receive messages from the component manager. * (This is intended for unit tests that need to add mock objects the tested components * expect to exist.) */ void AddMockComponent(CEntityHandle ent, InterfaceId iid, IComponent& component); /** * Allocates a component object of type 'cid', and attaches it to entity 'ent'. * (The component's Init is not called here - either Init or Deserialize must be called * before using the returned object.) */ IComponent* ConstructComponent(CEntityHandle ent, ComponentTypeId cid); /** * Constructs an entity based on the given template, and adds it the world with * entity ID @p ent. There should not be any existing components with that entity ID. * @return ent, or INVALID_ENTITY on error */ entity_id_t AddEntity(const std::wstring& templateName, entity_id_t ent); /** * Destroys all the components belonging to the specified entity when FlushDestroyedComponents is called. * Has no effect if the entity does not exist, or has already been added to the destruction queue. */ void DestroyComponentsSoon(entity_id_t ent); /** * Does the actual destruction of components from DestroyComponentsSoon. * This must not be called if the component manager is on the call stack (since it * will break internal iterators). */ void FlushDestroyedComponents(); IComponent* QueryInterface(entity_id_t ent, InterfaceId iid) const; typedef std::pair InterfacePair; typedef std::vector InterfaceList; typedef boost::unordered_map InterfaceListUnordered; InterfaceList GetEntitiesWithInterface(InterfaceId iid) const; const InterfaceListUnordered& GetEntitiesWithInterfaceUnordered(InterfaceId iid) const; /** * Send a message, targeted at a particular entity. The message will be received by any * components of that entity which subscribed to the message type, and by any other components * that subscribed globally to the message type. */ void PostMessage(entity_id_t ent, const CMessage& msg); /** * Send a message, not targeted at any particular entity. The message will be received by any * components that subscribed (either globally or not) to the message type. */ void BroadcastMessage(const CMessage& msg); /** * Resets the dynamic simulation state (deletes all entities, resets entity ID counters; * doesn't unload/reload component scripts). */ void ResetState(); /** * Initializes the random number generator with a seed determined by the host. */ void SetRNGSeed(u32 seed); // Various state serialization functions: bool ComputeStateHash(std::string& outHash, bool quick) const; bool DumpDebugState(std::ostream& stream, bool includeDebugInfo) const; // FlushDestroyedComponents must be called before SerializeState (since the destruction queue // won't get serialized) bool SerializeState(std::ostream& stream) const; bool DeserializeState(std::istream& stream); std::string GenerateSchema() const; ScriptInterface& GetScriptInterface() { return m_ScriptInterface; } private: // Implementations of functions exposed to scripts static void Script_RegisterComponentType_Common(ScriptInterface::CxPrivate* pCxPrivate, int iid, const std::string& cname, JS::HandleValue ctor, bool reRegister, bool systemComponent); static void Script_RegisterComponentType(ScriptInterface::CxPrivate* pCxPrivate, int iid, const std::string& cname, JS::HandleValue ctor); static void Script_RegisterSystemComponentType(ScriptInterface::CxPrivate* pCxPrivate, int iid, const std::string& cname, JS::HandleValue ctor); static void Script_ReRegisterComponentType(ScriptInterface::CxPrivate* pCxPrivate, int iid, const std::string& cname, JS::HandleValue ctor); static void Script_RegisterInterface(ScriptInterface::CxPrivate* pCxPrivate, const std::string& name); static void Script_RegisterMessageType(ScriptInterface::CxPrivate* pCxPrivate, const std::string& name); static void Script_RegisterGlobal(ScriptInterface::CxPrivate* pCxPrivate, const std::string& name, JS::HandleValue value); static IComponent* Script_QueryInterface(ScriptInterface::CxPrivate* pCxPrivate, int ent, int iid); static std::vector Script_GetEntitiesWithInterface(ScriptInterface::CxPrivate* pCxPrivate, int iid); static std::vector Script_GetComponentsWithInterface(ScriptInterface::CxPrivate* pCxPrivate, int iid); static void Script_PostMessage(ScriptInterface::CxPrivate* pCxPrivate, int ent, int mtid, JS::HandleValue data); static void Script_BroadcastMessage(ScriptInterface::CxPrivate* pCxPrivate, int mtid, JS::HandleValue data); static int Script_AddEntity(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName); static int Script_AddLocalEntity(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName); static void Script_DestroyEntity(ScriptInterface::CxPrivate* pCxPrivate, int ent); static void Script_FlushDestroyedEntities(ScriptInterface::CxPrivate* pCxPrivate); static bool Script_DataFileExists(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& fileName); - static JS::Value Script_ReadJSONFile(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& fileName); - static JS::Value Script_ReadCivJSONFile(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& fileName); static std::vector Script_FindJSONFiles(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& subPath, bool recursive); - static JS::Value ReadJSONFile(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& filePath, const std::wstring& fileName); // callback function to handle recursively finding files in a directory static Status FindJSONFilesCallback(const VfsPath&, const CFileInfo&, const uintptr_t); CMessage* ConstructMessage(int mtid, JS::HandleValue data); void SendGlobalMessage(entity_id_t ent, const CMessage& msg); void FlattenDynamicSubscriptions(); void RemoveComponentDynamicSubscriptions(IComponent* component); ComponentTypeId GetScriptWrapper(InterfaceId iid); CEntityHandle AllocateEntityHandle(entity_id_t ent); ScriptInterface m_ScriptInterface; CSimContext& m_SimContext; CEntityHandle m_SystemEntity; ComponentTypeId m_CurrentComponent; // used when loading component types bool m_CurrentlyHotloading; // TODO: some of these should be vectors std::map m_ComponentTypesById; std::vector m_ScriptedSystemComponents; std::vector > m_ComponentsByInterface; // indexed by InterfaceId std::map > m_ComponentsByTypeId; std::map > m_LocalMessageSubscriptions; std::map > m_GlobalMessageSubscriptions; std::map m_ComponentTypeIdsByName; std::map m_MessageTypeIdsByName; std::map m_MessageTypeNamesById; std::map m_InterfaceIdsByName; std::map m_DynamicMessageSubscriptionsNonsync; std::map > m_DynamicMessageSubscriptionsNonsyncByComponent; std::unordered_map m_ComponentCaches; // TODO: maintaining both ComponentsBy* is nasty; can we get rid of one, // while keeping QueryInterface and PostMessage sufficiently efficient? std::vector m_DestructionQueue; ComponentTypeId m_NextScriptComponentTypeId; entity_id_t m_NextEntityId; entity_id_t m_NextLocalEntityId; boost::rand48 m_RNG; friend class TestComponentManager; }; #endif // INCLUDED_COMPONENTMANAGER