summaryrefslogtreecommitdiff
path: root/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities
diff options
context:
space:
mode:
authorchai <215380520@qq.com>2024-06-03 10:15:45 +0800
committerchai <215380520@qq.com>2024-06-03 10:15:45 +0800
commitacea7b2e728787a0d83bbf83c8c1f042d2c32e7e (patch)
tree0bfec05c1ca2d71be2c337bcd110a0421f19318b /Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities
parent88febcb02bf127d961c6471d9e846c0e1315f5c3 (diff)
+ plugins project
Diffstat (limited to 'Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities')
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Aspect.cs48
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/AspectBuilder.cs63
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/BitArrayExtensions.cs54
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/ComponentManager.cs90
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/ComponentMapper.cs69
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/ComponentType.cs46
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Entity.cs87
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/EntityManager.cs120
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/EntitySubscription.cs61
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/MonoGame.Extended.Entities.csproj12
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/DrawSystem.cs16
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/EntityDrawSystem.cs14
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/EntityProcessingSystem.cs26
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/EntitySystem.cs51
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/EntityUpdateSystem.cs14
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/ISystem.cs9
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/UpdateSystem.cs16
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/World.cs84
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/WorldBuilder.cs26
19 files changed, 906 insertions, 0 deletions
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Aspect.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Aspect.cs
new file mode 100644
index 0000000..e6fb606
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Aspect.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Specialized;
+
+namespace MonoGame.Extended.Entities
+{
+ public class Aspect
+ {
+ internal Aspect()
+ {
+ AllSet = new BitVector32();
+ ExclusionSet = new BitVector32();
+ OneSet = new BitVector32();
+ }
+
+ public BitVector32 AllSet;
+ public BitVector32 ExclusionSet;
+ public BitVector32 OneSet;
+
+ public static AspectBuilder All(params Type[] types)
+ {
+ return new AspectBuilder().All(types);
+ }
+
+ public static AspectBuilder One(params Type[] types)
+ {
+ return new AspectBuilder().One(types);
+ }
+
+ public static AspectBuilder Exclude(params Type[] types)
+ {
+ return new AspectBuilder().Exclude(types);
+ }
+
+ public bool IsInterested(BitVector32 componentBits)
+ {
+ if (AllSet.Data != 0 && (componentBits.Data & AllSet.Data) != AllSet.Data)
+ return false;
+
+ if (ExclusionSet.Data != 0 && (componentBits.Data & ExclusionSet.Data) != 0)
+ return false;
+
+ if (OneSet.Data != 0 && (componentBits.Data & OneSet.Data) == 0)
+ return false;
+
+ return true;
+ }
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/AspectBuilder.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/AspectBuilder.cs
new file mode 100644
index 0000000..d749795
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/AspectBuilder.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Specialized;
+using MonoGame.Extended.Collections;
+
+namespace MonoGame.Extended.Entities
+{
+ public class AspectBuilder
+ {
+ public AspectBuilder()
+ {
+ AllTypes = new Bag<Type>();
+ ExclusionTypes = new Bag<Type>();
+ OneTypes = new Bag<Type>();
+ }
+
+ public Bag<Type> AllTypes { get; }
+ public Bag<Type> ExclusionTypes { get; }
+ public Bag<Type> OneTypes { get; }
+
+ public AspectBuilder All(params Type[] types)
+ {
+ foreach (var type in types)
+ AllTypes.Add(type);
+
+ return this;
+ }
+
+ public AspectBuilder One(params Type[] types)
+ {
+ foreach (var type in types)
+ OneTypes.Add(type);
+
+ return this;
+ }
+
+ public AspectBuilder Exclude(params Type[] types)
+ {
+ foreach (var type in types)
+ ExclusionTypes.Add(type);
+
+ return this;
+ }
+
+ public Aspect Build(ComponentManager componentManager)
+ {
+ var aspect = new Aspect();
+ Associate(componentManager, AllTypes, ref aspect.AllSet);
+ Associate(componentManager, OneTypes, ref aspect.OneSet);
+ Associate(componentManager, ExclusionTypes, ref aspect.ExclusionSet);
+ return aspect;
+ }
+
+ // ReSharper disable once ParameterTypeCanBeEnumerable.Local
+ private static void Associate(ComponentManager componentManager, Bag<Type> types, ref BitVector32 bits)
+ {
+ foreach (var type in types)
+ {
+ var id = componentManager.GetComponentTypeId(type);
+ bits[1 << id] = true;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/BitArrayExtensions.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/BitArrayExtensions.cs
new file mode 100644
index 0000000..2ca0737
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/BitArrayExtensions.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections;
+
+namespace MonoGame.Extended.Entities
+{
+ public static class BitArrayExtensions
+ {
+ public static bool IsEmpty(this BitArray bitArray)
+ {
+ for (var i = 0; i < bitArray.Length; i++)
+ {
+ if (bitArray[i])
+ return false;
+ }
+
+ return true;
+ }
+
+ public static bool ContainsAll(this BitArray bitArray, BitArray other)
+ {
+ var otherBitsLength = other.Length;
+ var bitsLength = bitArray.Length;
+
+ for (var i = bitsLength; i < otherBitsLength; i++)
+ {
+ if (other[i])
+ return false;
+ }
+
+ var s = Math.Min(bitsLength, otherBitsLength);
+
+ for (var i = 0; s > i; i++)
+ {
+ if ((bitArray[i] & other[i]) != other[i])
+ return false;
+ }
+
+ return true;
+ }
+
+ public static bool Intersects(this BitArray bitArray, BitArray other)
+ {
+ var s = Math.Min(bitArray.Length, other.Length);
+
+ for (var i = 0; s > i; i++)
+ {
+ if (bitArray[i] & other[i])
+ return true;
+ }
+
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/ComponentManager.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/ComponentManager.cs
new file mode 100644
index 0000000..a59c87c
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/ComponentManager.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using Microsoft.Xna.Framework;
+using MonoGame.Extended.Collections;
+using MonoGame.Extended.Entities.Systems;
+
+namespace MonoGame.Extended.Entities
+{
+ public interface IComponentMapperService
+ {
+ ComponentMapper<T> GetMapper<T>() where T : class;
+ }
+
+ public class ComponentManager : UpdateSystem, IComponentMapperService
+ {
+ public ComponentManager()
+ {
+ _componentMappers = new Bag<ComponentMapper>();
+ _componentTypes = new Dictionary<Type, int>();
+ }
+
+ private readonly Bag<ComponentMapper> _componentMappers;
+ private readonly Dictionary<Type, int> _componentTypes;
+
+ public Action<int> ComponentsChanged;
+
+ private ComponentMapper<T> CreateMapperForType<T>(int componentTypeId)
+ where T : class
+ {
+ // TODO: We can probably do better than this without a huge performance penalty by creating our own bit vector that grows after the first 32 bits.
+ if (componentTypeId >= 32)
+ throw new InvalidOperationException("Component type limit exceeded. We currently only allow 32 component types for performance reasons.");
+
+ var mapper = new ComponentMapper<T>(componentTypeId, ComponentsChanged);
+ _componentMappers[componentTypeId] = mapper;
+ return mapper;
+ }
+
+ public ComponentMapper GetMapper(int componentTypeId)
+ {
+ return _componentMappers[componentTypeId];
+ }
+
+ public ComponentMapper<T> GetMapper<T>()
+ where T : class
+ {
+ var componentTypeId = GetComponentTypeId(typeof(T));
+
+ if (_componentMappers[componentTypeId] != null)
+ return _componentMappers[componentTypeId] as ComponentMapper<T>;
+
+ return CreateMapperForType<T>(componentTypeId);
+ }
+
+ public int GetComponentTypeId(Type type)
+ {
+ if (_componentTypes.TryGetValue(type, out var id))
+ return id;
+
+ id = _componentTypes.Count;
+ _componentTypes.Add(type, id);
+ return id;
+ }
+
+ public BitVector32 CreateComponentBits(int entityId)
+ {
+ var componentBits = new BitVector32();
+ var mask = BitVector32.CreateMask();
+
+ for (var componentId = 0; componentId < _componentMappers.Count; componentId++)
+ {
+ componentBits[mask] = _componentMappers[componentId]?.Has(entityId) ?? false;
+ mask = BitVector32.CreateMask(mask);
+ }
+
+ return componentBits;
+ }
+
+ public void Destroy(int entityId)
+ {
+ foreach (var componentMapper in _componentMappers)
+ componentMapper?.Delete(entityId);
+ }
+
+ public override void Update(GameTime gameTime)
+ {
+ }
+ }
+}
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/ComponentMapper.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/ComponentMapper.cs
new file mode 100644
index 0000000..35b196c
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/ComponentMapper.cs
@@ -0,0 +1,69 @@
+using System;
+using MonoGame.Extended.Collections;
+
+namespace MonoGame.Extended.Entities
+{
+ public abstract class ComponentMapper
+ {
+ protected ComponentMapper(int id, Type componentType)
+ {
+ Id = id;
+ ComponentType = componentType;
+ }
+
+ public int Id { get; }
+ public Type ComponentType { get; }
+ public abstract bool Has(int entityId);
+ public abstract void Delete(int entityId);
+ }
+
+ public class ComponentMapper<T> : ComponentMapper
+ where T : class
+ {
+ public event Action<int> OnPut;
+ public event Action<int> OnDelete;
+
+ private readonly Action<int> _onCompositionChanged;
+
+ public ComponentMapper(int id, Action<int> onCompositionChanged)
+ : base(id, typeof(T))
+ {
+ _onCompositionChanged = onCompositionChanged;
+ Components = new Bag<T>();
+ }
+
+ public Bag<T> Components { get; }
+
+ public void Put(int entityId, T component)
+ {
+ Components[entityId] = component;
+ _onCompositionChanged(entityId);
+ OnPut?.Invoke(entityId);
+ }
+
+ public T Get(Entity entity)
+ {
+ return Get(entity.Id);
+ }
+
+ public T Get(int entityId)
+ {
+ return Components[entityId];
+ }
+
+ public override bool Has(int entityId)
+ {
+ if (entityId >= Components.Count)
+ return false;
+
+ return Components[entityId] != null;
+ }
+
+ public override void Delete(int entityId)
+ {
+ Components[entityId] = null;
+ _onCompositionChanged(entityId);
+ OnDelete?.Invoke(entityId);
+ }
+ }
+}
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/ComponentType.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/ComponentType.cs
new file mode 100644
index 0000000..ebef996
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/ComponentType.cs
@@ -0,0 +1,46 @@
+using System;
+
+namespace MonoGame.Extended.Entities
+{
+ //public class ComponentType : IEquatable<ComponentType>
+ //{
+ // public ComponentType(Type type, int id)
+ // {
+ // Type = type;
+ // Id = id;
+ // }
+
+ // public Type Type { get; }
+ // public int Id { get; }
+
+ // public bool Equals(ComponentType other)
+ // {
+ // if (ReferenceEquals(null, other)) return false;
+ // if (ReferenceEquals(this, other)) return true;
+ // return Id == other.Id;
+ // }
+
+ // public override bool Equals(object obj)
+ // {
+ // if (ReferenceEquals(null, obj)) return false;
+ // if (ReferenceEquals(this, obj)) return true;
+ // if (obj.GetType() != GetType()) return false;
+ // return Equals((ComponentType) obj);
+ // }
+
+ // public override int GetHashCode()
+ // {
+ // return Id;
+ // }
+
+ // public static bool operator ==(ComponentType left, ComponentType right)
+ // {
+ // return Equals(left, right);
+ // }
+
+ // public static bool operator !=(ComponentType left, ComponentType right)
+ // {
+ // return !Equals(left, right);
+ // }
+ //}
+}
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Entity.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Entity.cs
new file mode 100644
index 0000000..a56ccff
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Entity.cs
@@ -0,0 +1,87 @@
+using System;
+using System.Collections.Specialized;
+
+namespace MonoGame.Extended.Entities
+{
+ public class Entity : IEquatable<Entity>
+ {
+ private readonly EntityManager _entityManager;
+ private readonly ComponentManager _componentManager;
+
+ internal Entity(int id, EntityManager entityManager, ComponentManager componentManager)
+ {
+ Id = id;
+
+ _entityManager = entityManager;
+ _componentManager = componentManager;
+ }
+
+ public int Id { get; }
+
+ public BitVector32 ComponentBits => _entityManager.GetComponentBits(Id);
+
+ public void Attach<T>(T component)
+ where T : class
+ {
+ var mapper = _componentManager.GetMapper<T>();
+ mapper.Put(Id, component);
+ }
+
+ public void Detach<T>()
+ where T : class
+ {
+ var mapper = _componentManager.GetMapper<T>();
+ mapper.Delete(Id);
+ }
+
+ public T Get<T>()
+ where T : class
+ {
+ var mapper = _componentManager.GetMapper<T>();
+ return mapper.Get(Id);
+ }
+
+
+ public bool Has<T>()
+ where T : class
+ {
+ return _componentManager.GetMapper<T>().Has(Id);
+ }
+
+ public void Destroy()
+ {
+ _entityManager.Destroy(Id);
+ }
+
+ public bool Equals(Entity other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return Id == other.Id;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != GetType()) return false;
+ return Equals((Entity) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ // ReSharper disable once NonReadonlyMemberInGetHashCode
+ return Id;
+ }
+
+ public static bool operator ==(Entity left, Entity right)
+ {
+ return Equals(left, right);
+ }
+
+ public static bool operator !=(Entity left, Entity right)
+ {
+ return !Equals(left, right);
+ }
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/EntityManager.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/EntityManager.cs
new file mode 100644
index 0000000..5e1d28a
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/EntityManager.cs
@@ -0,0 +1,120 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Diagnostics;
+using System.Linq;
+using Microsoft.Xna.Framework;
+using MonoGame.Extended.Collections;
+using MonoGame.Extended.Entities.Systems;
+
+namespace MonoGame.Extended.Entities
+{
+ public class EntityManager : UpdateSystem
+ {
+ private const int _defaultBagSize = 128;
+
+ public EntityManager(ComponentManager componentManager)
+ {
+ _componentManager = componentManager;
+ _addedEntities = new Bag<int>(_defaultBagSize);
+ _removedEntities = new Bag<int>(_defaultBagSize);
+ _changedEntities = new Bag<int>(_defaultBagSize);
+ _entityToComponentBits = new Bag<BitVector32>(_defaultBagSize);
+ _componentManager.ComponentsChanged += OnComponentsChanged;
+
+ _entityBag = new Bag<Entity>(_defaultBagSize);
+ _entityPool = new Pool<Entity>(() => new Entity(_nextId++, this, _componentManager), _defaultBagSize);
+ }
+
+ private readonly ComponentManager _componentManager;
+ private int _nextId;
+
+ public int Capacity => _entityBag.Capacity;
+ public IEnumerable<int> Entities => _entityBag.Where(e => e != null).Select(e => e.Id);
+ public int ActiveCount { get; private set; }
+
+ private readonly Bag<Entity> _entityBag;
+ private readonly Pool<Entity> _entityPool;
+ private readonly Bag<int> _addedEntities;
+ private readonly Bag<int> _removedEntities;
+ private readonly Bag<int> _changedEntities;
+ private readonly Bag<BitVector32> _entityToComponentBits;
+
+ public event Action<int> EntityAdded;
+ public event Action<int> EntityRemoved;
+ public event Action<int> EntityChanged;
+
+ public Entity Create()
+ {
+ var entity = _entityPool.Obtain();
+ var id = entity.Id;
+ Debug.Assert(_entityBag[id] == null);
+ _entityBag[id] = entity;
+ _addedEntities.Add(id);
+ _entityToComponentBits[id] = new BitVector32(0);
+ return entity;
+ }
+
+ public void Destroy(int entityId)
+ {
+ if (!_removedEntities.Contains(entityId))
+ _removedEntities.Add(entityId);
+ }
+
+ public void Destroy(Entity entity)
+ {
+ Destroy(entity.Id);
+ }
+
+ public Entity Get(int entityId)
+ {
+ return _entityBag[entityId];
+ }
+
+ public BitVector32 GetComponentBits(int entityId)
+ {
+ return _entityToComponentBits[entityId];
+ }
+
+ private void OnComponentsChanged(int entityId)
+ {
+ _changedEntities.Add(entityId);
+ _entityToComponentBits[entityId] = _componentManager.CreateComponentBits(entityId);
+ EntityChanged?.Invoke(entityId);
+ }
+
+ public override void Update(GameTime gameTime)
+ {
+ foreach (var entityId in _addedEntities)
+ {
+ _entityToComponentBits[entityId] = _componentManager.CreateComponentBits(entityId);
+ ActiveCount++;
+ EntityAdded?.Invoke(entityId);
+ }
+
+ foreach (var entityId in _changedEntities)
+ {
+ _entityToComponentBits[entityId] = _componentManager.CreateComponentBits(entityId);
+ EntityChanged?.Invoke(entityId);
+ }
+
+ foreach (var entityId in _removedEntities)
+ {
+ // we must notify subscribers before removing it from the pool
+ // otherwise an entity system could still be using the entity when the same id is obtained.
+ EntityRemoved?.Invoke(entityId);
+
+ var entity = _entityBag[entityId];
+ _entityBag[entityId] = null;
+ _componentManager.Destroy(entityId);
+ _entityToComponentBits[entityId] = default(BitVector32);
+ ActiveCount--;
+ _entityPool.Free(entity);
+ }
+
+ _addedEntities.Clear();
+ _removedEntities.Clear();
+ _changedEntities.Clear();
+ }
+ }
+}
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/EntitySubscription.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/EntitySubscription.cs
new file mode 100644
index 0000000..6766b0f
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/EntitySubscription.cs
@@ -0,0 +1,61 @@
+using System;
+using MonoGame.Extended.Collections;
+
+namespace MonoGame.Extended.Entities
+{
+ internal class EntitySubscription : IDisposable
+ {
+ private readonly Bag<int> _activeEntities;
+ private readonly EntityManager _entityManager;
+ private readonly Aspect _aspect;
+ private bool _rebuildActives;
+
+ internal EntitySubscription(EntityManager entityManager, Aspect aspect)
+ {
+ _entityManager = entityManager;
+ _aspect = aspect;
+ _activeEntities = new Bag<int>(entityManager.Capacity);
+ _rebuildActives = true;
+
+ _entityManager.EntityAdded += OnEntityAdded;
+ _entityManager.EntityRemoved += OnEntityRemoved;
+ _entityManager.EntityChanged += OnEntityChanged;
+ }
+
+ private void OnEntityAdded(int entityId)
+ {
+ if (_aspect.IsInterested(_entityManager.GetComponentBits(entityId)))
+ _activeEntities.Add(entityId);
+ }
+
+ private void OnEntityRemoved(int entityId) => _rebuildActives = true;
+ private void OnEntityChanged(int entityId) => _rebuildActives = true;
+
+ public void Dispose()
+ {
+ _entityManager.EntityAdded -= OnEntityAdded;
+ _entityManager.EntityRemoved -= OnEntityRemoved;
+ }
+
+ public Bag<int> ActiveEntities
+ {
+ get
+ {
+ if (_rebuildActives)
+ RebuildActives();
+
+ return _activeEntities;
+ }
+ }
+
+ private void RebuildActives()
+ {
+ _activeEntities.Clear();
+
+ foreach (var entity in _entityManager.Entities)
+ OnEntityAdded(entity);
+
+ _rebuildActives = false;
+ }
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/MonoGame.Extended.Entities.csproj b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/MonoGame.Extended.Entities.csproj
new file mode 100644
index 0000000..21ca693
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/MonoGame.Extended.Entities.csproj
@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <Description>An Entity Component System to make MonoGame more awesome.</Description>
+ <PackageTags>monogame ecs entity component system</PackageTags>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\MonoGame.Extended\MonoGame.Extended.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/DrawSystem.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/DrawSystem.cs
new file mode 100644
index 0000000..b9e63eb
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/DrawSystem.cs
@@ -0,0 +1,16 @@
+using Microsoft.Xna.Framework;
+
+namespace MonoGame.Extended.Entities.Systems
+{
+ public interface IDrawSystem : ISystem
+ {
+ void Draw(GameTime gameTime);
+ }
+
+ public abstract class DrawSystem : IDrawSystem
+ {
+ public virtual void Dispose() { }
+ public virtual void Initialize(World world) { }
+ public abstract void Draw(GameTime gameTime);
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/EntityDrawSystem.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/EntityDrawSystem.cs
new file mode 100644
index 0000000..5a0d6b9
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/EntityDrawSystem.cs
@@ -0,0 +1,14 @@
+using Microsoft.Xna.Framework;
+
+namespace MonoGame.Extended.Entities.Systems
+{
+ public abstract class EntityDrawSystem : EntitySystem, IDrawSystem
+ {
+ protected EntityDrawSystem(AspectBuilder aspect)
+ : base(aspect)
+ {
+ }
+
+ public abstract void Draw(GameTime gameTime);
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/EntityProcessingSystem.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/EntityProcessingSystem.cs
new file mode 100644
index 0000000..c4d9339
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/EntityProcessingSystem.cs
@@ -0,0 +1,26 @@
+using Microsoft.Xna.Framework;
+
+namespace MonoGame.Extended.Entities.Systems
+{
+ public abstract class EntityProcessingSystem : EntityUpdateSystem
+ {
+ protected EntityProcessingSystem(AspectBuilder aspectBuilder)
+ : base(aspectBuilder)
+ {
+ }
+
+ public override void Update(GameTime gameTime)
+ {
+ Begin();
+
+ foreach (var entityId in ActiveEntities)
+ Process(gameTime, entityId);
+
+ End();
+ }
+
+ public virtual void Begin() { }
+ public abstract void Process(GameTime gameTime, int entityId);
+ public virtual void End() { }
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/EntitySystem.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/EntitySystem.cs
new file mode 100644
index 0000000..2c95107
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/EntitySystem.cs
@@ -0,0 +1,51 @@
+using MonoGame.Extended.Collections;
+
+namespace MonoGame.Extended.Entities.Systems
+{
+ public abstract class EntitySystem : ISystem
+ {
+ protected EntitySystem(AspectBuilder aspectBuilder)
+ {
+ _aspectBuilder = aspectBuilder;
+ }
+
+ public void Dispose()
+ {
+ if (_world != null)
+ {
+ _world.EntityManager.EntityAdded -= OnEntityAdded;
+ _world.EntityManager.EntityRemoved -= OnEntityRemoved;
+ }
+ }
+
+ private readonly AspectBuilder _aspectBuilder;
+ private EntitySubscription _subscription;
+
+ private World _world;
+
+ protected virtual void OnEntityChanged(int entityId) { }
+ protected virtual void OnEntityAdded(int entityId) { }
+ protected virtual void OnEntityRemoved(int entityId) { }
+
+ public Bag<int> ActiveEntities => _subscription.ActiveEntities;
+
+ public virtual void Initialize(World world)
+ {
+ _world = world;
+
+ var aspect = _aspectBuilder.Build(_world.ComponentManager);
+ _subscription = new EntitySubscription(_world.EntityManager, aspect);
+ _world.EntityManager.EntityAdded += OnEntityAdded;
+ _world.EntityManager.EntityRemoved += OnEntityRemoved;
+ _world.EntityManager.EntityChanged += OnEntityChanged;
+
+ Initialize(world.ComponentManager);
+ }
+
+ public abstract void Initialize(IComponentMapperService mapperService);
+
+ protected void DestroyEntity(int entityId) => _world.DestroyEntity(entityId);
+ protected Entity CreateEntity() => _world.CreateEntity();
+ protected Entity GetEntity(int entityId) => _world.GetEntity(entityId);
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/EntityUpdateSystem.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/EntityUpdateSystem.cs
new file mode 100644
index 0000000..97a61d5
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/EntityUpdateSystem.cs
@@ -0,0 +1,14 @@
+using Microsoft.Xna.Framework;
+
+namespace MonoGame.Extended.Entities.Systems
+{
+ public abstract class EntityUpdateSystem : EntitySystem, IUpdateSystem
+ {
+ protected EntityUpdateSystem(AspectBuilder aspectBuilder)
+ : base(aspectBuilder)
+ {
+ }
+
+ public abstract void Update(GameTime gameTime);
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/ISystem.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/ISystem.cs
new file mode 100644
index 0000000..d4511fc
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/ISystem.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace MonoGame.Extended.Entities.Systems
+{
+ public interface ISystem : IDisposable
+ {
+ void Initialize(World world);
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/UpdateSystem.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/UpdateSystem.cs
new file mode 100644
index 0000000..e85c964
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/UpdateSystem.cs
@@ -0,0 +1,16 @@
+using Microsoft.Xna.Framework;
+
+namespace MonoGame.Extended.Entities.Systems
+{
+ public interface IUpdateSystem : ISystem
+ {
+ void Update(GameTime gameTime);
+ }
+
+ public abstract class UpdateSystem : IUpdateSystem
+ {
+ public virtual void Dispose() { }
+ public virtual void Initialize(World world) { }
+ public abstract void Update(GameTime gameTime);
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/World.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/World.cs
new file mode 100644
index 0000000..022d4b8
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/World.cs
@@ -0,0 +1,84 @@
+using Microsoft.Xna.Framework;
+using MonoGame.Extended.Collections;
+using MonoGame.Extended.Entities.Systems;
+
+namespace MonoGame.Extended.Entities
+{
+ public class World : SimpleDrawableGameComponent
+ {
+ private readonly Bag<IUpdateSystem> _updateSystems;
+ private readonly Bag<IDrawSystem> _drawSystems;
+
+ internal World()
+ {
+ _updateSystems = new Bag<IUpdateSystem>();
+ _drawSystems = new Bag<IDrawSystem>();
+
+ RegisterSystem(ComponentManager = new ComponentManager());
+ RegisterSystem(EntityManager = new EntityManager(ComponentManager));
+ }
+
+ public override void Dispose()
+ {
+ foreach (var updateSystem in _updateSystems)
+ updateSystem.Dispose();
+
+ foreach (var drawSystem in _drawSystems)
+ drawSystem.Dispose();
+
+ _updateSystems.Clear();
+ _drawSystems.Clear();
+
+ base.Dispose();
+ }
+
+ internal EntityManager EntityManager { get; }
+ internal ComponentManager ComponentManager { get; }
+
+ public int EntityCount => EntityManager.ActiveCount;
+
+ internal void RegisterSystem(ISystem system)
+ {
+ // ReSharper disable once ConvertIfStatementToSwitchStatement
+ if (system is IUpdateSystem updateSystem)
+ _updateSystems.Add(updateSystem);
+
+ if (system is IDrawSystem drawSystem)
+ _drawSystems.Add(drawSystem);
+
+ system.Initialize(this);
+ }
+
+ public Entity GetEntity(int entityId)
+ {
+ return EntityManager.Get(entityId);
+ }
+
+ public Entity CreateEntity()
+ {
+ return EntityManager.Create();
+ }
+
+ public void DestroyEntity(int entityId)
+ {
+ EntityManager.Destroy(entityId);
+ }
+
+ public void DestroyEntity(Entity entity)
+ {
+ EntityManager.Destroy(entity);
+ }
+
+ public override void Update(GameTime gameTime)
+ {
+ foreach (var system in _updateSystems)
+ system.Update(gameTime);
+ }
+
+ public override void Draw(GameTime gameTime)
+ {
+ foreach (var system in _drawSystems)
+ system.Draw(gameTime);
+ }
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/WorldBuilder.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/WorldBuilder.cs
new file mode 100644
index 0000000..7f03323
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/WorldBuilder.cs
@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+using MonoGame.Extended.Entities.Systems;
+
+namespace MonoGame.Extended.Entities
+{
+ public class WorldBuilder
+ {
+ private readonly List<ISystem> _systems = new List<ISystem>();
+
+ public WorldBuilder AddSystem(ISystem system)
+ {
+ _systems.Add(system);
+ return this;
+ }
+
+ public World Build()
+ {
+ var world = new World();
+
+ foreach (var system in _systems)
+ world.RegisterSystem(system);
+
+ return world;
+ }
+ }
+} \ No newline at end of file