Index: ps/trunk/source/simulation2/system/ComponentManager.cpp
===================================================================
--- ps/trunk/source/simulation2/system/ComponentManager.cpp (revision 22002)
+++ ps/trunk/source/simulation2/system/ComponentManager.cpp (revision 22003)
@@ -1,1188 +1,1176 @@
/* Copyright (C) 2018 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);
// 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");
}
// Globalscripts may use VFS script functions
m_ScriptInterface.LoadGlobalScripts();
// 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("INVALID_PLAYER", (int)INVALID_PLAYER);
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;
}
Index: ps/trunk/source/simulation2/system/ParamNode.cpp
===================================================================
--- ps/trunk/source/simulation2/system/ParamNode.cpp (revision 22002)
+++ ps/trunk/source/simulation2/system/ParamNode.cpp (revision 22003)
@@ -1,439 +1,445 @@
/* 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 "ParamNode.h"
#include "lib/utf8.h"
#include "ps/CLogger.h"
#include "ps/CStr.h"
#include "ps/Filesystem.h"
#include "ps/XML/Xeromyces.h"
#include
#include
#include // this isn't in string.hpp in old Boosts
static CParamNode g_NullNode(false);
CParamNode::CParamNode(bool isOk) :
m_IsOk(isOk)
{
}
void CParamNode::LoadXML(CParamNode& ret, const XMBFile& xmb, const wchar_t* sourceIdentifier /*= NULL*/)
{
ret.ApplyLayer(xmb, xmb.GetRoot(), sourceIdentifier);
}
void CParamNode::LoadXML(CParamNode& ret, const VfsPath& path, const std::string& validatorName)
{
CXeromyces xero;
PSRETURN ok = xero.Load(g_VFS, path, validatorName);
if (ok != PSRETURN_OK)
return; // (Xeromyces already logged an error)
LoadXML(ret, xero, path.string().c_str());
}
PSRETURN CParamNode::LoadXMLString(CParamNode& ret, const char* xml, const wchar_t* sourceIdentifier /*=NULL*/)
{
CXeromyces xero;
PSRETURN ok = xero.LoadString(xml);
if (ok != PSRETURN_OK)
return ok;
ret.ApplyLayer(xero, xero.GetRoot(), sourceIdentifier);
return PSRETURN_OK;
}
void CParamNode::ApplyLayer(const XMBFile& xmb, const XMBElement& element, const wchar_t* sourceIdentifier /*= NULL*/)
{
ResetScriptVal();
std::string name = xmb.GetElementString(element.GetNodeName()); // TODO: is GetElementString inefficient?
CStrW value = element.GetText().FromUTF8();
bool hasSetValue = false;
// Look for special attributes
int at_disable = xmb.GetAttributeID("disable");
int at_replace = xmb.GetAttributeID("replace");
int at_filtered = xmb.GetAttributeID("filtered");
int at_merge = xmb.GetAttributeID("merge");
int at_op = xmb.GetAttributeID("op");
int at_datatype = xmb.GetAttributeID("datatype");
enum op {
INVALID,
ADD,
- MUL
+ MUL,
+ MUL_ROUND
} op = INVALID;
bool replacing = false;
bool filtering = false;
bool merging = false;
{
XERO_ITER_ATTR(element, attr)
{
if (attr.Name == at_disable)
{
m_Childs.erase(name);
return;
}
else if (attr.Name == at_replace)
{
m_Childs.erase(name);
replacing = true;
}
else if (attr.Name == at_filtered)
{
filtering = true;
}
else if (attr.Name == at_merge)
{
if (m_Childs.find(name) == m_Childs.end())
return;
merging = true;
}
else if (attr.Name == at_op)
{
if (std::wstring(attr.Value.begin(), attr.Value.end()) == L"add")
op = ADD;
else if (std::wstring(attr.Value.begin(), attr.Value.end()) == L"mul")
op = MUL;
+ else if (std::wstring(attr.Value.begin(), attr.Value.end()) == L"mul_round")
+ op = MUL_ROUND;
else
LOGWARNING("Invalid op '%ls'", attr.Value);
}
}
}
{
XERO_ITER_ATTR(element, attr)
{
if (attr.Name == at_datatype && std::wstring(attr.Value.begin(), attr.Value.end()) == L"tokens")
{
CParamNode& node = m_Childs[name];
// Split into tokens
std::vector oldTokens;
std::vector newTokens;
if (!replacing && !node.m_Value.empty()) // ignore the old tokens if replace="" was given
boost::algorithm::split(oldTokens, node.m_Value, boost::algorithm::is_space(), boost::algorithm::token_compress_on);
if (!value.empty())
boost::algorithm::split(newTokens, value, boost::algorithm::is_space(), boost::algorithm::token_compress_on);
// Merge the two lists
std::vector tokens = oldTokens;
for (size_t i = 0; i < newTokens.size(); ++i)
{
if (newTokens[i][0] == L'-')
{
std::vector::iterator tokenIt = std::find(tokens.begin(), tokens.end(), newTokens[i].substr(1));
if (tokenIt != tokens.end())
tokens.erase(tokenIt);
else
LOGWARNING("[ParamNode] Could not remove token '%s' from node '%s'%s; not present in list nor inherited (possible typo?)",
utf8_from_wstring(newTokens[i].substr(1)), name, sourceIdentifier ? (" in '" + utf8_from_wstring(sourceIdentifier) + "'").c_str() : "");
}
else
{
if (std::find(oldTokens.begin(), oldTokens.end(), newTokens[i]) == oldTokens.end())
tokens.push_back(newTokens[i]);
}
}
node.m_Value = boost::algorithm::join(tokens, L" ");
hasSetValue = true;
break;
}
}
}
// Add this element as a child node
CParamNode& node = m_Childs[name];
if (op != INVALID)
{
// TODO: Support parsing of data types other than fixed; log warnings in other cases
fixed oldval = node.ToFixed();
fixed mod = fixed::FromString(CStrW(value));
switch (op)
{
case ADD:
node.m_Value = (oldval + mod).ToString().FromUTF8();
break;
case MUL:
- node.m_Value = (oldval.Multiply(mod)).ToString().FromUTF8();
+ node.m_Value = oldval.Multiply(mod).ToString().FromUTF8();
+ break;
+ case MUL_ROUND:
+ node.m_Value = fixed::FromInt(oldval.Multiply(mod).ToInt_RoundToNearest()).ToString().FromUTF8();
break;
}
hasSetValue = true;
}
if (!hasSetValue && !merging)
node.m_Value = value;
// We also need to reset node's script val, even if it has no children
// or if the attributes change.
node.ResetScriptVal();
// For the filtered case
ChildrenMap childs;
// Recurse through the element's children
XERO_ITER_EL(element, child)
{
node.ApplyLayer(xmb, child, sourceIdentifier);
if (filtering)
{
std::string childname = xmb.GetElementString(child.GetNodeName());
if (node.m_Childs.find(childname) != node.m_Childs.end())
childs[childname] = std::move(node.m_Childs[childname]);
}
}
if (filtering)
node.m_Childs.swap(childs);
// Add the element's attributes, prefixing names with "@"
XERO_ITER_ATTR(element, attr)
{
// Skip special attributes
if (attr.Name == at_replace || attr.Name == at_op || attr.Name == at_merge || attr.Name == at_filtered)
continue;
// Add any others
std::string attrName = xmb.GetAttributeString(attr.Name);
node.m_Childs["@" + attrName].m_Value = attr.Value.FromUTF8();
}
}
const CParamNode& CParamNode::GetChild(const char* name) const
{
ChildrenMap::const_iterator it = m_Childs.find(name);
if (it == m_Childs.end())
return g_NullNode;
return it->second;
}
bool CParamNode::IsOk() const
{
return m_IsOk;
}
const std::wstring& CParamNode::ToString() const
{
return m_Value;
}
const std::string CParamNode::ToUTF8() const
{
return utf8_from_wstring(m_Value);
}
const CStrIntern CParamNode::ToUTF8Intern() const
{
return CStrIntern(utf8_from_wstring(m_Value));
}
int CParamNode::ToInt() const
{
int ret = 0;
std::wstringstream strm;
strm << m_Value;
strm >> ret;
return ret;
}
fixed CParamNode::ToFixed() const
{
return fixed::FromString(CStrW(m_Value));
}
float CParamNode::ToFloat() const
{
float ret = 0;
std::wstringstream strm;
strm << m_Value;
strm >> ret;
return ret;
}
bool CParamNode::ToBool() const
{
if (m_Value == L"true")
return true;
else
return false;
}
const CParamNode::ChildrenMap& CParamNode::GetChildren() const
{
return m_Childs;
}
std::wstring CParamNode::EscapeXMLString(const std::wstring& str)
{
std::wstring ret;
ret.reserve(str.size());
for (size_t i = 0; i < str.size(); ++i)
{
wchar_t c = str[i];
switch (c)
{
case '<': ret += L"<"; break;
case '>': ret += L">"; break;
case '&': ret += L"&"; break;
case '"': ret += L"""; break;
case '\t': ret += L" "; break;
case '\n': ret += L"
"; break;
case '\r': ret += L"
"; break;
default:
if ((0x20 <= c && c <= 0xD7FF) || (0xE000 <= c && c <= 0xFFFD))
ret += c;
else
ret += 0xFFFD;
}
}
return ret;
}
std::wstring CParamNode::ToXML() const
{
std::wstringstream strm;
ToXML(strm);
return strm.str();
}
void CParamNode::ToXML(std::wostream& strm) const
{
strm << m_Value;
ChildrenMap::const_iterator it = m_Childs.begin();
for (; it != m_Childs.end(); ++it)
{
// Skip attributes here (they were handled when the caller output the tag)
if (it->first.length() && it->first[0] == '@')
continue;
std::wstring name (it->first.begin(), it->first.end());
strm << L"<" << name;
// Output the child's attributes first
ChildrenMap::const_iterator cit = it->second.m_Childs.begin();
for (; cit != it->second.m_Childs.end(); ++cit)
{
if (cit->first.length() && cit->first[0] == '@')
{
std::wstring attrname (cit->first.begin()+1, cit->first.end());
strm << L" " << attrname << L"=\"" << EscapeXMLString(cit->second.m_Value) << L"\"";
}
}
strm << L">";
it->second.ToXML(strm);
strm << L"" << name << ">";
}
}
void CParamNode::ToJSVal(JSContext* cx, bool cacheValue, JS::MutableHandleValue ret) const
{
if (cacheValue && m_ScriptVal != NULL)
{
ret.set(*m_ScriptVal);
return;
}
ConstructJSVal(cx, ret);
if (cacheValue)
m_ScriptVal.reset(new JS::PersistentRootedValue(cx, ret));
}
void CParamNode::ConstructJSVal(JSContext* cx, JS::MutableHandleValue ret) const
{
JSAutoRequest rq(cx);
if (m_Childs.empty())
{
// Empty node - map to undefined
if (m_Value.empty())
{
ret.setUndefined();
return;
}
// Just a string
utf16string text(m_Value.begin(), m_Value.end());
JS::RootedString str(cx, JS_InternUCStringN(cx, reinterpret_cast(text.data()), text.length()));
if (str)
{
ret.setString(str);
return;
}
// TODO: report error
ret.setUndefined();
return;
}
// Got child nodes - convert this node into a hash-table-style object:
JS::RootedObject obj(cx, JS_NewPlainObject(cx));
if (!obj)
{
ret.setUndefined();
return; // TODO: report error
}
JS::RootedValue childVal(cx);
for (std::map::const_iterator it = m_Childs.begin(); it != m_Childs.end(); ++it)
{
it->second.ConstructJSVal(cx, &childVal);
if (!JS_SetProperty(cx, obj, it->first.c_str(), childVal))
{
ret.setUndefined();
return; // TODO: report error
}
}
// If the node has a string too, add that as an extra property
if (!m_Value.empty())
{
utf16string text(m_Value.begin(), m_Value.end());
JS::RootedString str(cx, JS_InternUCStringN(cx, reinterpret_cast(text.data()), text.length()));
if (!str)
{
ret.setUndefined();
return; // TODO: report error
}
JS::RootedValue childVal(cx, JS::StringValue(str));
if (!JS_SetProperty(cx, obj, "_string", childVal))
{
ret.setUndefined();
return; // TODO: report error
}
}
ret.setObject(*obj);
}
void CParamNode::ResetScriptVal()
{
m_ScriptVal = NULL;
}