/* Copyright (C) 2010 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 "IComponent.h" #include "SimContext.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" CComponentManager::CComponentManager(const CSimContext& context, bool skipScriptFunctions) : m_NextScriptComponentTypeId(CID__LastNative), m_ScriptInterface("Engine"), m_SimContext(context), m_CurrentlyHotloading(false) { m_ScriptInterface.SetCallbackData(static_cast (this)); if (!skipScriptFunctions) { m_ScriptInterface.RegisterFunction ("RegisterComponentType"); m_ScriptInterface.RegisterFunction ("RegisterGlobal"); m_ScriptInterface.RegisterFunction ("QueryInterface"); m_ScriptInterface.RegisterFunction ("PostMessage"); m_ScriptInterface.RegisterFunction ("BroadcastMessage"); } // Define MT_*, IID_* as script globals #define MESSAGE(name) m_ScriptInterface.SetGlobal("MT_" #name, (int)MT_##name); #define INTERFACE(name) m_ScriptInterface.SetGlobal("IID_" #name, (int)IID_##name); #define COMPONENT(name) #include "simulation2/TypeList.h" #undef MESSAGE #undef INTERFACE #undef COMPONENT m_ScriptInterface.SetGlobal("SYSTEM_ENTITY", (int)SYSTEM_ENTITY); } CComponentManager::~CComponentManager() { DestroyAllComponents(); // Release GC roots std::map::iterator it = m_ComponentTypesById.begin(); for (; it != m_ComponentTypesById.end(); ++it) if (it->second.type == CT_Script) m_ScriptInterface.RemoveRoot(&it->second.ctor); } #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); void CComponentManager::LoadComponentTypes() { #include "simulation2/TypeList.h" } #undef MESSAGE #undef INTERFACE #undef COMPONENT bool CComponentManager::LoadScript(const std::wstring& filename, bool hotload) { m_CurrentlyHotloading = hotload; CVFSFile file; PSRETURN loadOk = file.Load(filename); debug_assert(loadOk == PSRETURN_OK); // TODO std::wstring content(file.GetBuffer(), file.GetBuffer() + file.GetBufferSize()); // TODO: encodings etc bool ok = m_ScriptInterface.LoadScript(filename, content); return ok; } void CComponentManager::Script_RegisterGlobal(void* cbdata, std::string name, CScriptVal value) { CComponentManager* componentManager = static_cast (cbdata); componentManager->m_ScriptInterface.SetGlobal(name.c_str(), value); } void CComponentManager::Script_RegisterComponentType(void* cbdata, int iid, std::string cname, CScriptVal ctor) { CComponentManager* componentManager = static_cast (cbdata); // 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) { // Allocate a new cid number cid = componentManager->m_NextScriptComponentTypeId++; componentManager->m_ComponentTypeIdsByName[cname] = cid; } else { if (!componentManager->m_CurrentlyHotloading) { componentManager->m_ScriptInterface.ReportError("Registering component type with already-registered name"); // TODO: report the actual name return; } const ComponentType& ctPrevious = componentManager->m_ComponentTypesById[cid]; // We can only replace scripted component types, not native ones if (ctPrevious.type != CT_Script) { componentManager->m_ScriptInterface.ReportError("Hotloading script component type with same name as native component"); 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 != ctWrapper.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; } } // Clean up the old component type componentManager->m_ScriptInterface.RemoveRoot(&componentManager->m_ComponentTypesById[cid].ctor); mustReloadComponents = true; } // Construct a new ComponentType, using the wrapper's alloc functions ComponentType ct = { CT_Script, ctWrapper.iid, ctWrapper.alloc, ctWrapper.dealloc, cname, ctor.get() }; componentManager->m_ComponentTypesById[cid] = ct; componentManager->m_CurrentComponent = cid; // Stop the ctor getting GCed componentManager->m_ScriptInterface.AddRoot(&componentManager->m_ComponentTypesById[cid].ctor, "ComponentType ctor"); // TODO: check carefully that roots will never get leaked etc // Find all the ctor prototype's On* methods, and subscribe to the appropriate messages: CScriptVal proto; if (!componentManager->m_ScriptInterface.GetProperty(ctor.get(), "prototype", proto)) return; // error std::vector methods; if (!componentManager->m_ScriptInterface.EnumeratePropertyNamesWithPrefix(proto.get(), "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 std::map::const_iterator mit = componentManager->m_MessageTypeIdsByName.find(name); if (mit == componentManager->m_MessageTypeIdsByName.end()) { componentManager->m_ScriptInterface.ReportError("Registered component has unrecognised 'On...' message handler method"); // TODO: report the actual name return; } componentManager->SubscribeToMessageType(cid, mit->second); } 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) { jsval instance = eit->second->GetJSInstance(); if (instance) componentManager->m_ScriptInterface.SetPrototype(instance, proto.get()); } } } IComponent* CComponentManager::Script_QueryInterface(void* cbdata, int ent, int iid) { CComponentManager* componentManager = static_cast (cbdata); IComponent* component = componentManager->QueryInterface((entity_id_t)ent, iid); return component; } void CComponentManager::Script_PostMessage(void* cbdata, int ent, int mtid, CScriptVal data) { CComponentManager* componentManager = static_cast (cbdata); CMessage* msg = CMessageFromJSVal(mtid, componentManager->m_ScriptInterface, data.get()); if (!msg) return; // error componentManager->PostMessage(ent, *msg); delete msg; } void CComponentManager::Script_BroadcastMessage(void* cbdata, int mtid, CScriptVal data) { CComponentManager* componentManager = static_cast (cbdata); CMessage* msg = CMessageFromJSVal(mtid, componentManager->m_ScriptInterface, data.get()); if (!msg) return; // error componentManager->BroadcastMessage(*msg); delete msg; } void CComponentManager::DestroyAllComponents() { // 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_SimContext); m_ComponentTypesById[iit->first].dealloc(eit->second); } } m_ComponentsByInterface.clear(); m_ComponentsByTypeId.clear(); } void CComponentManager::RegisterComponentType(InterfaceId iid, ComponentTypeId cid, AllocFunc alloc, DeallocFunc dealloc, const char* name) { ComponentType c = { CT_Native, iid, alloc, dealloc, name, 0 }; m_ComponentTypesById.insert(std::make_pair(cid, c)); m_ComponentTypeIdsByName[name] = cid; } void CComponentManager::RegisterComponentTypeScriptWrapper(InterfaceId iid, ComponentTypeId cid, AllocFunc alloc, DeallocFunc dealloc, const char* name) { ComponentType c = { CT_ScriptWrapper, iid, alloc, dealloc, name, 0 }; m_ComponentTypesById.insert(std::make_pair(cid, c)); m_ComponentTypeIdsByName[name] = cid; // TODO: merge with RegisterComponentType } void CComponentManager::RegisterMessageType(MessageTypeId mtid, const char* name) { m_MessageTypeIdsByName[name] = mtid; } void CComponentManager::SubscribeToMessageType(ComponentTypeId cid, MessageTypeId mtid) { // TODO: verify mtid debug_assert(cid == m_CurrentComponent); // TODO: this should be redundant std::vector& types = m_ComponentTypeIdsByMessageType[mtid]; types.push_back(cid); std::sort(types.begin(), types.end()); // TODO: just sort once at the end of LoadComponents } 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) { 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; LOGERROR(L"No script wrapper found for interface id %d", iid); // TODO: report name (if iid is valid at all) return CID__Invalid; } bool CComponentManager::AddComponent(entity_id_t ent, ComponentTypeId cid, const CParamNode& paramNode) { IComponent* component = ConstructComponent(ent, cid); if (!component) return false; component->Init(m_SimContext, paramNode); return true; } IComponent* CComponentManager::ConstructComponent(entity_id_t ent, ComponentTypeId cid) { std::map::const_iterator it = m_ComponentTypesById.find(cid); if (it == m_ComponentTypesById.end()) { LOGERROR(L"Invalid component id %d", cid); return NULL; } const ComponentType& ct = it->second; std::map& emap1 = m_ComponentsByInterface[ct.iid]; if (emap1.find(ent) != emap1.end()) { LOGERROR(L"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 jsval obj = 0; if (ct.type == CT_Script) { obj = m_ScriptInterface.CallConstructor(ct.ctor); if (!obj) { LOGERROR(L"Script component constructor failed"); return NULL; } } // Construct the new component IComponent* component = ct.alloc(m_ScriptInterface, obj); debug_assert(component); component->SetEntityId(ent); // Store a reference to the new component emap1.insert(std::make_pair(ent, component)); emap2.insert(std::make_pair(ent, component)); return component; } void CComponentManager::AddMockComponent(entity_id_t 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 std::map& emap1 = m_ComponentsByInterface[iid]; if (emap1.find(ent) != emap1.end()) debug_warn(L"Multiple components for interface"); emap1.insert(std::make_pair(ent, &component)); } IComponent* CComponentManager::QueryInterface(entity_id_t ent, InterfaceId iid) const { std::map >::const_iterator iit = m_ComponentsByInterface.find(iid); if (iit == m_ComponentsByInterface.end()) { // Invalid iid, or no entities implement this interface return NULL; } std::map::const_iterator eit = iit->second.find(ent); if (eit == iit->second.end()) { // This entity doesn't implement this interface return NULL; } return eit->second; } static std::map g_EmptyEntityMap; const std::map& CComponentManager::GetEntitiesWithInterface(InterfaceId iid) const { std::map >::const_iterator iit = m_ComponentsByInterface.find(iid); if (iit == m_ComponentsByInterface.end()) { // Invalid iid, or no entities implement this interface return g_EmptyEntityMap; } return iit->second; } void CComponentManager::PostMessage(entity_id_t ent, const CMessage& msg) const { std::map >::const_iterator it = m_ComponentTypeIdsByMessageType.find(msg.GetType()); if (it == m_ComponentTypeIdsByMessageType.end()) { // Nobody subscribed to this message return; } std::vector::const_iterator ctit = it->second.begin(); for (; ctit != it->second.end(); ++ctit) { int cid = *ctit; std::map >::const_iterator emap = m_ComponentsByTypeId.find(cid); if (emap == m_ComponentsByTypeId.end()) continue; std::map::const_iterator eit = emap->second.find(ent); if (eit != emap->second.end()) eit->second->HandleMessage(m_SimContext, msg); } } void CComponentManager::BroadcastMessage(const CMessage& msg) const { std::map >::const_iterator it = m_ComponentTypeIdsByMessageType.find(msg.GetType()); if (it == m_ComponentTypeIdsByMessageType.end()) { // Nobody subscribed to this message return; } std::vector::const_iterator ctit = it->second.begin(); for (; ctit != it->second.end(); ++ctit) { int cid = *ctit; std::map >::const_iterator emap = m_ComponentsByTypeId.find(cid); if (emap == m_ComponentsByTypeId.end()) continue; std::map::const_iterator eit = emap->second.begin(); for (; eit != emap->second.end(); ++eit) eit->second->HandleMessage(m_SimContext, msg); } }