mirror of
https://github.com/AlexMacocian/MonoGame.Extended.git
synced 2026-07-24 03:56:31 +00:00
implemented aspect filtering builder
This commit is contained in:
@@ -13,6 +13,7 @@ namespace Sandbox
|
||||
private ComponentMapper<Transform2> _transformMapper;
|
||||
|
||||
public MyRenderSystem(GraphicsDevice graphicsDevice)
|
||||
: base(Aspect.All(typeof(Transform2)))
|
||||
{
|
||||
_spriteBatch = new SpriteBatch(graphicsDevice);
|
||||
}
|
||||
@@ -26,7 +27,7 @@ namespace Sandbox
|
||||
{
|
||||
_spriteBatch.Begin(samplerState: SamplerState.PointClamp);
|
||||
|
||||
foreach (var entity in World.EntityManager.Entities)
|
||||
foreach (var entity in GetEntities())
|
||||
{
|
||||
var transform = _transformMapper.GetComponent(entity);
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using MonoGame.Extended.Collections;
|
||||
|
||||
namespace MonoGame.Extended.Entities
|
||||
{
|
||||
public class Aspect
|
||||
{
|
||||
private Aspect()
|
||||
{
|
||||
AllSet = new BitArray(32);
|
||||
ExclusionSet = new BitArray(32);
|
||||
OneSet = new BitArray(32);
|
||||
}
|
||||
|
||||
public BitArray AllSet { get; }
|
||||
public BitArray ExclusionSet { get; }
|
||||
public BitArray OneSet { get; }
|
||||
|
||||
public static Builder All(params Type[] types)
|
||||
{
|
||||
return new Builder().All(types);
|
||||
}
|
||||
|
||||
public static Builder One(params Type[] types)
|
||||
{
|
||||
return new Builder().One(types);
|
||||
}
|
||||
|
||||
public static Builder Exclude(params Type[] types)
|
||||
{
|
||||
return new Builder().Exclude(types);
|
||||
}
|
||||
|
||||
public bool IsInterested(BitArray componentBits)
|
||||
{
|
||||
if (!AllSet.IsEmpty() && !componentBits.ContainsAll(AllSet))
|
||||
return false;
|
||||
|
||||
if (!ExclusionSet.IsEmpty() && ExclusionSet.Intersects(componentBits))
|
||||
return false;
|
||||
|
||||
if (!OneSet.IsEmpty() && !OneSet.Intersects(componentBits))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public class Builder
|
||||
{
|
||||
public Builder()
|
||||
{
|
||||
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 Builder All(params Type[] types)
|
||||
{
|
||||
foreach (var type in types)
|
||||
AllTypes.Add(type);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder One(params Type[] types)
|
||||
{
|
||||
foreach (var type in types)
|
||||
OneTypes.Add(type);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder 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, aspect.AllSet);
|
||||
Associate(componentManager, OneTypes, aspect.OneSet);
|
||||
Associate(componentManager, ExclusionTypes, aspect.ExclusionSet);
|
||||
return aspect;
|
||||
}
|
||||
|
||||
// ReSharper disable once ParameterTypeCanBeEnumerable.Local
|
||||
private static void Associate(ComponentManager componentManager, Bag<Type> types, BitArray bitArray)
|
||||
{
|
||||
foreach (var type in types)
|
||||
{
|
||||
var id = componentManager.GetComponentTypeId(type);
|
||||
bitArray.Set(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ namespace MonoGame.Extended.Entities
|
||||
public class ComponentManager : UpdateSystem
|
||||
{
|
||||
public ComponentManager()
|
||||
: base(Aspect.All())
|
||||
{
|
||||
_mappers = new Bag<ComponentMapper>();
|
||||
_componentTypes = new Dictionary<Type, int>();
|
||||
@@ -17,10 +18,9 @@ namespace MonoGame.Extended.Entities
|
||||
private readonly Bag<ComponentMapper> _mappers;
|
||||
private readonly Dictionary<Type, int> _componentTypes;
|
||||
|
||||
private ComponentMapper<T> CreateMapperForType<T>()
|
||||
private ComponentMapper<T> CreateMapperForType<T>(int id)
|
||||
where T : class
|
||||
{
|
||||
var id = _mappers.Count;
|
||||
var mapper = new ComponentMapper<T>(id);
|
||||
_mappers[id] = mapper;
|
||||
return mapper;
|
||||
@@ -29,12 +29,22 @@ namespace MonoGame.Extended.Entities
|
||||
public ComponentMapper<T> GetMapper<T>()
|
||||
where T : class
|
||||
{
|
||||
if (_componentTypes.TryGetValue(typeof(T), out var id))
|
||||
var id = GetComponentTypeId(typeof(T));
|
||||
|
||||
if (_mappers[id] != null)
|
||||
return _mappers[id] as ComponentMapper<T>;
|
||||
|
||||
var mapper = CreateMapperForType<T>();
|
||||
_componentTypes.Add(typeof(T), mapper.Id);
|
||||
return mapper;
|
||||
return CreateMapperForType<T>(id);
|
||||
}
|
||||
|
||||
public int GetComponentTypeId(Type type)
|
||||
{
|
||||
if (_componentTypes.TryGetValue(type, out var id))
|
||||
return id;
|
||||
|
||||
id = _componentTypes.Count;
|
||||
_componentTypes.Add(type, id);
|
||||
return id;
|
||||
}
|
||||
|
||||
public override void Initialize(ComponentManager componentManager)
|
||||
|
||||
@@ -24,7 +24,6 @@ namespace MonoGame.Extended.Entities
|
||||
Components = new Bag<T>();
|
||||
}
|
||||
|
||||
public int Id { get; }
|
||||
public Bag<T> Components { get; }
|
||||
|
||||
public void CreateComponent(int entityId, T component)
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace MonoGame.Extended.Entities
|
||||
public class EntityManager : UpdateSystem
|
||||
{
|
||||
public EntityManager(ComponentManager componentManager)
|
||||
: base(Aspect.All())
|
||||
{
|
||||
_componentManager = componentManager;
|
||||
Entities = new Bag<Entity>(128);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework;
|
||||
using MonoGame.Extended.Collections;
|
||||
using MonoGame.Extended.Entities.Systems;
|
||||
|
||||
|
||||
@@ -1,8 +1,26 @@
|
||||
namespace MonoGame.Extended.Entities.Systems
|
||||
using MonoGame.Extended.Collections;
|
||||
|
||||
namespace MonoGame.Extended.Entities.Systems
|
||||
{
|
||||
public abstract class BaseSystem
|
||||
{
|
||||
private readonly Aspect.Builder _aspectBuilder;
|
||||
|
||||
protected BaseSystem(Aspect.Builder aspect)
|
||||
{
|
||||
_aspectBuilder = aspect;
|
||||
}
|
||||
|
||||
protected Aspect Aspect { get; private set; }
|
||||
|
||||
public EntityWorld World { get; internal set; }
|
||||
|
||||
public abstract void Initialize(ComponentManager componentManager);
|
||||
|
||||
protected Bag<Entity> GetEntities()
|
||||
{
|
||||
var aspect = _aspectBuilder.Build(World.ComponentManager);
|
||||
return World.EntityManager.Entities;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,8 @@ namespace MonoGame.Extended.Entities.Systems
|
||||
{
|
||||
public abstract class DrawSystem : BaseSystem
|
||||
{
|
||||
protected DrawSystem()
|
||||
protected DrawSystem(Aspect.Builder aspect)
|
||||
: base(aspect)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ namespace MonoGame.Extended.Entities.Systems
|
||||
{
|
||||
public abstract class UpdateSystem : BaseSystem
|
||||
{
|
||||
protected UpdateSystem()
|
||||
protected UpdateSystem(Aspect.Builder aspect)
|
||||
: base(aspect)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MonoGame.Extended.Sprites;
|
||||
using Xunit;
|
||||
|
||||
namespace MonoGame.Extended.Entities.Tests
|
||||
{
|
||||
public class AspectBuilderTests
|
||||
{
|
||||
[Fact]
|
||||
public void MatchAllTypes()
|
||||
{
|
||||
var builder = new Aspect.Builder()
|
||||
.All(typeof(Transform2), typeof(Sprite));
|
||||
|
||||
Assert.Equal(2, builder.AllTypes.Count);
|
||||
Assert.Contains(typeof(Transform2), builder.AllTypes);
|
||||
Assert.Contains(typeof(Sprite), builder.AllTypes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchAllTypesIsEmpty()
|
||||
{
|
||||
var builder = new Aspect.Builder()
|
||||
.All();
|
||||
|
||||
Assert.Empty(builder.AllTypes);
|
||||
Assert.Empty(builder.OneTypes);
|
||||
Assert.Empty(builder.ExclusionTypes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchOneOfType()
|
||||
{
|
||||
var builder = new Aspect.Builder()
|
||||
.One(typeof(Transform2), typeof(Sprite));
|
||||
|
||||
Assert.Equal(2, builder.OneTypes.Count);
|
||||
Assert.Contains(typeof(Transform2), builder.OneTypes);
|
||||
Assert.Contains(typeof(Sprite), builder.OneTypes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExcludeTypes()
|
||||
{
|
||||
var builder = new Aspect.Builder()
|
||||
.Exclude(typeof(Transform2), typeof(Sprite));
|
||||
|
||||
Assert.Equal(2, builder.ExclusionTypes.Count);
|
||||
Assert.Contains(typeof(Transform2), builder.ExclusionTypes);
|
||||
Assert.Contains(typeof(Sprite), builder.ExclusionTypes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildAspect()
|
||||
{
|
||||
var componentManager = new ComponentManager();
|
||||
var builder = new Aspect.Builder()
|
||||
.All(typeof(Transform2), typeof(Sprite))
|
||||
.One(typeof(string))
|
||||
.Exclude(typeof(Texture2D));
|
||||
|
||||
var aspect = builder.Build(componentManager);
|
||||
|
||||
Assert.NotEmpty(aspect.AllSet);
|
||||
Assert.NotEmpty(aspect.OneSet);
|
||||
Assert.NotEmpty(aspect.ExclusionSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using System.Collections;
|
||||
using MonoGame.Extended.Sprites;
|
||||
using Xunit;
|
||||
|
||||
namespace MonoGame.Extended.Entities.Tests
|
||||
{
|
||||
public class DummyComponent
|
||||
{
|
||||
}
|
||||
|
||||
public class AspectTests
|
||||
{
|
||||
private readonly ComponentManager _componentManager;
|
||||
private readonly BitArray _entityA;
|
||||
private readonly BitArray _entityB;
|
||||
|
||||
public AspectTests()
|
||||
{
|
||||
_componentManager = new ComponentManager();
|
||||
_entityA = new BitArray(3)
|
||||
{
|
||||
[_componentManager.GetComponentTypeId(typeof(Transform2))] = true,
|
||||
[_componentManager.GetComponentTypeId(typeof(Sprite))] = true,
|
||||
[_componentManager.GetComponentTypeId(typeof(DummyComponent))] = true
|
||||
};
|
||||
_entityB = new BitArray(3)
|
||||
{
|
||||
[_componentManager.GetComponentTypeId(typeof(Transform2))] = true,
|
||||
[_componentManager.GetComponentTypeId(typeof(Sprite))] = true,
|
||||
};
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyAspectMatchesAllComponents()
|
||||
{
|
||||
var componentManager = new ComponentManager();
|
||||
var emptyAspect = Aspect.All()
|
||||
.Build(componentManager);
|
||||
|
||||
Assert.True(emptyAspect.IsInterested(_entityA));
|
||||
Assert.True(emptyAspect.IsInterested(_entityB));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsInterestedInAllComponents()
|
||||
{
|
||||
var allAspect = Aspect
|
||||
.All(typeof(Sprite), typeof(Transform2), typeof(DummyComponent))
|
||||
.Build(_componentManager);
|
||||
|
||||
Assert.True(allAspect.IsInterested(_entityA));
|
||||
Assert.False(allAspect.IsInterested(_entityB));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsInterestedInEitherOneOfTheComponents()
|
||||
{
|
||||
var eitherOneAspect = Aspect
|
||||
.One(typeof(Transform2), typeof(DummyComponent))
|
||||
.Build(_componentManager);
|
||||
|
||||
Assert.True(eitherOneAspect.IsInterested(_entityA));
|
||||
Assert.True(eitherOneAspect.IsInterested(_entityB));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsInterestedInJustOneComponent()
|
||||
{
|
||||
var oneAspect = Aspect
|
||||
.One(typeof(DummyComponent))
|
||||
.Build(_componentManager);
|
||||
|
||||
Assert.True(oneAspect.IsInterested(_entityA));
|
||||
Assert.False(oneAspect.IsInterested(_entityB));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsInterestedInExcludingOneComponent()
|
||||
{
|
||||
var oneAspect = Aspect
|
||||
.Exclude(typeof(DummyComponent))
|
||||
.Build(_componentManager);
|
||||
|
||||
Assert.False(oneAspect.IsInterested(_entityA));
|
||||
Assert.True(oneAspect.IsInterested(_entityB));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using MonoGame.Extended.Sprites;
|
||||
using Xunit;
|
||||
|
||||
namespace MonoGame.Extended.Entities.Tests
|
||||
{
|
||||
public class ComponentManagerTests
|
||||
{
|
||||
[Fact]
|
||||
public void GetMapperForType()
|
||||
{
|
||||
var componentManager = new ComponentManager();
|
||||
var transformMapper = componentManager.GetMapper<Transform2>();
|
||||
var spriteMapper = componentManager.GetMapper<Sprite>();
|
||||
|
||||
Assert.IsType<ComponentMapper<Transform2>>(transformMapper);
|
||||
Assert.IsType<ComponentMapper<Sprite>>(spriteMapper);
|
||||
Assert.Equal(0, transformMapper.Id);
|
||||
Assert.Equal(1, spriteMapper.Id);
|
||||
Assert.Same(spriteMapper, componentManager.GetMapper<Sprite>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetComponentTypeId()
|
||||
{
|
||||
var componentManager = new ComponentManager();
|
||||
|
||||
Assert.Equal(0, componentManager.GetComponentTypeId(typeof(Transform2)));
|
||||
Assert.Equal(1, componentManager.GetComponentTypeId(typeof(Sprite)));
|
||||
Assert.Equal(0, componentManager.GetComponentTypeId(typeof(Transform2)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,7 @@
|
||||
using System;
|
||||
using MonoGame.Extended.Entities.Systems;
|
||||
using Xunit;
|
||||
using Xunit;
|
||||
|
||||
namespace MonoGame.Extended.Entities.Tests
|
||||
{
|
||||
public class ComponentManagerTests
|
||||
{
|
||||
[Fact]
|
||||
public void CreateComponentManager()
|
||||
{
|
||||
var manager = new ComponentManager();
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public class ComponentMapperTests
|
||||
{
|
||||
[Fact]
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using MonoGame.Extended.Entities;
|
||||
using MonoGame.Extended.Entities.Legacy;
|
||||
using MonoGame.Extended.Entities.Legacy;
|
||||
|
||||
namespace MonoGame.Extended.Gui.Tests.Implementation
|
||||
{
|
||||
|
||||
+1
-2
@@ -1,5 +1,4 @@
|
||||
using MonoGame.Extended.Entities;
|
||||
using MonoGame.Extended.Entities.Legacy;
|
||||
using MonoGame.Extended.Entities.Legacy;
|
||||
|
||||
namespace MonoGame.Extended.Gui.Tests.Implementation
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user