using System; using System.Collections.Generic; using System.Reflection; using System.Linq; using Encompass.Exceptions; namespace Encompass { /// /// WorldBuilder is used to construct a World from Engines, Renderers, and an initial state of Entities, Components, and Messages. /// /// /// WorldBuilder enforces certain rules about Engine structure. It is forbidden to have messages create cycles between Engines, /// and no Component may be written by more than one Engine. /// The WorldBuilder uses Engines and their Message read/emit information to determine a valid ordering of the Engines, which is given to the World. /// public class WorldBuilder { private readonly int _entityCapacity; private readonly List _engines = new List(); private readonly DirectedGraph _engineGraph = new DirectedGraph(); private readonly ComponentStore _startingExistingComponentStore; private readonly ComponentStore _startingUpToDateComponentStore; private readonly ComponentManager _componentManager; private readonly EntityManager _entityManager; private readonly MessageManager _messageManager; private readonly TimeManager _timeManager; private readonly RenderManager _renderManager; private readonly TrackingManager _trackingManager; private readonly Dictionary> _typeToReaders = new Dictionary>(); private readonly HashSet _senders = new HashSet(); private readonly HashSet _componentTypesToPreload = new HashSet(); private readonly HashSet _messageTypes = new HashSet(); private readonly Dictionary _typeToIndex = new Dictionary(); private readonly Dictionary _indexToType = new Dictionary(); public WorldBuilder(int entityCapacity = 32768) { _entityCapacity = entityCapacity; _timeManager = new TimeManager(); _trackingManager = new TrackingManager(); _componentManager = new ComponentManager(_typeToIndex, _indexToType); _messageManager = new MessageManager(_timeManager); _entityManager = new EntityManager(_componentManager, entityCapacity); _renderManager = new RenderManager(_entityManager); _startingExistingComponentStore = new ComponentStore(_typeToIndex); _startingUpToDateComponentStore = new ComponentStore(_typeToIndex); } /// /// Creates and returns a new empty Entity. /// public Entity CreateEntity() { return _entityManager.CreateEntity(); } /// /// Specifies that the given Message should be sent immediately on the first World Update. /// public void SendMessage(in TMessage message) where TMessage : struct { _messageManager.AddMessage(message); } /// /// Specifies that the given Message should be sent after the specified number of seconds after the first World Update. /// public void SendMessage(in TMessage message, double time) where TMessage : struct { _messageManager.AddMessage(message, time); } /// /// Sets Component data for the specified Component Type on the specified Entity. /// public void SetComponent(Entity entity, in TComponent component) where TComponent : struct { RegisterComponentType(); _startingExistingComponentStore.Set(entity.ID, component); _startingUpToDateComponentStore.Set(entity.ID, component); } internal void RegisterComponentTypeNonGeneric(Type type) { var method = GetType().GetMethod("RegisterComponentType", BindingFlags.NonPublic | BindingFlags.Instance); var generic = method.MakeGenericMethod(type); generic.Invoke(this, null); } internal void RegisterComponentType() where TComponent : struct { if (!_typeToIndex.ContainsKey(typeof(TComponent))) { var count = _typeToIndex.Count; _typeToIndex.Add(typeof(TComponent), count); _indexToType.Add(count, typeof(TComponent)); _componentTypesToPreload.Add(typeof(TComponent)); _componentManager.RegisterComponentType(); _startingExistingComponentStore.RegisterComponentType(); _startingUpToDateComponentStore.RegisterComponentType(); } } internal void RegisterMessageTypes(IEnumerable types) { _messageTypes.UnionWith(types); } /// /// Adds the specified Engine to the World. /// /// An instance of an Engine. public Engine AddEngine(TEngine engine) where TEngine : Engine { engine.AssignEntityManager(_entityManager); engine.AssignComponentManager(_componentManager); engine.AssignMessageManager(_messageManager); engine.AssignTimeManager(_timeManager); engine.AssignTrackingManager(_trackingManager); _engines.Add(engine); _engineGraph.AddNode(engine); var messageReceiveTypes = engine.ReceiveTypes; var messageSendTypes = engine.SendTypes; RegisterMessageTypes(engine.ReceiveTypes); RegisterMessageTypes(engine.SendTypes); foreach (var writeImmediateType in engine.WriteImmediateTypes) { foreach (var readImmediateType in engine.ReadImmediateTypes) { if (readImmediateType == writeImmediateType) { throw new EngineSelfCycleException("Engine {0} both writes and reads immediate Component {1}", engine.GetType().Name, writeImmediateType.Name); } } } foreach (var messageReceiveType in messageReceiveTypes) { foreach (var messageSendType in messageSendTypes) { if (messageReceiveType == messageSendType) { throw new EngineSelfCycleException("Engine {0} both receives and sends Message {1}", engine.GetType().Name, messageReceiveType.Name); } } } if (messageSendTypes.Count > 0 || engine.WriteImmediateTypes.Count > 0) { _senders.Add(engine); } foreach (var componentType in engine.ReadTypes) { RegisterComponentTypeNonGeneric(componentType); } foreach (var componentType in engine.AddTypes) { RegisterComponentTypeNonGeneric(componentType); } foreach (var componentType in engine.WriteTypes) { RegisterComponentTypeNonGeneric(componentType); } foreach (var componentType in engine.WriteImmediateTypes) { RegisterComponentTypeNonGeneric(componentType); } foreach (var componentType in engine.QueryWithTypes) { _trackingManager.RegisterComponentTypeToEngine(componentType, engine); if (engine.ReadImmediateTypes.Contains(componentType)) { _trackingManager.RegisterImmediateComponentTypeToEngine(componentType, engine); } } foreach (var componentType in engine.QueryWithoutTypes) { _trackingManager.RegisterComponentTypeToEngine(componentType, engine); if (engine.ReadImmediateTypes.Contains(componentType)) { _trackingManager.RegisterImmediateComponentTypeToEngine(componentType, engine); } } foreach (var receiveType in engine.ReceiveTypes) { if (!_typeToReaders.ContainsKey(receiveType)) { _typeToReaders.Add(receiveType, new HashSet()); } _typeToReaders[receiveType].Add(engine); } foreach (var receiveType in engine.ReadImmediateTypes) { if (!_typeToReaders.ContainsKey(receiveType)) { _typeToReaders.Add(receiveType, new HashSet()); } _typeToReaders[receiveType].Add(engine); } return engine; } /// /// Adds the specified Renderer to the World. /// public void AddRenderer(Renderer renderer) { renderer.AssignComponentManager(_componentManager); renderer.AssignEntityManager(_entityManager); _renderManager.AddRenderer(renderer); } private void BuildEngineGraph() { foreach (var senderEngine in _senders) { foreach (var messageType in senderEngine.SendTypes) { if (_typeToReaders.ContainsKey(messageType)) { foreach (var readerEngine in _typeToReaders[messageType]) { if (senderEngine != readerEngine) { if (!_engineGraph.Exists(senderEngine, readerEngine)) { _engineGraph.AddEdge(senderEngine, readerEngine); } } } } } foreach (var messageType in senderEngine.WriteImmediateTypes) { if (_typeToReaders.ContainsKey(messageType)) { foreach (var readerEngine in _typeToReaders[messageType]) { if (senderEngine != readerEngine) { if (!_engineGraph.Exists(senderEngine, readerEngine)) { _engineGraph.AddEdge(senderEngine, readerEngine); } } } } } } } /// /// Builds the World out of the state specified on the WorldBuilder. /// Validates and constructs an ordering of the given Engines. /// /// An instance of World. public World Build() { BuildEngineGraph(); if (_engineGraph.Cyclic()) { var cycles = _engineGraph.SimpleCycles(); var errorString = "Cycle(s) found in Engines: "; foreach (var cycle in cycles) { var reversed = cycle.Reverse(); errorString += "\n" + string.Join(" -> ", reversed.Select((engine) => engine.GetType().Name)) + " -> " + reversed.First().GetType().Name; } throw new EngineCycleException(errorString); } var writtenComponentTypesWithoutPriority = new HashSet(); var writtenComponentTypesWithPriority = new HashSet(); var duplicateWritesWithoutPriority = new List(); var duplicateWritesWithSamePriority = new List(); var writePriorities = new Dictionary>(); var writeMessageToEngines = new Dictionary>(); foreach (var engine in _engines) { if (engine.GetType().GetCustomAttribute() != null) { engine._usesTimeDilation = false; } var defaultWritePriorityAttribute = engine.GetType().GetCustomAttribute(false); foreach (var writeType in engine.WriteTypes) { int? priority = null; if (engine.WritePriorities.ContainsKey(writeType)) { priority = engine.WritePriorities[writeType]; } else if (defaultWritePriorityAttribute != null) { priority = defaultWritePriorityAttribute.WritePriority; } if (priority.HasValue) { writtenComponentTypesWithPriority.Add(writeType); if (!writePriorities.ContainsKey(writeType)) { writePriorities[writeType] = new HashSet(); } if (writePriorities[writeType].Contains(priority.Value)) { duplicateWritesWithSamePriority.Add(writeType); } else if (writtenComponentTypesWithoutPriority.Contains(writeType)) { duplicateWritesWithoutPriority.Add(writeType); } else { writePriorities[writeType].Add(priority.Value); } } else { if (writtenComponentTypesWithoutPriority.Contains(writeType) || writtenComponentTypesWithPriority.Contains(writeType)) { duplicateWritesWithoutPriority.Add(writeType); } else { writtenComponentTypesWithoutPriority.Add(writeType); } } if (!writeMessageToEngines.ContainsKey(writeType)) { writeMessageToEngines[writeType] = new List(); } writeMessageToEngines[writeType].Add(engine); } } if (duplicateWritesWithoutPriority.Count > 0) { var errorString = "Multiple Engines write the same Component without declaring priority: "; foreach (var componentType in duplicateWritesWithoutPriority) { errorString += "\n" + componentType.Name + " written by: " + string.Join(", ", writeMessageToEngines[componentType].Select((engine) => engine.GetType().Name)); } errorString += "\nTo resolve the conflict, add priority arguments to the Writes declarations or use a DefaultWritePriority attribute."; throw new EngineWriteConflictException(errorString); } if (duplicateWritesWithSamePriority.Count > 0) { var errorString = "Multiple Engines write the same Component with the same priority: "; foreach (var componentType in duplicateWritesWithSamePriority) { errorString += "\n" + componentType.Name + " written by: " + string.Join(", ", writeMessageToEngines[componentType].Select(engine => engine.GetType().Name)); } errorString += "\nTo resolve the conflict, add priority arguments to the Writes declarations or use a DefaultWritePriority attribute."; throw new EngineWriteConflictException(errorString); } PreloadJIT(_messageTypes); var engineOrder = new List(); foreach (var engine in _engineGraph.TopologicalSort()) { engineOrder.Add(engine); engine.BuildEntityQuery(); } var world = new World( engineOrder, _entityManager, _componentManager, _trackingManager, _messageManager, _timeManager, _renderManager ); _componentManager.SetExistingComponentStore(_startingExistingComponentStore); _componentManager.SetUpToDateComponentStore(_startingUpToDateComponentStore); _trackingManager.InitializeTracking(_entityManager.EntityIDs); return world; } /// /// This is necessary because Encompass heavily uses generic methods with value types, /// so the first time any code path runs the JIT gets smashed. This method warms up the runtime. /// It does so by grabbing all component and message types known to the WorldBuilder and /// executing every possible generic method that could be executed with those types. /// private void PreloadJIT(IEnumerable messageTypes) { var dummyTimeManager = new TimeManager(); var dummyMessageManager = new MessageManager(dummyTimeManager); var dummyTrackingManager = new TrackingManager(); var dummyComponentManager = new ComponentManager(_typeToIndex, _indexToType); var dummyEntityManager = new EntityManager(dummyComponentManager, _entityCapacity); var dummyRenderManager = new RenderManager(dummyEntityManager); var prepEngineOrder = new List(); foreach (var componentType in _componentTypesToPreload) { var dummyRegisterMethod = typeof(ComponentManager).GetMethod("RegisterComponentType", BindingFlags.Public | BindingFlags.Instance); var dummyGeneric = dummyRegisterMethod.MakeGenericMethod(componentType); dummyGeneric.Invoke(dummyComponentManager, null); } var uberEngine = new UberEngine(_componentTypesToPreload, messageTypes); uberEngine.AssignEntityManager(dummyEntityManager); uberEngine.AssignComponentManager(dummyComponentManager); uberEngine.AssignMessageManager(dummyMessageManager); uberEngine.AssignTimeManager(dummyTimeManager); uberEngine.AssignTrackingManager(dummyTrackingManager); var uberRenderer = new UberRenderer(_componentTypesToPreload); uberRenderer.AssignComponentManager(dummyComponentManager); uberRenderer.AssignEntityManager(dummyEntityManager); prepEngineOrder.Add(uberEngine); var dummyWorld = new World( prepEngineOrder, dummyEntityManager, dummyComponentManager, dummyTrackingManager, dummyMessageManager, dummyTimeManager, dummyRenderManager ); uberEngine.Write(); dummyComponentManager.WriteComponents(); dummyWorld.Update(1); uberEngine.Write(); dummyComponentManager.WriteComponents(); uberRenderer.SetEntity(uberEngine.Entity); uberRenderer.Render(1, 0); } } }