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