using System; using System.Collections.Generic; using System.Reflection; using System.Linq; using Encompass.Exceptions; using MoonTools.Core.Graph; using MoonTools.Core.Graph.Extensions; 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 = GraphBuilder.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 DrawLayerManager _drawLayerManager; 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 bool _rendererRegistered = false; public WorldBuilder(int entityCapacity = 32768) { _entityCapacity = entityCapacity; _drawLayerManager = new DrawLayerManager(); _timeManager = new TimeManager(); _trackingManager = new TrackingManager(); _componentManager = new ComponentManager(_drawLayerManager, _typeToIndex); _messageManager = new MessageManager(_timeManager); _entityManager = new EntityManager(_componentManager, entityCapacity); _renderManager = new RenderManager(_entityManager, _drawLayerManager); _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, IMessage { _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, IMessage { _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, IComponent { RegisterComponentType(); _startingExistingComponentStore.Set(entity.ID, component); _startingUpToDateComponentStore.Set(entity.ID, component); if (component is IDrawableComponent drawableComponent) { _componentManager.RegisterDrawableComponent(entity.ID, drawableComponent.Layer); _drawLayerManager.RegisterOrderedDrawable(); } } internal void RegisterComponentType() where TComponent : struct, IComponent { if (!_typeToIndex.ContainsKey(typeof(TComponent))) { _typeToIndex.Add(typeof(TComponent), _typeToIndex.Count); _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.Union(engine.SendTypes)); foreach (var writeImmediateType in engine.WriteImmediateTypes.Intersect(engine.ReadImmediateTypes)) { throw new EngineSelfCycleException("Engine {0} both writes and reads immediate Component {1}", engine.GetType().Name, writeImmediateType.Name); } foreach (var messageType in messageReceiveTypes.Intersect(messageSendTypes)) { throw new EngineSelfCycleException("Engine {0} both receives and sends Message {1}", engine.GetType().Name, messageType.Name); } if (messageSendTypes.Count > 0 || engine.WriteImmediateTypes.Count > 0) { _senders.Add(engine); } foreach (var componentType in engine.QueryWithTypes.Union(engine.QueryWithoutTypes)) { _trackingManager.RegisterComponentTypeToEngine(componentType, engine); if (engine.ReadImmediateTypes.Contains(componentType)) { _trackingManager.RegisterImmediateComponentTypeToEngine(componentType, engine); } } foreach (var receiveType in engine.ReceiveTypes.Union(engine.ReadImmediateTypes)) { if (!_typeToReaders.ContainsKey(receiveType)) { _typeToReaders.Add(receiveType, new HashSet()); } _typeToReaders[receiveType].Add(engine); } return engine; } /// /// Registers a draw layer. This must be done before any Renderers are registered. /// /// The draw layer to register. public void RegisterDrawLayer(int layer) { if (_rendererRegistered) { throw new IllegalDrawLayerException("Cannot register a draw layer after a Renderer has been registered."); } _drawLayerManager.RegisterDrawLayer(layer); } /// /// Adds the specified OrderedRenderer to the World. /// public OrderedRenderer AddOrderedRenderer(OrderedRenderer renderer) where TComponent : struct, IComponent, IDrawableComponent { RegisterComponentType(); renderer.AssignEntityManager(_entityManager); renderer.AssignComponentManager(_componentManager); _renderManager.RegisterOrderedRenderer(renderer.InternalRender); _rendererRegistered = true; return renderer; } /// /// Adds the specified GeneralRenderer to the World at the specified layer. /// Higher layer numbers draw on top of lower layer numbers. /// /// An instance of a GeneralRenderer. /// The layer at which the GeneralRenderer should render. Higher numbers draw over lower numbers. public TRenderer AddGeneralRenderer(TRenderer renderer, int layer) where TRenderer : GeneralRenderer { renderer.AssignEntityManager(_entityManager); renderer.AssignComponentManager(_componentManager); _renderManager.RegisterGeneralRendererWithLayer(renderer, layer); _rendererRegistered = true; return renderer; } private void BuildEngineGraph() { foreach (var senderEngine in _senders) { foreach (var messageType in senderEngine.SendTypes.Union(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 dummyDrawLayerManager = new DrawLayerManager(); var dummyTrackingManager = new TrackingManager(); var dummyComponentManager = new ComponentManager(dummyDrawLayerManager, _typeToIndex); var dummyEntityManager = new EntityManager(dummyComponentManager, _entityCapacity); var dummyRenderManager = new RenderManager(dummyEntityManager, dummyDrawLayerManager); // doing reflection to grab all component types, because not all writes need to be declared foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { foreach (var componentType in assembly.GetTypes()) { if (typeof(IComponent).IsAssignableFrom(componentType) && componentType.IsValueType && !componentType.IsEnum && !componentType.IsPrimitive) { var method = typeof(WorldBuilder).GetMethod("RegisterComponentType", BindingFlags.NonPublic | BindingFlags.Instance); var generic = method.MakeGenericMethod(componentType); generic.Invoke(this, null); var dummyRegisterMethod = typeof(ComponentManager).GetMethod("RegisterComponentType", BindingFlags.Public | BindingFlags.Instance); var dummyGeneric = dummyRegisterMethod.MakeGenericMethod(componentType); dummyGeneric.Invoke(dummyComponentManager, null); } if (componentType.GetInterface("IDrawableComponent") != null) { // register draw layer using property value var instance = Activator.CreateInstance(componentType); var layerPropertyInfo = componentType.GetProperty("Layer"); dummyDrawLayerManager.RegisterDrawLayer((int)layerPropertyInfo.GetValue(instance)); var drawLayerManagerRegisterMethod = typeof(DrawLayerManager).GetMethod("RegisterOrderedDrawable"); var drawLayerManagerRegisterGenericMethod = drawLayerManagerRegisterMethod.MakeGenericMethod(componentType); drawLayerManagerRegisterGenericMethod.Invoke(dummyDrawLayerManager, null); } } } var prepEngineOrder = new List(); 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(); } } }