refactor so entity and components use GUID

pull/5/head
Evan Hemsley 2019-06-17 11:33:38 -07:00
parent e4705dc7ea
commit 7c787290eb
6 changed files with 124 additions and 145 deletions

View File

@ -6,133 +6,111 @@ namespace Encompass
{ {
internal class ComponentManager internal class ComponentManager
{ {
private Dictionary<uint, List<IComponent>> entityIDToComponents = new Dictionary<uint, List<IComponent>>(); private Dictionary<Guid, IComponent> IDToComponent = new Dictionary<Guid, IComponent>();
private Dictionary<IComponent, uint> componentToEntityID = new Dictionary<IComponent, uint>(); private Dictionary<Guid, List<Guid>> entityIDToComponentIDs = new Dictionary<Guid, List<Guid>>();
private Dictionary<Guid, Guid> componentIDToEntityID = new Dictionary<Guid, Guid>();
private Dictionary<Type, List<IComponent>> activeComponents = new Dictionary<Type, List<IComponent>>(); private Dictionary<Type, List<Guid>> activeComponents = new Dictionary<Type, List<Guid>>();
private Dictionary<Type, List<IComponent>> inactiveComponents = new Dictionary<Type, List<IComponent>>(); private Dictionary<Type, List<Guid>> inactiveComponents = new Dictionary<Type, List<Guid>>();
private List<IComponent> componentsToActivate = new List<IComponent>(); private List<Guid> componentsToActivate = new List<Guid>();
private List<IComponent> componentsToDeactivate = new List<IComponent>(); private List<Guid> componentsToDeactivate = new List<Guid>();
private List<IComponent> componentsToRemove = new List<IComponent>(); private List<Guid> componentsToRemove = new List<Guid>();
internal void AddComponent<TComponent>(uint entityID, TComponent component) where TComponent : struct, IComponent internal Guid AddComponent<TComponent>(Guid entityID, TComponent component) where TComponent : struct, IComponent
{ {
if (!entityIDToComponents.ContainsKey(entityID)) var componentID = Guid.NewGuid();
IDToComponent[componentID] = component;
if (!entityIDToComponentIDs.ContainsKey(entityID))
{ {
entityIDToComponents.Add(entityID, new List<IComponent>()); entityIDToComponentIDs.Add(entityID, new List<Guid>());
} }
entityIDToComponents[entityID].Add(component); entityIDToComponentIDs[entityID].Add(componentID);
componentToEntityID[component] = entityID; componentIDToEntityID[componentID] = entityID;
if (!activeComponents.ContainsKey(typeof(TComponent))) if (!activeComponents.ContainsKey(typeof(TComponent)))
{ {
activeComponents.Add(typeof(TComponent), new List<IComponent>()); activeComponents.Add(typeof(TComponent), new List<Guid>());
inactiveComponents.Add(typeof(TComponent), new List<IComponent>()); inactiveComponents.Add(typeof(TComponent), new List<Guid>());
} }
MarkForActivation(component); MarkForActivation(componentID);
return componentID;
} }
internal IEnumerable<IComponent> GetComponentsByEntity(uint entityID) internal IEnumerable<KeyValuePair<Guid, IComponent>> GetComponentsByEntity(Guid entityID)
{ {
return entityIDToComponents[entityID]; return entityIDToComponentIDs[entityID].Select((id) => new KeyValuePair<Guid, IComponent>(id, IDToComponent[id]));
} }
internal IEnumerable<TComponent> GetActiveComponentsByType<TComponent>() where TComponent : struct, IComponent internal IEnumerable<KeyValuePair<Guid, TComponent>> GetActiveComponentsByType<TComponent>() where TComponent : struct, IComponent
{ {
return activeComponents[typeof(TComponent)].Cast<TComponent>(); return activeComponents[typeof(TComponent)].Select((id) => new KeyValuePair<Guid, TComponent>(id, (TComponent)IDToComponent[id]));
} }
internal TComponent GetActiveComponentByType<TComponent>() where TComponent : struct, IComponent internal KeyValuePair<Guid, TComponent> GetActiveComponentByType<TComponent>() where TComponent : struct, IComponent
{ {
return GetActiveComponentsByType<TComponent>().Single(); return GetActiveComponentsByType<TComponent>().Single();
} }
internal IEnumerable<TComponent> GetComponentsByEntityAndType<TComponent>(uint entityID) where TComponent : struct, IComponent internal IEnumerable<KeyValuePair<Guid, TComponent>> GetComponentsByEntityAndType<TComponent>(Guid entityID) where TComponent : struct, IComponent
{ {
var entity_components = GetComponentsByEntity(entityID).Cast<TComponent>(); var entity_components = GetComponentsByEntity(entityID).Select((kv) => new KeyValuePair<Guid, TComponent>(kv.Key, (TComponent)kv.Value));
var active_components_by_type = GetActiveComponentsByType<TComponent>(); var active_components_by_type = GetActiveComponentsByType<TComponent>();
return entity_components.Intersect(active_components_by_type).Cast<TComponent>(); return entity_components.Intersect(active_components_by_type);
} }
internal bool EntityHasComponentOfType<TComponent>(uint entityID) where TComponent : struct, IComponent internal bool EntityHasComponentOfType<TComponent>(Guid entityID) where TComponent : struct, IComponent
{ {
return GetComponentsByEntityAndType<TComponent>(entityID).Any(); return GetComponentsByEntityAndType<TComponent>(entityID).Any();
} }
/** Replaces the component with another. */ internal void UpdateComponent<TComponent>(Guid componentID, TComponent newComponentValue) where TComponent : struct, IComponent
internal void UpdateComponent<TComponent>(TComponent originalComponent, TComponent newComponent) where TComponent : struct, IComponent
{ {
var entityID = componentToEntityID[originalComponent]; var entityID = componentIDToEntityID[componentID];
IDToComponent[componentID] = newComponentValue;
entityIDToComponents[entityID].Remove(originalComponent);
entityIDToComponents[entityID].Add(newComponent);
componentToEntityID.Remove(originalComponent);
componentToEntityID.Add(newComponent, entityID);
if (activeComponents[originalComponent.GetType()].Remove(originalComponent))
{
activeComponents[originalComponent.GetType()].Add(newComponent);
} }
if (inactiveComponents[originalComponent.GetType()].Remove(originalComponent)) internal void RemoveAllComponentsFromEntity(Guid entityID)
{ {
inactiveComponents[originalComponent.GetType()].Add(newComponent); var componentIDs = entityIDToComponentIDs[entityID];
foreach (var componentID in componentIDs)
{
var component = IDToComponent[componentID];
activeComponents[component.GetType()].Remove(componentID);
inactiveComponents[component.GetType()].Remove(componentID);
} }
if (componentsToActivate.Remove(originalComponent)) entityIDToComponentIDs.Remove(entityID);
{
componentsToActivate.Add(newComponent);
} }
if (componentsToDeactivate.Remove(originalComponent)) internal void MarkForActivation(Guid componentID)
{ {
componentsToDeactivate.Add(newComponent); componentsToActivate.Add(componentID);
} }
if (componentsToRemove.Remove(originalComponent)) internal void MarkForDeactivation(Guid componentID)
{ {
componentsToRemove.Add(newComponent); componentsToDeactivate.Add(componentID);
}
} }
internal void RemoveAllComponentsFromEntity(uint entityID) internal void MarkForRemoval(Guid componentID)
{ {
var components = GetComponentsByEntity(entityID); componentsToRemove.Add(componentID);
foreach (var component in components)
{
activeComponents[component.GetType()].Remove(component);
inactiveComponents[component.GetType()].Remove(component);
}
entityIDToComponents.Remove(entityID);
}
internal void MarkForActivation(IComponent component)
{
componentsToActivate.Add(component);
}
internal void MarkForDeactivation(IComponent component)
{
componentsToDeactivate.Add(component);
}
internal void MarkForRemoval(IComponent component)
{
componentsToRemove.Add(component);
} }
internal void ActivateComponents() internal void ActivateComponents()
{ {
foreach (var component in componentsToActivate) foreach (var componentID in componentsToActivate)
{ {
activeComponents[component.GetType()].Add(component); var component = IDToComponent[componentID];
inactiveComponents[component.GetType()].Remove(component); activeComponents[component.GetType()].Add(componentID);
inactiveComponents[component.GetType()].Remove(componentID);
} }
componentsToActivate.Clear(); componentsToActivate.Clear();
@ -140,19 +118,23 @@ namespace Encompass
internal void DeactivateComponents() internal void DeactivateComponents()
{ {
foreach (var component in componentsToDeactivate) foreach (var componentID in componentsToDeactivate)
{ {
activeComponents[component.GetType()].Remove(component); var component = IDToComponent[componentID];
inactiveComponents[component.GetType()].Add(component); activeComponents[component.GetType()].Remove(componentID);
inactiveComponents[component.GetType()].Add(componentID);
} }
componentsToDeactivate.Clear();
} }
internal void RemoveComponents() internal void RemoveComponents()
{ {
foreach (var component in componentsToRemove) foreach (var componentID in componentsToRemove)
{ {
activeComponents[component.GetType()].Remove(component); var component = IDToComponent[componentID];
inactiveComponents[component.GetType()].Remove(component); activeComponents[component.GetType()].Remove(componentID);
inactiveComponents[component.GetType()].Remove(componentID);
} }
componentsToRemove.Clear(); componentsToRemove.Clear();

View File

@ -58,21 +58,21 @@ namespace Encompass
return this.entityManager.CreateEntity(); return this.entityManager.CreateEntity();
} }
protected IEnumerable<TComponent> ReadComponents<TComponent>() where TComponent : struct, IComponent protected IEnumerable<KeyValuePair<Guid, TComponent>> ReadComponents<TComponent>() where TComponent : struct, IComponent
{ {
return this.componentManager.GetActiveComponentsByType<TComponent>(); return this.componentManager.GetActiveComponentsByType<TComponent>();
} }
protected TComponent ReadComponent<TComponent>() where TComponent : struct, IComponent protected KeyValuePair<Guid, TComponent> ReadComponent<TComponent>() where TComponent : struct, IComponent
{ {
return this.componentManager.GetActiveComponentByType<TComponent>(); return this.componentManager.GetActiveComponentByType<TComponent>();
} }
internal void UpdateComponentInWorld<TComponent>(TComponent originalComponent, TComponent newComponent) where TComponent : struct, IComponent internal void UpdateComponentInWorld<TComponent>(Guid componentID, TComponent newComponent) where TComponent : struct, IComponent
{ {
if (mutateComponentTypes.Contains(typeof(TComponent))) if (mutateComponentTypes.Contains(typeof(TComponent)))
{ {
this.componentManager.UpdateComponent(originalComponent, newComponent); this.componentManager.UpdateComponent(componentID, newComponent);
} }
else else
{ {
@ -80,18 +80,9 @@ namespace Encompass
} }
} }
protected void UpdateComponent<TComponent>(TComponent component, Func<TComponent, TComponent> updateFunction) where TComponent : struct, IComponent protected void UpdateComponent<TComponent>(Guid componentID, TComponent newComponentValue) where TComponent : struct, IComponent
{ {
var updatedComponent = updateFunction(component); this.UpdateComponentInWorld(componentID, newComponentValue);
this.UpdateComponentInWorld(component, updatedComponent);
}
protected void UpdateComponents<TComponent>(IEnumerable<TComponent> components, Func<TComponent, TComponent> updateFunction) where TComponent : struct, IComponent
{
foreach (var component in components)
{
this.UpdateComponent(component, updateFunction);
}
} }
protected void EmitMessage<TMessage>(TMessage message) where TMessage : struct, IMessage protected void EmitMessage<TMessage>(TMessage message) where TMessage : struct, IMessage

View File

@ -1,31 +1,32 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Encompass namespace Encompass
{ {
public struct Entity public struct Entity
{ {
public readonly uint id; public readonly Guid id;
private ComponentManager componentManager; private ComponentManager componentManager;
internal Entity(uint id, ComponentManager componentManager) internal Entity(Guid id, ComponentManager componentManager)
{ {
this.id = id; this.id = id;
this.componentManager = componentManager; this.componentManager = componentManager;
} }
public void AddComponent<TComponent>(TComponent component) where TComponent : struct, IComponent public Guid AddComponent<TComponent>(TComponent component) where TComponent : struct, IComponent
{ {
componentManager.AddComponent<TComponent>(id, component); return componentManager.AddComponent<TComponent>(id, component);
} }
public IEnumerable<TComponent> GetComponents<TComponent>() where TComponent : struct, IComponent public IEnumerable<KeyValuePair<Guid, TComponent>> GetComponents<TComponent>() where TComponent : struct, IComponent
{ {
return componentManager.GetComponentsByEntityAndType<TComponent>(id); return componentManager.GetComponentsByEntityAndType<TComponent>(id);
} }
public TComponent GetComponent<TComponent>() where TComponent : struct, IComponent public KeyValuePair<Guid, TComponent> GetComponent<TComponent>() where TComponent : struct, IComponent
{ {
return GetComponents<TComponent>().First(); return GetComponents<TComponent>().First();
} }

View File

@ -1,13 +1,12 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Encompass namespace Encompass
{ {
internal class EntityManager internal class EntityManager
{ {
private uint nextID = 1;
private List<Entity> entities = new List<Entity>(); private List<Entity> entities = new List<Entity>();
private Dictionary<uint, Entity> IDToEntity = new Dictionary<uint, Entity>(); private Dictionary<Guid, Entity> IDToEntity = new Dictionary<Guid, Entity>();
private List<Entity> entitiesMarkedForDestroy = new List<Entity>(); private List<Entity> entitiesMarkedForDestroy = new List<Entity>();
@ -25,7 +24,7 @@ namespace Encompass
return new Entity(NextID(), componentManager); return new Entity(NextID(), componentManager);
} }
public Entity GetEntity(uint id) public Entity GetEntity(Guid id)
{ {
return this.IDToEntity[id]; return this.IDToEntity[id];
} }
@ -43,11 +42,9 @@ namespace Encompass
} }
} }
private uint NextID() private Guid NextID()
{ {
var id = this.nextID; return Guid.NewGuid();
this.nextID++;
return id;
} }
} }
} }

View File

@ -1,15 +1,17 @@
using NUnit.Framework; using NUnit.Framework;
using System.Linq; using FluentAssertions;
using Encompass; using Encompass;
using System.Collections.Generic;
using System; using System;
using System.Linq;
using System.Collections.Generic;
namespace Tests namespace Tests
{ {
public class EngineTest public class EngineTest
{ {
static IEnumerable<MockComponent> resultComponents; static List<KeyValuePair<Guid, MockComponent>> resultComponents;
static MockComponent resultComponent; static MockComponent resultComponent;
static List<MockMessage> resultMessages; static List<MockMessage> resultMessages;
@ -18,7 +20,7 @@ namespace Tests
{ {
public override void Update(float dt) public override void Update(float dt)
{ {
resultComponents = this.ReadComponents<MockComponent>(); resultComponents = this.ReadComponents<MockComponent>().ToList();
} }
} }
@ -26,7 +28,7 @@ namespace Tests
{ {
public override void Update(float dt) public override void Update(float dt)
{ {
resultComponent = this.ReadComponent<MockComponent>(); resultComponent = this.ReadComponent<MockComponent>().Value;
} }
} }
@ -46,15 +48,16 @@ namespace Tests
mockComponentB.myInt = 1; mockComponentB.myInt = 1;
mockComponentB.myString = "howdy"; mockComponentB.myString = "howdy";
entity.AddComponent(mockComponent); var componentAID = entity.AddComponent(mockComponent);
entity.AddComponent(mockComponentB); var componentBID = entity.AddComponent(mockComponentB);
var world = worldBuilder.Build(); var world = worldBuilder.Build();
world.Update(0.01f); world.Update(0.01f);
Assert.Contains(mockComponent, resultComponents.ToList()); var resultComponentValues = resultComponents.Select((kv) => kv.Value);
Assert.Contains(mockComponentB, resultComponents.ToList()); resultComponentValues.Should().Contain(mockComponent);
resultComponentValues.Should().Contain(mockComponentB);
} }
[Test] [Test]
@ -107,14 +110,13 @@ namespace Tests
{ {
public override void Update(float dt) public override void Update(float dt)
{ {
var component = this.ReadComponent<MockComponent>(); (var componentID, var component) = this.ReadComponent<MockComponent>();
this.UpdateComponent(component, (MockComponent comp) =>
{ component.myInt = 420;
comp.myInt = 420; component.myString = "blaze it";
comp.myString = "blaze it"; this.UpdateComponent(componentID, component);
return comp;
}); resultComponent = this.ReadComponent<MockComponent>().Value;
resultComponent = this.ReadComponent<MockComponent>();
} }
} }
@ -144,14 +146,13 @@ namespace Tests
{ {
public override void Update(float dt) public override void Update(float dt)
{ {
var component = this.ReadComponent<MockComponent>(); (var componentID, var component) = this.ReadComponent<MockComponent>();
this.UpdateComponent(component, (MockComponent comp) =>
{ component.myInt = 420;
comp.myInt = 420; component.myString = "blaze it";
comp.myString = "blaze it"; this.UpdateComponent(componentID, component);
return comp;
}); component = this.ReadComponent<MockComponent>().Value;
component = this.ReadComponent<MockComponent>();
} }
} }

View File

@ -1,10 +1,16 @@
using NUnit.Framework; using NUnit.Framework;
using FluentAssertions;
using System.Linq; using System.Linq;
using Encompass; using Encompass;
using System.Collections.Generic;
using System;
namespace Tests { namespace Tests
struct MockComponent : IComponent { {
struct MockComponent : IComponent
{
public string myString; public string myString;
public int myInt; public int myInt;
} }
@ -27,7 +33,7 @@ namespace Tests {
// world.Update(); // world.Update();
Assert.IsTrue(entity.HasComponent<MockComponent>()); Assert.IsTrue(entity.HasComponent<MockComponent>());
Assert.AreEqual(mockComponent, entity.GetComponent<MockComponent>()); Assert.That(entity.GetComponent<MockComponent>().Value, Is.EqualTo(mockComponent));
} }
[Test] [Test]
@ -48,15 +54,16 @@ namespace Tests {
mockComponentC.myInt = 1; mockComponentC.myInt = 1;
mockComponentC.myString = "howdy"; mockComponentC.myString = "howdy";
entity.AddComponent<MockComponent>(mockComponentA); var componentAID = entity.AddComponent<MockComponent>(mockComponentA);
entity.AddComponent<MockComponent>(mockComponentB); var componentBID = entity.AddComponent<MockComponent>(mockComponentB);
entity.AddComponent<MockComponent>(mockComponentC); var componentCID = entity.AddComponent<MockComponent>(mockComponentC);
var world = worldBuilder.Build(); var world = worldBuilder.Build();
Assert.Contains(mockComponentA, entity.GetComponents<MockComponent>().ToList()); var components = entity.GetComponents<MockComponent>();
Assert.Contains(mockComponentB, entity.GetComponents<MockComponent>().ToList()); components.Should().Contain(new KeyValuePair<Guid, MockComponent>(componentAID, mockComponentA));
Assert.Contains(mockComponentC, entity.GetComponents<MockComponent>().ToList()); components.Should().Contain(new KeyValuePair<Guid, MockComponent>(componentBID, mockComponentB));
components.Should().Contain(new KeyValuePair<Guid, MockComponent>(componentCID, mockComponentC));
} }
[Test] [Test]
@ -69,11 +76,11 @@ namespace Tests {
mockComponent.myInt = 3; mockComponent.myInt = 3;
mockComponent.myString = "hello"; mockComponent.myString = "hello";
entity.AddComponent<MockComponent>(mockComponent); var componentID = entity.AddComponent<MockComponent>(mockComponent);
var world = worldBuilder.Build(); var world = worldBuilder.Build();
Assert.AreEqual(mockComponent, entity.GetComponent<MockComponent>()); Assert.AreEqual(new KeyValuePair<Guid, MockComponent>(componentID, mockComponent), entity.GetComponent<MockComponent>());
} }
[Test] [Test]