Move source projects into source directory

This commit is contained in:
Christopher Whitley
2024-05-18 19:59:41 -04:00
parent 9c5f7cd3b4
commit a4d72eaf6c
423 changed files with 123 additions and 105 deletions
@@ -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;
}
}
}
@@ -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;
}
}
}
}
@@ -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;
}
}
}
@@ -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)
{
}
}
}
@@ -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);
}
}
}
@@ -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);
// }
//}
}
@@ -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);
}
}
}
@@ -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();
}
}
}
@@ -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;
}
}
}
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<Authors>craftworkgames</Authors>
<Description>An Entity Component System to make MonoGame more awesome.</Description>
<PackageTags>monogame ecs entity component system</PackageTags>
</PropertyGroup>
<PropertyGroup>
<NoWarn>NU1701</NoWarn>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MonoGame.Extended\MonoGame.Extended.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.303">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>
@@ -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);
}
}
@@ -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);
}
}
@@ -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() { }
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -0,0 +1,9 @@
using System;
namespace MonoGame.Extended.Entities.Systems
{
public interface ISystem : IDisposable
{
void Initialize(World world);
}
}
@@ -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);
}
}
@@ -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);
}
}
}
@@ -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;
}
}
}