thread safe data structures

pull/5/head
Evan Hemsley 2019-07-28 19:54:15 -07:00
parent f3a7331ae9
commit 6aca668619
5 changed files with 62 additions and 57 deletions

1
TODO
View File

@ -4,3 +4,4 @@
- emit two packages: one for dev, which includes expensive runtime validation checks, and one for release, which disables them
- thread safety
- create ConcurrentHashSet based on ConcurrentDictionary

View File

@ -1,38 +1,40 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Encompass.Exceptions;
// we use byte as a dummy argument to ConcurrentDictionary so we can treat it like a HashSet
namespace Encompass
{
internal class ComponentManager
{
private readonly DrawLayerManager drawLayerManager;
private readonly Dictionary<Guid, Type> componentIDToType = new Dictionary<Guid, Type>();
private readonly Dictionary<Guid, IComponent> IDToComponent = new Dictionary<Guid, IComponent>();
private readonly Dictionary<Guid, HashSet<Guid>> entityIDToComponentIDs = new Dictionary<Guid, HashSet<Guid>>();
private readonly Dictionary<Guid, Guid> componentIDToEntityID = new Dictionary<Guid, Guid>();
private readonly ConcurrentDictionary<Guid, Type> componentIDToType = new ConcurrentDictionary<Guid, Type>();
private readonly ConcurrentDictionary<Guid, IComponent> IDToComponent = new ConcurrentDictionary<Guid, IComponent>();
private readonly ConcurrentDictionary<Guid, ConcurrentDictionary<Guid, byte>> entityIDToComponentIDs = new ConcurrentDictionary<Guid, ConcurrentDictionary<Guid, byte>>();
private readonly ConcurrentDictionary<Guid, Guid> componentIDToEntityID = new ConcurrentDictionary<Guid, Guid>();
private readonly Dictionary<Type, HashSet<Guid>> typeToComponentIDs = new Dictionary<Type, HashSet<Guid>>();
private readonly ConcurrentDictionary<Type, ConcurrentDictionary<Guid, byte>> typeToComponentIDs = new ConcurrentDictionary<Type, ConcurrentDictionary<Guid, byte>>();
private readonly List<Guid> activeComponents = new List<Guid>();
private readonly List<Guid> inactiveComponents = new List<Guid>();
private readonly ConcurrentDictionary<Guid, byte> activeComponents = new ConcurrentDictionary<Guid, byte>();
private readonly ConcurrentDictionary<Guid, byte> inactiveComponents = new ConcurrentDictionary<Guid, byte>();
private readonly HashSet<Guid> componentsMarkedForActivation = new HashSet<Guid>();
private readonly HashSet<Guid> componentsMarkedForDeactivation = new HashSet<Guid>();
private readonly HashSet<Guid> componentsMarkedForRemoval = new HashSet<Guid>();
private readonly ConcurrentDictionary<Guid, byte> componentsMarkedForDeactivation = new ConcurrentDictionary<Guid, byte>();
private readonly ConcurrentDictionary<Guid, byte> componentsMarkedForRemoval = new ConcurrentDictionary<Guid, byte>();
private readonly Dictionary<Guid, IComponent> pendingUpdates = new Dictionary<Guid, IComponent>();
private readonly ConcurrentDictionary<Guid, IComponent> pendingUpdates = new ConcurrentDictionary<Guid, IComponent>();
//shared references with EntityManager
private readonly HashSet<Guid> entitiesWithAddedComponents;
private readonly HashSet<Guid> entitiesWithRemovedComponents;
private readonly ConcurrentDictionary<Guid, byte> entitiesWithAddedComponents;
private readonly ConcurrentDictionary<Guid, byte> entitiesWithRemovedComponents;
public ComponentManager(
DrawLayerManager drawLayerManager,
HashSet<Guid> entitiesWithAddedComponents,
HashSet<Guid> entitiesWithRemovedComponents
ConcurrentDictionary<Guid, byte> entitiesWithAddedComponents,
ConcurrentDictionary<Guid, byte> entitiesWithRemovedComponents
)
{
this.drawLayerManager = drawLayerManager;
@ -42,7 +44,7 @@ namespace Encompass
internal void RegisterEntity(Guid entityID)
{
entityIDToComponentIDs.Add(entityID, new HashSet<Guid>());
entityIDToComponentIDs.TryAdd(entityID, new ConcurrentDictionary<Guid, byte>());
}
internal Guid NextID()
@ -57,17 +59,17 @@ namespace Encompass
if (!typeToComponentIDs.ContainsKey(typeof(TComponent)))
{
typeToComponentIDs.Add(typeof(TComponent), new HashSet<Guid>());
typeToComponentIDs.TryAdd(typeof(TComponent), new ConcurrentDictionary<Guid, byte>());
}
typeToComponentIDs[typeof(TComponent)].Add(componentID);
typeToComponentIDs[typeof(TComponent)].TryAdd(componentID, 0);
entityIDToComponentIDs[entity.ID].Add(componentID);
entityIDToComponentIDs[entity.ID].TryAdd(componentID, 0);
componentIDToEntityID[componentID] = entity.ID;
activeComponents.Add(componentID);
activeComponents.TryAdd(componentID, 0);
entitiesWithAddedComponents.Add(entity.ID);
entitiesWithAddedComponents.TryAdd(entity.ID, 0);
return componentID;
}
@ -82,26 +84,26 @@ namespace Encompass
internal IEnumerable<Guid> GetComponentIDsByEntityID(Guid entityID)
{
return entityIDToComponentIDs.ContainsKey(entityID) ?
entityIDToComponentIDs[entityID] :
entityIDToComponentIDs[entityID].Keys :
Enumerable.Empty<Guid>();
}
internal IEnumerable<ValueTuple<Guid, IComponent>> GetComponentsByEntity(Guid entityID)
{
return GetComponentIDsByEntityID(entityID).Intersect(activeComponents).Select((id) => new ValueTuple<Guid, IComponent>(id, IDToComponent[id]));
return GetComponentIDsByEntityID(entityID).Intersect(activeComponents.Keys).Select((id) => new ValueTuple<Guid, IComponent>(id, IDToComponent[id]));
}
internal IEnumerable<ValueTuple<Guid, Guid, TComponent>> GetActiveComponentsByType<TComponent>() where TComponent : struct, IComponent
{
return typeToComponentIDs.ContainsKey(typeof(TComponent)) ?
typeToComponentIDs[typeof(TComponent)].Intersect(activeComponents).Select((id) => new ValueTuple<Guid, Guid, TComponent>(GetEntityIDByComponentID(id), id, (TComponent)IDToComponent[id])) :
typeToComponentIDs[typeof(TComponent)].Keys.Intersect(activeComponents.Keys).Select((id) => new ValueTuple<Guid, Guid, TComponent>(GetEntityIDByComponentID(id), id, (TComponent)IDToComponent[id])) :
Enumerable.Empty<ValueTuple<Guid, Guid, TComponent>>();
}
internal IEnumerable<ValueTuple<Guid, IComponent>> GetActiveComponentsByType(Type type)
{
return typeToComponentIDs.ContainsKey(type) ?
typeToComponentIDs[type].Intersect(activeComponents).Select((id) => new ValueTuple<Guid, IComponent>(id, IDToComponent[id])) :
typeToComponentIDs[type].Keys.Intersect(activeComponents.Keys).Select((id) => new ValueTuple<Guid, IComponent>(id, IDToComponent[id])) :
Enumerable.Empty<ValueTuple<Guid, IComponent>>();
}
@ -156,7 +158,7 @@ namespace Encompass
throw new RepeatUpdateComponentException("Component {0} with ID {1} was updated multiple times this frame", typeof(TComponent).Name, componentID);
}
pendingUpdates.Add(componentID, newComponentValue);
pendingUpdates.TryAdd(componentID, newComponentValue);
}
internal void PerformComponentUpdates()
@ -179,23 +181,23 @@ namespace Encompass
internal void Activate(Guid componentID)
{
if (inactiveComponents.Remove(componentID))
if (inactiveComponents.TryRemove(componentID, out _))
{
activeComponents.Add(componentID);
activeComponents.TryAdd(componentID, 0);
}
var entityID = GetEntityIDByComponentID(componentID);
entitiesWithAddedComponents.Add(entityID);
entitiesWithAddedComponents.TryAdd(entityID, 0);
}
internal void MarkForDeactivation(Guid componentID)
{
componentsMarkedForDeactivation.Add(componentID);
componentsMarkedForDeactivation.TryAdd(componentID, 0);
}
internal void DeactivateMarkedComponents()
{
foreach (var componentID in componentsMarkedForDeactivation)
foreach (var componentID in componentsMarkedForDeactivation.Keys)
{
Deactivate(componentID);
}
@ -205,23 +207,23 @@ namespace Encompass
private void Deactivate(Guid componentID)
{
if (activeComponents.Remove(componentID))
if (activeComponents.TryRemove(componentID, out _))
{
inactiveComponents.Add(componentID);
inactiveComponents.TryAdd(componentID, 0);
}
var entityID = GetEntityIDByComponentID(componentID);
entitiesWithRemovedComponents.Add(entityID);
entitiesWithRemovedComponents.TryAdd(entityID, 0);
}
internal void MarkForRemoval(Guid componentID)
{
componentsMarkedForRemoval.Add(componentID);
componentsMarkedForRemoval.TryAdd(componentID, 0);
}
internal void RemoveMarkedComponents()
{
foreach (var componentID in componentsMarkedForRemoval)
foreach (var componentID in componentsMarkedForRemoval.Keys)
{
Remove(componentID);
}
@ -234,28 +236,28 @@ namespace Encompass
var component = IDToComponent[componentID];
var type = componentIDToType[componentID];
activeComponents.Remove(componentID);
inactiveComponents.Remove(componentID);
activeComponents.TryRemove(componentID, out _);
inactiveComponents.TryRemove(componentID, out _);
var entityID = componentIDToEntityID[componentID];
if (entityIDToComponentIDs.ContainsKey(entityID))
{
entityIDToComponentIDs[entityID].Remove(componentID);
entityIDToComponentIDs[entityID].TryRemove(componentID, out _);
}
IDToComponent.Remove(componentID);
componentIDToType.Remove(componentID);
componentIDToEntityID.Remove(componentID);
typeToComponentIDs[type].Remove(componentID);
IDToComponent.TryRemove(componentID, out _);
componentIDToType.TryRemove(componentID, out _);
componentIDToEntityID.TryRemove(componentID, out _);
typeToComponentIDs[type].TryRemove(componentID, out _);
drawLayerManager.UnRegisterComponentWithLayer(componentID);
entitiesWithRemovedComponents.Add(entityID);
entitiesWithRemovedComponents.TryAdd(entityID, 0);
}
public void RegisterDestroyedEntity(Guid entityID)
{
entityIDToComponentIDs.Remove(entityID);
entityIDToComponentIDs.TryRemove(entityID, out _);
}
}
}

