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""; } } 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; }