LCOV - code coverage report
Current view: top level - source/simulation2/system - ComponentManager.h (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 3 3 100.0 %
Date: 2023-01-19 00:18:29 Functions: 6 6 100.0 %

          Line data    Source code
       1             : /* Copyright (C) 2022 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #ifndef INCLUDED_COMPONENTMANAGER
      19             : #define INCLUDED_COMPONENTMANAGER
      20             : 
      21             : #include "ps/Filesystem.h"
      22             : #include "scriptinterface/ScriptInterface.h"
      23             : #include "simulation2/helpers/Player.h"
      24             : #include "simulation2/system/Components.h"
      25             : #include "simulation2/system/Entity.h"
      26             : #include "simulation2/system/IComponent.h"
      27             : 
      28             : #include <boost/random/linear_congruential.hpp>
      29             : #include <map>
      30             : #include <set>
      31             : #include <unordered_map>
      32             : 
      33             : class IComponent;
      34             : class CParamNode;
      35             : class CMessage;
      36             : class CSimContext;
      37             : class CDynamicSubscription;
      38             : 
      39             : class CComponentManager
      40             : {
      41             :     NONCOPYABLE(CComponentManager);
      42             : public:
      43             :     // We can't use EInterfaceId/etc directly, since scripts dynamically generate new IDs
      44             :     // and casting arbitrary ints to enums is undefined behaviour, so use 'int' typedefs
      45             :     typedef int InterfaceId;
      46             :     typedef int ComponentTypeId;
      47             :     typedef int MessageTypeId;
      48             : 
      49             : private:
      50             :     using AllocFunc = IComponent::AllocFunc;
      51             :     using DeallocFunc = IComponent::DeallocFunc;
      52             : 
      53             :     // ComponentTypes come in three types:
      54             :     //   Native: normal C++ component
      55             :     //   ScriptWrapper: C++ component that wraps a JS component implementation
      56             :     //   Script: a ScriptWrapper linked to a specific JS component implementation
      57             :     enum EComponentTypeType
      58             :     {
      59             :         CT_Native,
      60             :         CT_ScriptWrapper,
      61             :         CT_Script
      62             :     };
      63             : 
      64             :     // Representation of a component type, to be used when instantiating components
      65       31586 :     struct ComponentType
      66             :     {
      67             :         EComponentTypeType type;
      68             :         InterfaceId iid;
      69             :         AllocFunc alloc;
      70             :         DeallocFunc dealloc;
      71             :         std::string name;
      72             :         std::string schema; // RelaxNG fragment
      73             :         std::unique_ptr<JS::PersistentRootedValue> ctor; // only valid if type == CT_Script
      74             :     };
      75             : 
      76             : public:
      77             :     CComponentManager(CSimContext&, std::shared_ptr<ScriptContext> cx, bool skipScriptFunctions = false);
      78             :     ~CComponentManager();
      79             : 
      80             :     void LoadComponentTypes();
      81             : 
      82             :     /**
      83             :      * Load a script and execute it in a new function scope.
      84             :      * @param filename VFS path to load
      85             :      * @param hotload set to true if this script has been loaded before, and redefinitions of
      86             :      * existing components should not be considered errors
      87             :      */
      88             :     bool LoadScript(const VfsPath& filename, bool hotload = false);
      89             : 
      90             :     void RegisterMessageType(MessageTypeId mtid, const char* name);
      91             : 
      92             :     void RegisterComponentType(InterfaceId, ComponentTypeId, AllocFunc, DeallocFunc, const char*, const std::string& schema);
      93             :     void RegisterComponentTypeScriptWrapper(InterfaceId, ComponentTypeId, AllocFunc, DeallocFunc, const char*, const std::string& schema);
      94             : 
      95             :     void MarkScriptedComponentForSystemEntity(CComponentManager::ComponentTypeId cid);
      96             : 
      97             :     /**
      98             :      * Subscribe the current component type to the given message type.
      99             :      * Each component's HandleMessage will be called on any BroadcastMessage of this message type,
     100             :      * or on any PostMessage of this type targeted at the component's entity.
     101             :      * Must only be called by a component type's ClassInit.
     102             :      */
     103             :     void SubscribeToMessageType(MessageTypeId mtid);
     104             : 
     105             :     /**
     106             :      * Subscribe the current component type to all messages of the given message type.
     107             :      * Each component's HandleMessage will be called on any BroadcastMessage or PostMessage of this message type,
     108             :      * regardless of the entity.
     109             :      * Must only be called by a component type's ClassInit.
     110             :      */
     111             :     void SubscribeGloballyToMessageType(MessageTypeId mtid);
     112             : 
     113             :     /**
     114             :      * Subscribe the given component instance to all messages of the given message type.
     115             :      * The component's HandleMessage will be called on any BroadcastMessage or PostMessage of
     116             :      * this message type, regardless of the entity.
     117             :      *
     118             :      * This can be called at any time (including inside the HandleMessage callback for this message type).
     119             :      *
     120             :      * The component type must not have statically subscribed to this message type in its ClassInit.
     121             :      *
     122             :      * The subscription status is not saved or network-synchronised. Components must remember to
     123             :      * resubscribe in their Deserialize methods if they still want the message.
     124             :      *
     125             :      * This is primarily intended for Interpolate and RenderSubmit messages, to avoid the cost of
     126             :      * sending the message to components that do not currently need to do any rendering.
     127             :      */
     128             :     void DynamicSubscriptionNonsync(MessageTypeId mtid, IComponent* component, bool enabled);
     129             : 
     130             :     /**
     131             :      * @param cname Requested component type name (not including any "CID" or "CCmp" prefix)
     132             :      * @return The component type id, or CID__Invalid if not found
     133             :      */
     134             :     ComponentTypeId LookupCID(const std::string& cname) const;
     135             : 
     136             :     /**
     137             :      * @return The name of the given component type, or "" if not found
     138             :      */
     139             :     std::string LookupComponentTypeName(ComponentTypeId cid) const;
     140             : 
     141             :     /**
     142             :      * Set up an empty SYSTEM_ENTITY. Must be called after ResetState() and before GetSystemEntity().
     143             :      */
     144             :     void InitSystemEntity();
     145             : 
     146             :     /**
     147             :      * Returns a CEntityHandle with id SYSTEM_ENTITY.
     148             :      */
     149          22 :     CEntityHandle GetSystemEntity() { ASSERT(m_SystemEntity.GetId() == SYSTEM_ENTITY); return m_SystemEntity; }
     150             : 
     151             :     /**
     152             :      * Returns a CEntityHandle with id @p ent.
     153             :      * If @p allowCreate is true and there is no existing CEntityHandle, a new handle will be allocated.
     154             :      */
     155             :     CEntityHandle LookupEntityHandle(entity_id_t ent, bool allowCreate = false);
     156             : 
     157             :     /**
     158             :      * Returns true if the entity with id @p ent exists.
     159             :      */
     160             :     bool EntityExists(entity_id_t ent) const;
     161             : 
     162             :     /**
     163             :      * Returns a new entity ID that has never been used before.
     164             :      * This affects the simulation state so it must only be called in network-synchronised ways.
     165             :      */
     166             :     entity_id_t AllocateNewEntity();
     167             : 
     168             :     /**
     169             :      * Returns a new local entity ID that has never been used before.
     170             :      * This entity will not be synchronised over the network, stored in saved games, etc.
     171             :      */
     172             :     entity_id_t AllocateNewLocalEntity();
     173             : 
     174             :     /**
     175             :      * Returns a new entity ID that has never been used before.
     176             :      * If possible, returns preferredId, and ensures this ID won't be allocated again.
     177             :      * This affects the simulation state so it must only be called in network-synchronised ways.
     178             :      */
     179             :     entity_id_t AllocateNewEntity(entity_id_t preferredId);
     180             : 
     181             :     /**
     182             :      * Constructs a component of type 'cid', initialised with data 'paramNode',
     183             :      * and attaches it to entity 'ent'.
     184             :      *
     185             :      * @return true on success; false on failure, and logs an error message
     186             :      */
     187             :     bool AddComponent(CEntityHandle ent, ComponentTypeId cid, const CParamNode& paramNode);
     188             : 
     189             :     /**
     190             :      * Add all system components to the system entity (skip the scripted components or the AI components on demand)
     191             :      */
     192             :     void AddSystemComponents(bool skipScriptedComponents, bool skipAI);
     193             : 
     194             :     /**
     195             :      * Adds an externally-created component, so that it is returned by QueryInterface
     196             :      * but does not get destroyed and does not receive messages from the component manager.
     197             :      * (This is intended for unit tests that need to add mock objects the tested components
     198             :      * expect to exist.)
     199             :      */
     200             :     void AddMockComponent(CEntityHandle ent, InterfaceId iid, IComponent& component);
     201             : 
     202             :     /**
     203             :      * Allocates a component object of type 'cid', and attaches it to entity 'ent'.
     204             :      * (The component's Init is not called here - either Init or Deserialize must be called
     205             :      * before using the returned object.)
     206             :      */
     207             :     IComponent* ConstructComponent(CEntityHandle ent, ComponentTypeId cid);
     208             : 
     209             :     /**
     210             :      * Constructs an entity based on the given template, and adds it the world with
     211             :      * entity ID @p ent. There should not be any existing components with that entity ID.
     212             :      * @return ent, or INVALID_ENTITY on error
     213             :      */
     214             :     entity_id_t AddEntity(const std::wstring& templateName, entity_id_t ent);
     215             : 
     216             :     /**
     217             :      * Destroys all the components belonging to the specified entity when FlushDestroyedComponents is called.
     218             :      * Has no effect if the entity does not exist, or has already been added to the destruction queue.
     219             :      */
     220             :     void DestroyComponentsSoon(entity_id_t ent);
     221             : 
     222             :     /**
     223             :      * Does the actual destruction of components from DestroyComponentsSoon.
     224             :      * This must not be called if the component manager is on the call stack (since it
     225             :      * will break internal iterators).
     226             :      */
     227             :     void FlushDestroyedComponents();
     228             : 
     229             :     IComponent* QueryInterface(entity_id_t ent, InterfaceId iid) const;
     230             : 
     231             :     using InterfacePair = std::pair<entity_id_t, IComponent*>;
     232             :     using InterfaceList = std::vector<InterfacePair>;
     233             :     using InterfaceListUnordered = std::unordered_map<entity_id_t, IComponent*>;
     234             : 
     235             :     InterfaceList GetEntitiesWithInterface(InterfaceId iid) const;
     236             :     const InterfaceListUnordered& GetEntitiesWithInterfaceUnordered(InterfaceId iid) const;
     237             : 
     238             :     /**
     239             :      * Send a message, targeted at a particular entity. The message will be received by any
     240             :      * components of that entity which subscribed to the message type, and by any other components
     241             :      * that subscribed globally to the message type.
     242             :      */
     243             :     void PostMessage(entity_id_t ent, const CMessage& msg);
     244             : 
     245             :     /**
     246             :      * Send a message, not targeted at any particular entity. The message will be received by any
     247             :      * components that subscribed (either globally or not) to the message type.
     248             :      */
     249             :     void BroadcastMessage(const CMessage& msg);
     250             : 
     251             :     /**
     252             :      * Resets the dynamic simulation state (deletes all entities, resets entity ID counters;
     253             :      * doesn't unload/reload component scripts).
     254             :      */
     255             :     void ResetState();
     256             : 
     257             :     /**
     258             :      * Initializes the random number generator with a seed determined by the host.
     259             :      */
     260             :     void SetRNGSeed(u32 seed);
     261             : 
     262             :     // Various state serialization functions:
     263             :     bool ComputeStateHash(std::string& outHash, bool quick) const;
     264             :     bool DumpDebugState(std::ostream& stream, bool includeDebugInfo) const;
     265             :     // FlushDestroyedComponents must be called before SerializeState (since the destruction queue
     266             :     // won't get serialized)
     267             :     bool SerializeState(std::ostream& stream) const;
     268             :     bool DeserializeState(std::istream& stream);
     269             : 
     270             :     std::string GenerateSchema() const;
     271             : 
     272         325 :     ScriptInterface& GetScriptInterface() { return m_ScriptInterface; }
     273             : 
     274             : private:
     275             :     // Implementations of functions exposed to scripts
     276             :     void Script_RegisterComponentType_Common(int iid, const std::string& cname, JS::HandleValue ctor, bool reRegister, bool systemComponent);
     277             :     void Script_RegisterComponentType(int iid, const std::string& cname, JS::HandleValue ctor);
     278             :     void Script_RegisterSystemComponentType(int iid, const std::string& cname, JS::HandleValue ctor);
     279             :     void Script_ReRegisterComponentType(int iid, const std::string& cname, JS::HandleValue ctor);
     280             :     void Script_RegisterInterface(const std::string& name);
     281             :     void Script_RegisterMessageType(const std::string& name);
     282             :     void Script_RegisterGlobal(const std::string& name, JS::HandleValue value);
     283             :     std::vector<int> Script_GetEntitiesWithInterface(int iid);
     284             :     std::vector<IComponent*> Script_GetComponentsWithInterface(int iid);
     285             :     void Script_PostMessage(int ent, int mtid, JS::HandleValue data);
     286             :     void Script_BroadcastMessage(int mtid, JS::HandleValue data);
     287             :     int Script_AddEntity(const std::wstring& templateName);
     288             :     int Script_AddLocalEntity(const std::wstring& templateName);
     289             :     const CParamNode& Script_GetTemplate(const std::string& templateName);
     290             : 
     291             :     CMessage* ConstructMessage(int mtid, JS::HandleValue data);
     292             :     void SendGlobalMessage(entity_id_t ent, const CMessage& msg);
     293             : 
     294             :     void FlattenDynamicSubscriptions();
     295             :     void RemoveComponentDynamicSubscriptions(IComponent* component);
     296             : 
     297             :     ComponentTypeId GetScriptWrapper(InterfaceId iid);
     298             : 
     299             :     CEntityHandle AllocateEntityHandle(entity_id_t ent);
     300             : 
     301             :     ScriptInterface m_ScriptInterface;
     302             :     CSimContext& m_SimContext;
     303             : 
     304             :     CEntityHandle m_SystemEntity;
     305             : 
     306             :     ComponentTypeId m_CurrentComponent; // used when loading component types
     307             :     bool m_CurrentlyHotloading;
     308             : 
     309             :     // TODO: some of these should be vectors
     310             :     std::map<ComponentTypeId, ComponentType> m_ComponentTypesById;
     311             :     std::vector<CComponentManager::ComponentTypeId> m_ScriptedSystemComponents;
     312             :     std::vector<std::unordered_map<entity_id_t, IComponent*> > m_ComponentsByInterface; // indexed by InterfaceId
     313             :     std::map<ComponentTypeId, std::map<entity_id_t, IComponent*> > m_ComponentsByTypeId;
     314             :     std::map<MessageTypeId, std::vector<ComponentTypeId> > m_LocalMessageSubscriptions;
     315             :     std::map<MessageTypeId, std::vector<ComponentTypeId> > m_GlobalMessageSubscriptions;
     316             :     std::map<std::string, ComponentTypeId> m_ComponentTypeIdsByName;
     317             :     std::map<std::string, MessageTypeId> m_MessageTypeIdsByName;
     318             :     std::map<MessageTypeId, std::string> m_MessageTypeNamesById;
     319             :     std::map<std::string, InterfaceId> m_InterfaceIdsByName;
     320             : 
     321             :     std::map<MessageTypeId, CDynamicSubscription> m_DynamicMessageSubscriptionsNonsync;
     322             :     std::map<IComponent*, std::set<MessageTypeId> > m_DynamicMessageSubscriptionsNonsyncByComponent;
     323             : 
     324             :     std::unordered_map<entity_id_t, SEntityComponentCache*> m_ComponentCaches;
     325             : 
     326             :     // TODO: maintaining both ComponentsBy* is nasty; can we get rid of one,
     327             :     // while keeping QueryInterface and PostMessage sufficiently efficient?
     328             : 
     329             :     std::vector<entity_id_t> m_DestructionQueue;
     330             : 
     331             :     ComponentTypeId m_NextScriptComponentTypeId;
     332             :     entity_id_t m_NextEntityId;
     333             :     entity_id_t m_NextLocalEntityId;
     334             : 
     335             :     boost::rand48 m_RNG;
     336             : 
     337             :     friend class TestComponentManager;
     338             : };
     339             : 
     340             : #endif // INCLUDED_COMPONENTMANAGER

Generated by: LCOV version 1.13