View File

@ -1,3 +1,4 @@
using System.Collections.Concurrent;
using System;
using System.Collections.Generic;
@ -5,22 +6,22 @@ namespace Encompass
{
internal class EntityManager
{
private readonly Dictionary<Guid, Entity> IDToEntity = new Dictionary<Guid, Entity>();
private readonly ConcurrentDictionary<Guid, Entity> IDToEntity = new ConcurrentDictionary<Guid, Entity>();
private readonly HashSet<Guid> entitiesMarkedForDestroy = new HashSet<Guid>();
private readonly Dictionary<Type, HashSet<IEntityTracker>> componentTypeToEntityTrackers = new Dictionary<Type, HashSet<IEntityTracker>>();
private readonly Dictionary<Guid, HashSet<IEntityTracker>> entityToEntityTrackers = new Dictionary<Guid, HashSet<IEntityTracker>>();
private readonly HashSet<Guid> entitiesWithAddedComponents;
private readonly HashSet<Guid> entitiesWithRemovedComponents;
private readonly ConcurrentDictionary<Guid, byte> entitiesWithAddedComponents;
private readonly ConcurrentDictionary<Guid, byte> entitiesWithRemovedComponents;
private readonly ComponentManager componentManager;
public EntityManager(
ComponentManager componentManager,
HashSet<Guid> entitiesWithAddedComponents,
HashSet<Guid> entitiesWithRemovedComponents
ConcurrentDictionary<Guid, byte> entitiesWithAddedComponents,
ConcurrentDictionary<Guid, byte> entitiesWithRemovedComponents
)
{
this.componentManager = componentManager;
@ -57,7 +58,7 @@ namespace Encompass
foreach (var entityID in entitiesMarkedForDestroy)
{
componentManager.MarkAllComponentsOnEntityForRemoval(entityID);
IDToEntity.Remove(entityID);
IDToEntity.TryRemove(entityID, out _);
entityToEntityTrackers.Remove(entityID);
componentManager.RegisterDestroyedEntity(entityID);
}
@ -96,17 +97,17 @@ namespace Encompass
public void RegisterDirtyEntityWithAddedComponents(Guid entityID)
{
entitiesWithAddedComponents.Add(entityID);
entitiesWithAddedComponents.TryAdd(entityID, 0);
}
public void RegisterDirtyEntityWithRemovedComponents(Guid entityID)
{
entitiesWithRemovedComponents.Add(entityID);
entitiesWithRemovedComponents.TryAdd(entityID, 0);
}
public void CheckEntitiesWithAddedComponents()
{
foreach (var entityID in entitiesWithAddedComponents)
foreach (var entityID in entitiesWithAddedComponents.Keys)
{
CheckAndRegisterEntity(entityID);
}
@ -116,7 +117,7 @@ namespace Encompass
public void CheckEntitiesWithRemovedComponents()
{
foreach (var entityID in entitiesWithRemovedComponents)
foreach (var entityID in entitiesWithRemovedComponents.Keys)
{
if (entityToEntityTrackers.ContainsKey(entityID))
{

View File

@ -1,3 +1,4 @@
using System.Collections.Concurrent;
using System;
using System.Collections.Generic;
using System.Reflection;
@ -32,8 +33,8 @@ namespace Encompass
public WorldBuilder()
{
var entitiesWithAddedComponents = new HashSet<Guid>();
var entitiesWithRemovedComponents = new HashSet<Guid>();
var entitiesWithAddedComponents = new ConcurrentDictionary<Guid, byte>();
var entitiesWithRemovedComponents = new ConcurrentDictionary<Guid, byte>();
drawLayerManager = new DrawLayerManager();
componentManager = new ComponentManager(drawLayerManager, entitiesWithAddedComponents, entitiesWithRemovedComponents);
entityManager = new EntityManager(componentManager, entitiesWithAddedComponents, entitiesWithRemovedComponents);