diff --git a/encompass-cs/ComponentManager.cs b/encompass-cs/ComponentManager.cs index d5545b2..38496aa 100644 --- a/encompass-cs/ComponentManager.cs +++ b/encompass-cs/ComponentManager.cs @@ -6,6 +6,7 @@ namespace Encompass { internal class ComponentManager { + private Dictionary componentIDToType = new Dictionary(); private Dictionary IDToComponent = new Dictionary(); private Dictionary> entityIDToComponentIDs = new Dictionary>(); private Dictionary componentIDToEntityID = new Dictionary(); @@ -22,6 +23,7 @@ namespace Encompass var componentID = Guid.NewGuid(); IDToComponent[componentID] = component; + componentIDToType[componentID] = typeof(TComponent); if (!entityIDToComponentIDs.ContainsKey(entityID)) { @@ -54,6 +56,13 @@ namespace Encompass Enumerable.Empty>(); } + internal IEnumerable> GetActiveComponentsByType(Type type) + { + return activeComponents.ContainsKey(type) ? + activeComponents[type].Select((id) => new KeyValuePair(id, IDToComponent[id])) : + Enumerable.Empty>(); + } + internal KeyValuePair GetActiveComponentByType() where TComponent : struct, IComponent { return GetActiveComponentsByType().Single(); @@ -66,14 +75,41 @@ namespace Encompass return entity_components.Intersect(active_components_by_type); } + internal IEnumerable> GetComponentsByEntityAndType(Guid entityID, Type type) + { + var entityComponents = GetComponentsByEntity(entityID); + var activeComponentsByType = GetActiveComponentsByType(type); + return entityComponents.Intersect(activeComponentsByType); + } + internal bool EntityHasComponentOfType(Guid entityID) where TComponent : struct, IComponent { return GetComponentsByEntityAndType(entityID).Any(); } + internal bool EntityHasComponentOfType(Guid entityID, Type type) + { + return GetComponentsByEntityAndType(entityID, type).Any(); + } + + internal IComponent GetComponentByID(Guid componentID) + { + return IDToComponent[componentID]; + } + + internal Type GetComponentTypeByID(Guid componentID) + { + return componentIDToType[componentID]; + } + + internal Guid GetEntityIDFromComponentID(Guid componentID) + { + return componentIDToEntityID[componentID]; + } + internal void UpdateComponent(Guid componentID, TComponent newComponentValue) where TComponent : struct, IComponent { - var entityID = componentIDToEntityID[componentID]; + var entityID = GetEntityIDFromComponentID(componentID); IDToComponent[componentID] = newComponentValue; } diff --git a/encompass-cs/Entity.cs b/encompass-cs/Entity.cs index 0c9ce69..966012f 100644 --- a/encompass-cs/Entity.cs +++ b/encompass-cs/Entity.cs @@ -36,6 +36,11 @@ namespace Encompass return componentManager.EntityHasComponentOfType(id); } + internal bool HasComponent(Type type) + { + return componentManager.EntityHasComponentOfType(id, type); + } + internal void RemoveAllComponents() { componentManager.RemoveAllComponentsFromEntity(id); diff --git a/encompass-cs/EntityManager.cs b/encompass-cs/EntityManager.cs index 49c4ffe..7bf3d30 100644 --- a/encompass-cs/EntityManager.cs +++ b/encompass-cs/EntityManager.cs @@ -26,7 +26,7 @@ namespace Encompass public Entity GetEntity(Guid id) { - return this.IDToEntity[id]; + return IDToEntity[id]; } public void MarkForDestroy(Entity entity) diff --git a/encompass-cs/IDrawComponent.cs b/encompass-cs/IDrawComponent.cs new file mode 100644 index 0000000..fa7c1c7 --- /dev/null +++ b/encompass-cs/IDrawComponent.cs @@ -0,0 +1,6 @@ +namespace Encompass +{ + public interface IDrawComponent : IComponent, IRenderable + { + } +} diff --git a/encompass-cs/IRenderable.cs b/encompass-cs/IRenderable.cs new file mode 100644 index 0000000..cc32e4f --- /dev/null +++ b/encompass-cs/IRenderable.cs @@ -0,0 +1,7 @@ +namespace Encompass +{ + public interface IRenderable + { + int Layer { get; set; } + } +} diff --git a/encompass-cs/RenderManager.cs b/encompass-cs/RenderManager.cs new file mode 100644 index 0000000..4d2ce5c --- /dev/null +++ b/encompass-cs/RenderManager.cs @@ -0,0 +1,134 @@ +using System; +using System.Reflection; +using System.Collections.Generic; + +namespace Encompass +{ + internal class RenderManager + { + private EntityManager entityManager; + private ComponentManager componentManager; + + private SortedList layerOrder = new SortedList(); + private Dictionary> layerIndexToComponentIDs = new Dictionary>(); + private Dictionary> layerIndexToGeneralRenderers = new Dictionary>(); + + private Dictionary drawComponentTypeToEntityRenderer = new Dictionary(); + + public RenderManager(EntityManager entityManager, ComponentManager componentManager) + { + this.entityManager = entityManager; + this.componentManager = componentManager; + } + + public void RegisterEntityRenderer(EntityRenderer renderer) + { + var rendersAttribute = renderer.GetType().GetCustomAttribute(false); + if (rendersAttribute != null) + { + drawComponentTypeToEntityRenderer.Add(rendersAttribute.drawComponentType, renderer); + } + } + + public void RegisterGeneralRendererWithLayer(GeneralRenderer renderer, int layer) + { + if (layerIndexToGeneralRenderers.ContainsKey(layer)) + { + var set = layerIndexToGeneralRenderers[layer]; + set.Add(renderer); + } + else + { + var set = new HashSet(); + layerIndexToGeneralRenderers.Add(layer, set); + set.Add(renderer); + } + + if (!layerOrder.ContainsKey(layer)) + { + layerOrder.Add(layer, layer); + } + } + + public void UnregisterGeneralRendererWithLayer(GeneralRenderer renderer, int layer) + { + if (layerIndexToGeneralRenderers.ContainsKey(layer)) + { + layerIndexToGeneralRenderers[layer].Remove(renderer); + } + } + + public void AdjustRendererLayer(GeneralRenderer renderer, int oldLayer, int newLayer) + { + UnregisterGeneralRendererWithLayer(renderer, oldLayer); + RegisterGeneralRendererWithLayer(renderer, newLayer); + } + + public void RegisterComponentWithLayer(Guid id, int layer) + { + if (layerIndexToComponentIDs.ContainsKey(layer)) + { + var set = layerIndexToComponentIDs[layer]; + set.Add(id); + } + else + { + var set = new HashSet(); + layerIndexToComponentIDs.Add(layer, set); + set.Add(id); + } + + if (!layerOrder.ContainsKey(layer)) + { + layerOrder.Add(layer, layer); + } + } + + public void UnRegisterComponentWithLayer(Guid id, int layer) + { + if (layerIndexToComponentIDs.ContainsKey(layer)) + { + layerIndexToComponentIDs[layer].Remove(id); + } + } + + public void AdjustComponentLayer(Guid id, int oldLayer, int newLayer) + { + UnRegisterComponentWithLayer(id, oldLayer); + RegisterComponentWithLayer(id, newLayer); + } + + public void Draw() + { + foreach (var layerKVPair in layerOrder) + { + var layer = layerKVPair.Key; + var componentIDSet = layerIndexToComponentIDs[layer]; + var generalRendererSet = layerIndexToGeneralRenderers[layer]; + + foreach (var componentID in componentIDSet) + { + var component = componentManager.GetComponentByID(componentID); + var componentType = componentManager.GetComponentTypeByID(componentID); + + if (drawComponentTypeToEntityRenderer.ContainsKey(componentType)) + { + var renderer = drawComponentTypeToEntityRenderer[componentType]; + var entityID = componentManager.GetEntityIDFromComponentID(componentID); + + if (renderer.IsTracking(entityID)) + { + var entity = entityManager.GetEntity(entityID); + renderer.Render(entity); + } + } + } + + foreach (var generalRenderer in generalRendererSet) + { + generalRenderer.Render(); + } + } + } + } +} diff --git a/encompass-cs/Renderer.cs b/encompass-cs/Renderer.cs new file mode 100644 index 0000000..1cf3351 --- /dev/null +++ b/encompass-cs/Renderer.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Encompass +{ + public abstract class Renderer + { + private EntityManager entityManager; + private ComponentManager componentManager; + + internal void AssignEntityManager(EntityManager entityManager) + { + this.entityManager = entityManager; + } + + internal void AssignComponentManager(ComponentManager componentManager) + { + this.componentManager = componentManager; + } + + protected Entity GetEntity(Guid entityID) + { + return entityManager.GetEntity(entityID); + } + + protected IEnumerable> ReadComponents() where TComponent : struct, IComponent + { + return componentManager.GetActiveComponentsByType(); + } + + protected KeyValuePair ReadComponent() where TComponent : struct, IComponent + { + return componentManager.GetActiveComponentByType(); + } + } +} diff --git a/encompass-cs/World.cs b/encompass-cs/World.cs index f282c91..6ca62cf 100644 --- a/encompass-cs/World.cs +++ b/encompass-cs/World.cs @@ -8,18 +8,21 @@ namespace Encompass private EntityManager entityManager; private ComponentManager componentManager; private MessageManager messageManager; + private RenderManager renderManager; internal World( List enginesInOrder, EntityManager entityManager, ComponentManager componentManager, - MessageManager messageManager + MessageManager messageManager, + RenderManager renderManager ) { this.enginesInOrder = enginesInOrder; this.entityManager = entityManager; this.componentManager = componentManager; this.messageManager = messageManager; + this.renderManager = renderManager; } public void Update(float dt) @@ -35,5 +38,10 @@ namespace Encompass componentManager.DeactivateComponents(); componentManager.RemoveComponents(); } + + public void Draw() + { + renderManager.Draw(); + } } } diff --git a/encompass-cs/WorldBuilder.cs b/encompass-cs/WorldBuilder.cs index 0b9c0eb..6a12b2a 100644 --- a/encompass-cs/WorldBuilder.cs +++ b/encompass-cs/WorldBuilder.cs @@ -13,6 +13,7 @@ namespace Encompass private ComponentManager componentManager; private EntityManager entityManager; private MessageManager messageManager; + private RenderManager renderManager; private Dictionary> messageTypeToEmitters = new Dictionary>(); private Dictionary> messageTypeToReaders = new Dictionary>(); @@ -22,11 +23,12 @@ namespace Encompass componentManager = new ComponentManager(); entityManager = new EntityManager(componentManager); messageManager = new MessageManager(); + renderManager = new RenderManager(entityManager, componentManager); } public Entity CreateEntity() { - return this.entityManager.CreateEntity(); + return entityManager.CreateEntity(); } public Engine AddEngine() where TEngine : Engine, new() @@ -88,6 +90,25 @@ namespace Encompass return engine; } + public TRenderer AddRenderer() where TRenderer : Renderer, new() + { + var renderer = new TRenderer(); + renderer.AssignEntityManager(entityManager); + renderer.AssignComponentManager(componentManager); + + if (renderer is EntityRenderer) + { + renderManager.RegisterEntityRenderer(renderer as EntityRenderer); + } + else if (renderer is GeneralRenderer) + { + var generalRenderer = renderer as GeneralRenderer; + renderManager.RegisterGeneralRendererWithLayer(generalRenderer, generalRenderer.Layer); + } + + return renderer; + } + public World Build() { if (engineGraph.Cyclic()) @@ -155,9 +176,10 @@ namespace Encompass var world = new World( engineOrder, - this.entityManager, - this.componentManager, - this.messageManager + entityManager, + componentManager, + messageManager, + renderManager ); this.componentManager.ActivateComponents(); diff --git a/encompass-cs/attributes/Emits.cs b/encompass-cs/attributes/Emits.cs index dc25fca..ff6082f 100644 --- a/encompass-cs/attributes/Emits.cs +++ b/encompass-cs/attributes/Emits.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; namespace Encompass { - [System.AttributeUsage(System.AttributeTargets.Class)] - public class Emits : System.Attribute + [AttributeUsage(AttributeTargets.Class)] + public class Emits : Attribute { public readonly List emitMessageTypes; diff --git a/encompass-cs/attributes/Renders.cs b/encompass-cs/attributes/Renders.cs new file mode 100644 index 0000000..70a3c28 --- /dev/null +++ b/encompass-cs/attributes/Renders.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Encompass +{ + [AttributeUsage(AttributeTargets.Class)] + public class Renders : Attribute + { + public Type drawComponentType; + public readonly List componentTypes; + + public Renders(Type drawComponentType, params Type[] componentTypes) + { + this.drawComponentType = drawComponentType; + this.componentTypes = new List(componentTypes); + } + } +} diff --git a/encompass-cs/renderers/EntityRenderer.cs b/encompass-cs/renderers/EntityRenderer.cs new file mode 100644 index 0000000..18d148e --- /dev/null +++ b/encompass-cs/renderers/EntityRenderer.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Encompass +{ + public abstract class EntityRenderer : Renderer + { + private readonly List componentTypes = new List(); + private readonly Type drawComponentType; + + private EntityTracker entityTracker = new EntityTracker(); + + public abstract void Render(Entity entity); + + public bool CheckAndTrackEntity(Guid entityID) + { + var entity = GetEntity(entityID); + var shouldTrack = CheckEntity(entity); + if (shouldTrack) { entityTracker.TrackEntity(entityID); } + return shouldTrack; + } + + public bool CheckAndUntrackEntity(Guid entityID) + { + var entity = GetEntity(entityID); + var shouldUntrack = CheckEntity(entity); + if (shouldUntrack) { entityTracker.UntrackEntity(entityID); } + return shouldUntrack; + } + + public bool IsTracking(Guid entityID) + { + return entityTracker.IsTracking(entityID); + } + + internal bool CheckEntity(Entity entity) + { + return EntityChecker.CheckEntity(entity, componentTypes) && + entity.HasComponent(drawComponentType); + } + } +} diff --git a/encompass-cs/renderers/GeneralRenderer.cs b/encompass-cs/renderers/GeneralRenderer.cs new file mode 100644 index 0000000..6046146 --- /dev/null +++ b/encompass-cs/renderers/GeneralRenderer.cs @@ -0,0 +1,8 @@ +namespace Encompass +{ + public abstract class GeneralRenderer : Renderer, IRenderable + { + public int Layer { get; set; } + public abstract void Render(); + } +} diff --git a/encompass-cs/utility/EntityChecker.cs b/encompass-cs/utility/EntityChecker.cs new file mode 100644 index 0000000..eaabb76 --- /dev/null +++ b/encompass-cs/utility/EntityChecker.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Encompass +{ + internal static class EntityChecker + { + public static bool CheckEntity(Entity entity, IEnumerable componentTypes) + { + return componentTypes.All((componentType) => entity.HasComponent(componentType)); + } + } +} diff --git a/encompass-cs/utility/EntityTracker.cs b/encompass-cs/utility/EntityTracker.cs new file mode 100644 index 0000000..79438ad --- /dev/null +++ b/encompass-cs/utility/EntityTracker.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Encompass +{ + internal class EntityTracker + { + private HashSet trackedEntityIDs = new HashSet(); + private HashSet deactivatedEntityIDs = new HashSet(); + + public IEnumerable TrackedEntityIDs + { + get { return trackedEntityIDs; } + } + + public IEnumerable DeactivatedEntityIds + { + get { return deactivatedEntityIDs; } + } + + public void TrackEntity(Guid entityID) + { + trackedEntityIDs.Add(entityID); + } + + public void UntrackEntity(Guid entityID) + { + if (trackedEntityIDs.Remove(entityID)) + { + deactivatedEntityIDs.Remove(entityID); + } + } + + public void ActivateEntity(Guid entityID) + { + if (deactivatedEntityIDs.Remove(entityID)) + { + trackedEntityIDs.Add(entityID); + } + } + + public void DeactivateEntity(Guid entityID) + { + if (trackedEntityIDs.Remove(entityID)) + { + deactivatedEntityIDs.Add(entityID); + } + } + + public bool IsTracking(Guid entityID) + { + return trackedEntityIDs.Contains(entityID); + } + } +} diff --git a/test/EntityRendererTest.cs b/test/EntityRendererTest.cs new file mode 100644 index 0000000..69a8251 --- /dev/null +++ b/test/EntityRendererTest.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Text; + +using NUnit.Framework; +using Encompass; + +namespace Tests +{ + public class EntityRendererTest + { + struct AComponent : IComponent { } + struct BComponent : IComponent { } + struct CComponent : IComponent { } + + struct TestDrawComponent : IDrawComponent + { + public int Layer { get; set; } + } + + [Renders(typeof(TestDrawComponent), typeof(AComponent), typeof(BComponent))] + class TestRenderer : EntityRenderer + { + public override void Render(Entity entity) { } + } + + [Test] + public void CheckAndTrackEntities() + { + var worldBuilder = new WorldBuilder(); + var renderer = worldBuilder.AddRenderer(); + + AComponent aComponent; + BComponent bComponent; + TestDrawComponent testDrawComponent = default(TestDrawComponent); + + var entityToTrack = worldBuilder.CreateEntity(); + entityToTrack.AddComponent(aComponent); + entityToTrack.AddComponent(bComponent); + entityToTrack.AddComponent(testDrawComponent); + + var entityNotToTrack = worldBuilder.CreateEntity(); + entityNotToTrack.AddComponent(aComponent); + entityNotToTrack.AddComponent(testDrawComponent); + + var entityWithoutDrawComponent = worldBuilder.CreateEntity(); + entityWithoutDrawComponent.AddComponent(aComponent); + entityWithoutDrawComponent.AddComponent(bComponent); + + var world = worldBuilder.Build(); + + world.Update(0.01f); + + Console.WriteLine(renderer.IsTracking(entityNotToTrack.id)); + + Assert.IsTrue(renderer.IsTracking(entityToTrack.id)); + Assert.IsFalse(renderer.IsTracking(entityNotToTrack.id)); + Assert.IsFalse(renderer.IsTracking(entityWithoutDrawComponent.id)); + } + } +}