From 82daabb12005c6771f88baada3c09b918fbadacb Mon Sep 17 00:00:00 2001 From: Mike Bowers Date: Tue, 11 Jun 2024 11:14:36 -0400 Subject: [PATCH] Updated ComponentManager to implement IEnumerable (#823) (#888) --- .../ComponentManager.cs | 73 ++++++++++++++++++- .../ComponentManagerTests.cs | 42 ++++++++++- 2 files changed, 109 insertions(+), 6 deletions(-) diff --git a/source/MonoGame.Extended.Entities/ComponentManager.cs b/source/MonoGame.Extended.Entities/ComponentManager.cs index a59c87ced..f89faa91d 100644 --- a/source/MonoGame.Extended.Entities/ComponentManager.cs +++ b/source/MonoGame.Extended.Entities/ComponentManager.cs @@ -1,18 +1,21 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; +using System.Reflection; using Microsoft.Xna.Framework; using MonoGame.Extended.Collections; using MonoGame.Extended.Entities.Systems; namespace MonoGame.Extended.Entities { - public interface IComponentMapperService + public interface IComponentMapperService : IEnumerable { ComponentMapper GetMapper() where T : class; + ComponentMapper this[Type type] { get; } } - public class ComponentManager : UpdateSystem, IComponentMapperService + public class ComponentManager : UpdateSystem, IComponentMapperService, IEnumerable { public ComponentManager() { @@ -20,11 +23,26 @@ public ComponentManager() _componentTypes = new Dictionary(); } - private readonly Bag _componentMappers; - private readonly Dictionary _componentTypes; + internal readonly Bag _componentMappers; + internal readonly Dictionary _componentTypes; public Action ComponentsChanged; + private ComponentMapper CreateMapperForType(Type type, int componentTypeId) + { + if (!type.IsClass) + throw new ArgumentException("Type must be a class type.", nameof(type)); + + // 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 mapperType = typeof(ComponentMapper<>).MakeGenericType(type); + var mapper = Activator.CreateInstance(mapperType, args: new object[] { componentTypeId, ComponentsChanged }); + _componentMappers[componentTypeId] = (ComponentMapper)mapper; + return (ComponentMapper)mapper; + } + private ComponentMapper CreateMapperForType(int componentTypeId) where T : class { @@ -86,5 +104,52 @@ public void Destroy(int entityId) public override void Update(GameTime gameTime) { } + + public ComponentMapper this[Type type] + { + get + { + var componentTypeId = GetComponentTypeId(type); + + if (_componentMappers[componentTypeId] != null) + return _componentMappers[componentTypeId]; + + return CreateMapperForType(type, componentTypeId); + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public IEnumerator GetEnumerator() => new ComponentMapperEnumerator(this); + + public struct ComponentMapperEnumerator : IEnumerator + { + private readonly ComponentManager _componentManager; + private IEnumerator _enumerator; + + internal ComponentMapperEnumerator(ComponentManager componentManager) + { + _componentManager = componentManager; + _enumerator = _componentManager._componentMappers.GetEnumerator(); + } + + public bool MoveNext() + { + return _enumerator.MoveNext(); + } + + public void Reset() + { + _enumerator.Reset(); + } + + object IEnumerator.Current => _enumerator.Current; + public ComponentMapper Current => _enumerator.Current; + + public void Dispose() + { + _enumerator?.Dispose(); + } + } } } diff --git a/tests/MonoGame.Extended.Entities.Tests/ComponentManagerTests.cs b/tests/MonoGame.Extended.Entities.Tests/ComponentManagerTests.cs index 8d988e408..abf1a16d9 100644 --- a/tests/MonoGame.Extended.Entities.Tests/ComponentManagerTests.cs +++ b/tests/MonoGame.Extended.Entities.Tests/ComponentManagerTests.cs @@ -1,4 +1,5 @@ -using MonoGame.Extended.Sprites; +using System.Collections.Generic; +using MonoGame.Extended.Sprites; using Xunit; namespace MonoGame.Extended.Entities.Tests @@ -43,5 +44,42 @@ public void GetComponentTypeId() // Assert.Equal(0b101, identity); //} + + + [Fact] + public void GetMapperForTypeByIndexer() + { + var componentManager = new ComponentManager(); + var transformMapper = componentManager[typeof(Transform2)]; + var spriteMapper = componentManager[typeof(Sprite)]; + + Assert.IsType>(transformMapper); + Assert.IsType>(spriteMapper); + Assert.Equal(0, transformMapper.Id); + Assert.Equal(1, spriteMapper.Id); + Assert.Same(spriteMapper, componentManager[typeof(Sprite)]); + } + + [Fact] + public void EnumerateMappers() + { + var componentManager = new ComponentManager(); + + var transformMapper = componentManager.GetMapper(); + var spriteMapper = componentManager.GetMapper(); + var cameraMapper = componentManager.GetMapper(); + + List enumeratedMappers = new List(); + + foreach (ComponentMapper mapper in componentManager) + { + enumeratedMappers.Add(mapper); + } + + Assert.Equal(3, enumeratedMappers.Count); + Assert.Contains(transformMapper, enumeratedMappers); + Assert.Contains(spriteMapper, enumeratedMappers); + Assert.Contains(cameraMapper, enumeratedMappers); + } } -} \ No newline at end of file +}