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
|