storage optimization
							parent
							
								
									71a95cb2d7
								
							
						
					
					
						commit
						91cf93e1c9
					
				|  | @ -1,46 +1,46 @@ | |||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Runtime.CompilerServices; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
| 	internal class ComponentDepot | ||||
| 	{ | ||||
| 		private Dictionary<Type, ComponentStorage> storages = new Dictionary<Type, ComponentStorage>(); | ||||
| 		private TypeIndices ComponentTypeIndices; | ||||
| 
 | ||||
| 		private Dictionary<FilterSignature, IndexableSet<int>> filterSignatureToEntityIDs = new Dictionary<FilterSignature, IndexableSet<int>>(); | ||||
| 		private ComponentStorage[] storages = new ComponentStorage[256]; | ||||
| 
 | ||||
| 		private Dictionary<Type, HashSet<FilterSignature>> typeToFilterSignatures = new Dictionary<Type, HashSet<FilterSignature>>(); | ||||
| 
 | ||||
| #if DEBUG | ||||
| 		private Dictionary<Type, Filter> singleComponentFilters = new Dictionary<Type, Filter>(); | ||||
| #endif | ||||
| 
 | ||||
| 		private HashSet<Type> TypesWithDisabledSerialization = new HashSet<Type>(); | ||||
| 
 | ||||
| 		[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| 		internal void Register<TComponent>() where TComponent : unmanaged | ||||
| 		public ComponentDepot(TypeIndices componentTypeIndices) | ||||
| 		{ | ||||
| 			if (!storages.ContainsKey(typeof(TComponent))) | ||||
| 			{ | ||||
| 				storages.Add(typeof(TComponent), new ComponentStorage<TComponent>()); | ||||
| #if DEBUG | ||||
| 				singleComponentFilters.Add(typeof(TComponent), CreateFilter(new HashSet<Type>() { typeof(TComponent) }, new HashSet<Type>())); | ||||
| #endif | ||||
| 			} | ||||
| 			ComponentTypeIndices = componentTypeIndices; | ||||
| 		} | ||||
| 
 | ||||
| 		[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| 		private void Register<TComponent>(int index) where TComponent : unmanaged | ||||
| 		{ | ||||
| 			if (index >= storages.Length) | ||||
| 			{ | ||||
| 				Array.Resize(ref storages, storages.Length * 2); | ||||
| 			} | ||||
| 
 | ||||
| 			storages[index] = new ComponentStorage<TComponent>(); | ||||
| 		} | ||||
| 
 | ||||
| 		// FIXME: is this necessary? | ||||
| 		private ComponentStorage Lookup(Type type) | ||||
| 		{ | ||||
| 			return storages[type]; | ||||
| 			return storages[ComponentTypeIndices.GetIndex(type)]; | ||||
| 		} | ||||
| 
 | ||||
| 		[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| 		private ComponentStorage<TComponent> Lookup<TComponent>() where TComponent : unmanaged | ||||
| 		{ | ||||
| 			// TODO: is it possible to optimize this? | ||||
| 			Register<TComponent>(); | ||||
| 			return (ComponentStorage<TComponent>) storages[typeof(TComponent)]; | ||||
| 			var storageIndex = ComponentTypeIndices.GetIndex<TComponent>(); | ||||
| 			// TODO: is there some way to avoid this null check? | ||||
| 			if (storages[storageIndex] == null) | ||||
| 			{ | ||||
| 				Register<TComponent>(storageIndex); | ||||
| 			} | ||||
| 			return (ComponentStorage<TComponent>) storages[storageIndex]; | ||||
| 		} | ||||
| 
 | ||||
| 		public bool Some<TComponent>() where TComponent : unmanaged | ||||
|  | @ -48,41 +48,26 @@ namespace MoonTools.ECS | |||
| 			return Lookup<TComponent>().Any(); | ||||
| 		} | ||||
| 
 | ||||
| 		public bool Has<TComponent>(int entityID) where TComponent : unmanaged | ||||
| 		{ | ||||
| 			return Lookup<TComponent>().Has(entityID); | ||||
| 		} | ||||
| 
 | ||||
| 		private bool Has(Type type, int entityID) | ||||
| 		{ | ||||
| 			return Lookup(type).Has(entityID); | ||||
| 		} | ||||
| 
 | ||||
| 		public ref readonly TComponent Get<TComponent>(int entityID) where TComponent : unmanaged | ||||
| 		{ | ||||
| 			return ref Lookup<TComponent>().Get(entityID); | ||||
| 		} | ||||
| 
 | ||||
| 		public ref readonly TComponent Get<TComponent>() where TComponent : unmanaged | ||||
| #if DEBUG | ||||
| 		public object Debug_Get(int entityID, int componentTypeIndex) | ||||
| 		{ | ||||
| 			return ref Lookup<TComponent>().Get(); | ||||
| 			return storages[componentTypeIndex].Debug_Get(entityID); | ||||
| 		} | ||||
| #endif | ||||
| 
 | ||||
| 		public ref readonly TComponent GetFirst<TComponent>() where TComponent : unmanaged | ||||
| 		{ | ||||
| 			return ref Lookup<TComponent>().GetFirst(); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Set<TComponent>(int entityID, in TComponent component) where TComponent : unmanaged | ||||
| 		{ | ||||
| 			var existed = Lookup<TComponent>().Set(entityID, component); | ||||
| 
 | ||||
| 			// update filters | ||||
| 			if (!existed) | ||||
| 			{ | ||||
| 				if (typeToFilterSignatures.TryGetValue(typeof(TComponent), out var filterSignatures)) | ||||
| 				{ | ||||
| 					foreach (var filterSignature in filterSignatures) | ||||
| 					{ | ||||
| 						CheckFilter(filterSignature, entityID); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			Lookup<TComponent>().Set(entityID, component); | ||||
| 		} | ||||
| 
 | ||||
| 		public Entity GetSingletonEntity<TComponent>() where TComponent : unmanaged | ||||
|  | @ -95,209 +80,14 @@ namespace MoonTools.ECS | |||
| 			return Lookup<TComponent>().AllComponents(); | ||||
| 		} | ||||
| 
 | ||||
| 		private void Remove(Type type, int entityID) | ||||
| 		public void Remove(int entityID, int storageIndex) | ||||
| 		{ | ||||
| 			var existed = Lookup(type).Remove(entityID); | ||||
| 
 | ||||
| 			// update filters | ||||
| 			if (existed) | ||||
| 			{ | ||||
| 				if (typeToFilterSignatures.TryGetValue(type, out var filterSignatures)) | ||||
| 				{ | ||||
| 					foreach (var filterSignature in filterSignatures) | ||||
| 					{ | ||||
| 						CheckFilter(filterSignature, entityID); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			storages[storageIndex].Remove(entityID); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Remove<TComponent>(int entityID) where TComponent : unmanaged | ||||
| 		{ | ||||
| 			var existed = Lookup<TComponent>().Remove(entityID); | ||||
| 
 | ||||
| 			// update filters | ||||
| 			if (existed) | ||||
| 			{ | ||||
| 				if (typeToFilterSignatures.TryGetValue(typeof(TComponent), out var filterSignatures)) | ||||
| 				{ | ||||
| 					foreach (var filterSignature in filterSignatures) | ||||
| 					{ | ||||
| 						CheckFilter(filterSignature, entityID); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			Lookup<TComponent>().Remove(entityID); | ||||
| 		} | ||||
| 
 | ||||
| 		// TODO: is there some way to optimize this without complicating serialization? | ||||
| 		public void OnEntityDestroy(int entityID) | ||||
| 		{ | ||||
| 			foreach (var type in storages.Keys) | ||||
| 			{ | ||||
| 				Remove(type, entityID); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public Filter CreateFilter(HashSet<Type> included, HashSet<Type> excluded) | ||||
| 		{ | ||||
| 			var filterSignature = new FilterSignature(included, excluded); | ||||
| 			if (!filterSignatureToEntityIDs.ContainsKey(filterSignature)) | ||||
| 			{ | ||||
| 				filterSignatureToEntityIDs.Add(filterSignature, new IndexableSet<int>()); | ||||
| 
 | ||||
| 				foreach (var type in included) | ||||
| 				{ | ||||
| 					if (!typeToFilterSignatures.ContainsKey(type)) | ||||
| 					{ | ||||
| 						typeToFilterSignatures.Add(type, new HashSet<FilterSignature>()); | ||||
| 					} | ||||
| 
 | ||||
| 					typeToFilterSignatures[type].Add(filterSignature); | ||||
| 				} | ||||
| 
 | ||||
| 				foreach (var type in excluded) | ||||
| 				{ | ||||
| 					if (!typeToFilterSignatures.ContainsKey(type)) | ||||
| 					{ | ||||
| 						typeToFilterSignatures.Add(type, new HashSet<FilterSignature>()); | ||||
| 					} | ||||
| 
 | ||||
| 					typeToFilterSignatures[type].Add(filterSignature); | ||||
| 				} | ||||
| 			} | ||||
| 			return new Filter(this, included, excluded); | ||||
| 		} | ||||
| 
 | ||||
| 		// FIXME: this dictionary should probably just store entities | ||||
| 		public IEnumerable<Entity> FilterEntities(Filter filter) | ||||
| 		{ | ||||
| 			foreach (var id in filterSignatureToEntityIDs[filter.Signature]) | ||||
| 			{ | ||||
| 				yield return new Entity(id); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public IEnumerable<Entity> FilterEntitiesRandom(Filter filter) | ||||
| 		{ | ||||
| 			foreach (var index in RandomGenerator.LinearCongruentialGenerator(FilterCount(filter))) | ||||
| 			{ | ||||
| 				yield return new Entity(filterSignatureToEntityIDs[filter.Signature][index]); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public Entity FilterNthEntity(Filter filter, int index) | ||||
| 		{ | ||||
| 			return new Entity(filterSignatureToEntityIDs[filter.Signature][index]); | ||||
| 		} | ||||
| 
 | ||||
| 		public Entity FilterRandomEntity(Filter filter) | ||||
| 		{ | ||||
| 			var randomIndex = RandomGenerator.Next(FilterCount(filter)); | ||||
| 			return new Entity(filterSignatureToEntityIDs[filter.Signature][randomIndex]); | ||||
| 		} | ||||
| 
 | ||||
| 		public int FilterCount(Filter filter) | ||||
| 		{ | ||||
| 			return filterSignatureToEntityIDs[filter.Signature].Count; | ||||
| 		} | ||||
| 
 | ||||
| 		private void CheckFilter(FilterSignature filterSignature, int entityID) | ||||
| 		{ | ||||
| 			foreach (var type in filterSignature.Included) | ||||
| 			{ | ||||
| 				if (!Has(type, entityID)) | ||||
| 				{ | ||||
| 					filterSignatureToEntityIDs[filterSignature].Remove(entityID); | ||||
| 					return; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			foreach (var type in filterSignature.Excluded) | ||||
| 			{ | ||||
| 				if (Has(type, entityID)) | ||||
| 				{ | ||||
| 					filterSignatureToEntityIDs[filterSignature].Remove(entityID); | ||||
| 					return; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			filterSignatureToEntityIDs[filterSignature].Add(entityID); | ||||
| 		} | ||||
| 
 | ||||
| 		public void DisableSerialization<TComponent>() where TComponent : unmanaged | ||||
| 		{ | ||||
| 			TypesWithDisabledSerialization.Add(typeof(TComponent)); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Save(ComponentDepotState state) | ||||
| 		{ | ||||
| 			foreach (var (type, storage) in storages) | ||||
| 			{ | ||||
| 				if (!TypesWithDisabledSerialization.Contains(type)) | ||||
| 				{ | ||||
| 					if (!state.StorageStates.ContainsKey(type)) | ||||
| 					{ | ||||
| 						state.StorageStates.Add(type, storage.CreateState()); | ||||
| 					} | ||||
| 
 | ||||
| 					storage.Save(state.StorageStates[type]); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			foreach (var (signature, set) in filterSignatureToEntityIDs) | ||||
| 			{ | ||||
| 				// FIXME: we could cache this | ||||
| 				if (!signature.Included.Overlaps(TypesWithDisabledSerialization) && !signature.Excluded.Overlaps(TypesWithDisabledSerialization)) | ||||
| 				{ | ||||
| 					if (!state.FilterStates.ContainsKey(signature)) | ||||
| 					{ | ||||
| 						state.FilterStates[signature] = new IndexableSetState<int>(set.Count); | ||||
| 					} | ||||
| 					set.Save(state.FilterStates[signature]); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public void Load(ComponentDepotState state) | ||||
| 		{ | ||||
| 			foreach (var (type, storageState) in state.StorageStates) | ||||
| 			{ | ||||
| 				storages[type].Load(storageState); | ||||
| 			} | ||||
| 
 | ||||
| 			foreach (var (signature, setState) in state.FilterStates) | ||||
| 			{ | ||||
| 				filterSignatureToEntityIDs[signature].Load(setState); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| #if DEBUG | ||||
| 		public IEnumerable<object> Debug_GetAllComponents(int entityID) | ||||
| 		{ | ||||
| 			foreach (var (type, storage) in storages) | ||||
| 			{ | ||||
| 				if (storage.Has(entityID)) | ||||
| 				{ | ||||
| 					yield return storage.Debug_Get(entityID); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public IEnumerable<Entity> Debug_GetEntities(Type componentType) | ||||
| 		{ | ||||
| 			return singleComponentFilters[componentType].Entities; | ||||
| 		} | ||||
| 
 | ||||
| 		public IEnumerable<Type> Debug_SearchComponentType(string typeString) | ||||
| 		{ | ||||
| 			foreach (var type in storages.Keys) | ||||
| 			{ | ||||
| 				if (type.ToString().ToLower().Contains(typeString.ToLower())) | ||||
| 				{ | ||||
| 					yield return type; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| #endif | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -6,12 +6,14 @@ namespace MoonTools.ECS | |||
| { | ||||
| 	internal abstract class ComponentStorage | ||||
| 	{ | ||||
| 		public abstract bool Has(int entityID); | ||||
| 		public abstract bool Remove(int entityID); | ||||
| 		public abstract object Debug_Get(int entityID); | ||||
| 		public abstract ComponentStorageState CreateState(); | ||||
| 		public abstract void Save(ComponentStorageState state); | ||||
| 		public abstract void Load(ComponentStorageState state); | ||||
| 
 | ||||
| #if DEBUG | ||||
| 		public abstract object Debug_Get(int entityID); | ||||
| #endif | ||||
| 	} | ||||
| 
 | ||||
| 	internal class ComponentStorage<TComponent> : ComponentStorage where TComponent : unmanaged | ||||
|  | @ -26,22 +28,12 @@ namespace MoonTools.ECS | |||
| 			return nextID > 0; | ||||
| 		} | ||||
| 
 | ||||
| 		public override bool Has(int entityID) | ||||
| 		{ | ||||
| 			return entityIDToStorageIndex.ContainsKey(entityID); | ||||
| 		} | ||||
| 
 | ||||
| 		public ref readonly TComponent Get(int entityID) | ||||
| 		{ | ||||
| 			return ref components[entityIDToStorageIndex[entityID]]; | ||||
| 		} | ||||
| 
 | ||||
| 		public override object Debug_Get(int entityID) | ||||
| 		{ | ||||
| 			return components[entityIDToStorageIndex[entityID]]; | ||||
| 		} | ||||
| 
 | ||||
| 		public ref readonly TComponent Get() | ||||
| 		public ref readonly TComponent GetFirst() | ||||
| 		{ | ||||
| #if DEBUG | ||||
| 			if (nextID == 0) | ||||
|  | @ -52,11 +44,8 @@ namespace MoonTools.ECS | |||
| 			return ref components[0]; | ||||
| 		} | ||||
| 
 | ||||
| 		// Returns true if the entity already had this component. | ||||
| 		public bool Set(int entityID, in TComponent component) | ||||
| 		public void Set(int entityID, in TComponent component) | ||||
| 		{ | ||||
| 			bool result = true; | ||||
| 
 | ||||
| 			if (!entityIDToStorageIndex.ContainsKey(entityID)) | ||||
| 			{ | ||||
| 				var index = nextID; | ||||
|  | @ -70,13 +59,9 @@ namespace MoonTools.ECS | |||
| 
 | ||||
| 				entityIDToStorageIndex[entityID] = index; | ||||
| 				entityIDs[index] = entityID; | ||||
| 
 | ||||
| 				result = false; | ||||
| 			} | ||||
| 
 | ||||
| 			components[entityIDToStorageIndex[entityID]] = component; | ||||
| 
 | ||||
| 			return result; | ||||
| 		} | ||||
| 
 | ||||
| 		// Returns true if the entity had this component. | ||||
|  | @ -166,5 +151,12 @@ namespace MoonTools.ECS | |||
| 
 | ||||
| 			nextID = state.Count; | ||||
| 		} | ||||
| 
 | ||||
| #if DEBUG | ||||
| 		public override object Debug_Get(int entityID) | ||||
| 		{ | ||||
| 			return components[entityIDToStorageIndex[entityID]]; | ||||
| 		} | ||||
| #endif | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -9,23 +9,40 @@ namespace MoonTools.ECS | |||
| { | ||||
| 	public abstract class DebugSystem : System | ||||
| 	{ | ||||
| #if DEBUG | ||||
| 		private Dictionary<Type, Filter> singleComponentFilters = new Dictionary<Type, Filter>(); | ||||
| #endif | ||||
| 
 | ||||
| 		protected DebugSystem(World world) : base(world) | ||||
| 		{ | ||||
| 		} | ||||
| 
 | ||||
| 		protected IEnumerable<object> Debug_GetAllComponents(Entity entity) | ||||
| 		{ | ||||
| 			return ComponentDepot.Debug_GetAllComponents(entity.ID); | ||||
| 			foreach (var typeIndex in EntityStorage.ComponentTypeIndices(entity.ID)) | ||||
| 			{ | ||||
| 				yield return ComponentDepot.Debug_Get(entity.ID, typeIndex); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		protected IEnumerable<Entity> Debug_GetEntities(Type componentType) | ||||
| 		{ | ||||
| 			return ComponentDepot.Debug_GetEntities(componentType); | ||||
| 			if (!singleComponentFilters.ContainsKey(componentType)) | ||||
| 			{ | ||||
| 				singleComponentFilters.Add(componentType, new Filter(FilterStorage, new HashSet<int>(ComponentTypeIndices.GetIndex(componentType)), new HashSet<int>())); | ||||
| 			} | ||||
| 			return singleComponentFilters[componentType].Entities; | ||||
| 		} | ||||
| 
 | ||||
| 		protected IEnumerable<Type> Debug_SearchComponentType(string typeString) | ||||
| 		{ | ||||
| 			return ComponentDepot.Debug_SearchComponentType(typeString); | ||||
| 			foreach (var type in ComponentTypeIndices.Types) | ||||
| 			{ | ||||
| 				if (type.ToString().ToLower().Contains(typeString.ToLower())) | ||||
| 				{ | ||||
| 					yield return type; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -9,7 +9,10 @@ namespace MoonTools.ECS | |||
| 		internal EntityStorage EntityStorage => World.EntityStorage; | ||||
| 		internal ComponentDepot ComponentDepot => World.ComponentDepot; | ||||
| 		internal RelationDepot RelationDepot => World.RelationDepot; | ||||
| 		protected FilterBuilder FilterBuilder => new FilterBuilder(ComponentDepot); | ||||
| 		protected FilterBuilder FilterBuilder => new FilterBuilder(FilterStorage, ComponentTypeIndices); | ||||
| 		internal FilterStorage FilterStorage => World.FilterStorage; | ||||
| 		internal TypeIndices ComponentTypeIndices => World.ComponentTypeIndices; | ||||
| 		internal TypeIndices RelationTypeIndices => World.RelationTypeIndices; | ||||
| 
 | ||||
| 		public EntityComponentReader(World world) | ||||
| 		{ | ||||
|  | @ -23,7 +26,8 @@ namespace MoonTools.ECS | |||
| 
 | ||||
| 		protected bool Has<TComponent>(in Entity entity) where TComponent : unmanaged | ||||
| 		{ | ||||
| 			return ComponentDepot.Has<TComponent>(entity.ID); | ||||
| 			var storageIndex = ComponentTypeIndices.GetIndex<TComponent>(); | ||||
| 			return EntityStorage.HasComponent(entity.ID, storageIndex); | ||||
| 		} | ||||
| 
 | ||||
| 		protected bool Some<TComponent>() where TComponent : unmanaged | ||||
|  | @ -38,7 +42,7 @@ namespace MoonTools.ECS | |||
| 
 | ||||
| 		protected ref readonly TComponent GetSingleton<TComponent>() where TComponent : unmanaged | ||||
| 		{ | ||||
| 			return ref ComponentDepot.Get<TComponent>(); | ||||
| 			return ref ComponentDepot.GetFirst<TComponent>(); | ||||
| 		} | ||||
| 
 | ||||
| 		protected Entity GetSingletonEntity<TComponent>() where TComponent : unmanaged | ||||
|  |  | |||
|  | @ -5,12 +5,19 @@ namespace MoonTools.ECS | |||
| 	internal class EntityStorage | ||||
| 	{ | ||||
| 		private int nextID = 0; | ||||
| 		// FIXME: why is this duplicated? | ||||
| 		private readonly Stack<int> availableIDs = new Stack<int>(); | ||||
| 		private readonly HashSet<int> availableIDHash = new HashSet<int>(); | ||||
| 
 | ||||
| 		private Dictionary<int, HashSet<int>> EntityToComponentTypeIndices = new Dictionary<int, HashSet<int>>(); | ||||
| 		private Dictionary<int, HashSet<int>> EntityToRelationTypeIndices = new Dictionary<int, HashSet<int>>(); | ||||
| 
 | ||||
| 		public Entity Create() | ||||
| 		{ | ||||
| 			return new Entity(NextID()); | ||||
| 			var entity = new Entity(NextID()); | ||||
| 			EntityToComponentTypeIndices.TryAdd(entity.ID, new HashSet<int>()); | ||||
| 			EntityToRelationTypeIndices.TryAdd(entity.ID, new HashSet<int>()); | ||||
| 			return entity; | ||||
| 		} | ||||
| 
 | ||||
| 		public bool Exists(in Entity entity) | ||||
|  | @ -20,29 +27,47 @@ namespace MoonTools.ECS | |||
| 
 | ||||
| 		public void Destroy(in Entity entity) | ||||
| 		{ | ||||
| 			EntityToComponentTypeIndices[entity.ID].Clear(); | ||||
| 			EntityToRelationTypeIndices[entity.ID].Clear(); | ||||
| 			Release(entity.ID); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Save(EntityStorageState state) | ||||
| 		// Returns true if the component is new. | ||||
| 		public bool SetComponent(int entityID, int storageIndex) | ||||
| 		{ | ||||
| 			state.NextID = nextID; | ||||
| 			state.availableIDs.Clear(); | ||||
| 			foreach (var id in availableIDs) | ||||
| 			{ | ||||
| 				state.availableIDs.Add(id); | ||||
| 			} | ||||
| 			return EntityToComponentTypeIndices[entityID].Add(storageIndex); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Load(EntityStorageState state) | ||||
| 		public bool HasComponent(int entityID, int storageIndex) | ||||
| 		{ | ||||
| 			nextID = state.NextID; | ||||
| 			availableIDs.Clear(); | ||||
| 			availableIDHash.Clear(); | ||||
| 			foreach (var id in state.availableIDs) | ||||
| 			{ | ||||
| 				availableIDs.Push(id); | ||||
| 				availableIDHash.Add(id); | ||||
| 			} | ||||
| 			return EntityToComponentTypeIndices[entityID].Contains(storageIndex); | ||||
| 		} | ||||
| 
 | ||||
| 		// Returns true if the component existed. | ||||
| 		public bool RemoveComponent(int entityID, int storageIndex) | ||||
| 		{ | ||||
| 			return EntityToComponentTypeIndices[entityID].Remove(storageIndex); | ||||
| 		} | ||||
| 
 | ||||
| 		public void AddRelation(int entityID, int relationIndex) | ||||
| 		{ | ||||
| 			EntityToRelationTypeIndices[entityID].Add(relationIndex); | ||||
| 		} | ||||
| 
 | ||||
| 		public void RemoveRelation(int entityId, int relationIndex) | ||||
| 		{ | ||||
| 			EntityToRelationTypeIndices[entityId].Remove(relationIndex); | ||||
| 		} | ||||
| 
 | ||||
| 		// TODO: should these ints be ID types? | ||||
| 		public IEnumerable<int> ComponentTypeIndices(int entityID) | ||||
| 		{ | ||||
| 			return EntityToComponentTypeIndices[entityID]; | ||||
| 		} | ||||
| 
 | ||||
| 		public IEnumerable<int> RelationTypeIndices(int entityID) | ||||
| 		{ | ||||
| 			return EntityToRelationTypeIndices[entityID]; | ||||
| 		} | ||||
| 
 | ||||
| 		private int NextID() | ||||
|  |  | |||
|  | @ -1,27 +1,26 @@ | |||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Collections.Generic; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
| 	public class Filter | ||||
| 	{ | ||||
| 		internal FilterSignature Signature; | ||||
| 		private ComponentDepot ComponentDepot; | ||||
| 		private FilterStorage FilterStorage; | ||||
| 
 | ||||
| 		internal Filter(ComponentDepot componentDepot, HashSet<Type> included, HashSet<Type> excluded) | ||||
| 		internal Filter(FilterStorage filterStorage, HashSet<int> included, HashSet<int> excluded) | ||||
| 		{ | ||||
| 			ComponentDepot = componentDepot; | ||||
| 			FilterStorage = filterStorage; | ||||
| 			Signature = new FilterSignature(included, excluded); | ||||
| 		} | ||||
| 
 | ||||
| 		public IEnumerable<Entity> Entities => ComponentDepot.FilterEntities(this); | ||||
| 		public IEnumerable<Entity> EntitiesInRandomOrder => ComponentDepot.FilterEntitiesRandom(this); | ||||
| 		public Entity RandomEntity => ComponentDepot.FilterRandomEntity(this); | ||||
| 		public IEnumerable<Entity> Entities => FilterStorage.FilterEntities(Signature); | ||||
| 		public IEnumerable<Entity> EntitiesInRandomOrder => FilterStorage.FilterEntitiesRandom(Signature); | ||||
| 		public Entity RandomEntity => FilterStorage.FilterRandomEntity(Signature); | ||||
| 
 | ||||
| 		public int Count => ComponentDepot.FilterCount(this); | ||||
| 		public int Count => FilterStorage.FilterCount(Signature); | ||||
| 		public bool Empty => Count == 0; | ||||
| 
 | ||||
| 		// WARNING: this WILL crash if the index is out of range! | ||||
| 		public Entity NthEntity(int index) => ComponentDepot.FilterNthEntity(this, index); | ||||
| 		public Entity NthEntity(int index) => FilterStorage.FilterNthEntity(Signature, index); | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -5,41 +5,42 @@ namespace MoonTools.ECS | |||
| { | ||||
| 	public struct FilterBuilder | ||||
| 	{ | ||||
| 		private ComponentDepot ComponentDepot; | ||||
| 		private HashSet<Type> Included; | ||||
| 		private HashSet<Type> Excluded; | ||||
| 		private TypeIndices ComponentTypeIndices; | ||||
| 		private FilterStorage FilterStorage; | ||||
| 		private HashSet<int> Included; | ||||
| 		private HashSet<int> Excluded; | ||||
| 
 | ||||
| 		internal FilterBuilder(ComponentDepot componentDepot) | ||||
| 		internal FilterBuilder(FilterStorage filterStorage, TypeIndices componentTypeIndices) | ||||
| 		{ | ||||
| 			ComponentDepot = componentDepot; | ||||
| 			Included = new HashSet<Type>(); | ||||
| 			Excluded = new HashSet<Type>(); | ||||
| 			FilterStorage = filterStorage; | ||||
| 			ComponentTypeIndices = componentTypeIndices; | ||||
| 			Included = new HashSet<int>(); | ||||
| 			Excluded = new HashSet<int>(); | ||||
| 		} | ||||
| 
 | ||||
| 		private FilterBuilder(ComponentDepot componentDepot, HashSet<Type> included, HashSet<Type> excluded) | ||||
| 		private FilterBuilder(FilterStorage filterStorage, TypeIndices componentTypeIndices, HashSet<int> included, HashSet<int> excluded) | ||||
| 		{ | ||||
| 			ComponentDepot = componentDepot; | ||||
| 			FilterStorage = filterStorage; | ||||
| 			ComponentTypeIndices = componentTypeIndices; | ||||
| 			Included = included; | ||||
| 			Excluded = excluded; | ||||
| 		} | ||||
| 
 | ||||
| 		public FilterBuilder Include<TComponent>() where TComponent : unmanaged | ||||
| 		{ | ||||
| 			ComponentDepot.Register<TComponent>(); | ||||
| 			Included.Add(typeof(TComponent)); | ||||
| 			return new FilterBuilder(ComponentDepot, Included, Excluded); | ||||
| 			Included.Add(ComponentTypeIndices.GetIndex<TComponent>()); | ||||
| 			return new FilterBuilder(FilterStorage, ComponentTypeIndices, Included, Excluded); | ||||
| 		} | ||||
| 
 | ||||
| 		public FilterBuilder Exclude<TComponent>() where TComponent : unmanaged | ||||
| 		{ | ||||
| 			ComponentDepot.Register<TComponent>(); | ||||
| 			Excluded.Add(typeof(TComponent)); | ||||
| 			return new FilterBuilder(ComponentDepot, Included, Excluded); | ||||
| 			Excluded.Add(ComponentTypeIndices.GetIndex<TComponent>()); | ||||
| 			return new FilterBuilder(FilterStorage, ComponentTypeIndices, Included, Excluded); | ||||
| 		} | ||||
| 
 | ||||
| 		public Filter Build() | ||||
| 		{ | ||||
| 			return ComponentDepot.CreateFilter(Included, Excluded); | ||||
| 			return FilterStorage.CreateFilter(Included, Excluded); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -5,12 +5,10 @@ namespace MoonTools.ECS | |||
| { | ||||
| 	public struct FilterSignature | ||||
| 	{ | ||||
| 		private const int HASH_FACTOR = 97; | ||||
| 		public readonly HashSet<int> Included; | ||||
| 		public readonly HashSet<int> Excluded; | ||||
| 
 | ||||
| 		public readonly HashSet<Type> Included; | ||||
| 		public readonly HashSet<Type> Excluded; | ||||
| 
 | ||||
| 		public FilterSignature(HashSet<Type> included, HashSet<Type> excluded) | ||||
| 		public FilterSignature(HashSet<int> included, HashSet<int> excluded) | ||||
| 		{ | ||||
| 			Included = included; | ||||
| 			Excluded = excluded; | ||||
|  | @ -33,19 +31,19 @@ namespace MoonTools.ECS | |||
| 
 | ||||
| 		public override int GetHashCode() | ||||
| 		{ | ||||
| 			int result = 1; | ||||
| 			var hashcode = 1; | ||||
| 
 | ||||
| 			foreach (var type in Included) | ||||
| 			{ | ||||
| 				result *= HASH_FACTOR + GuidToInt(type.GUID); | ||||
| 				hashcode = HashCode.Combine(hashcode, type); | ||||
| 			} | ||||
| 
 | ||||
| 			// FIXME: Is there a way to avoid collisions when this is the same set as included? | ||||
| 			foreach (var type in Excluded) | ||||
| 			{ | ||||
| 				result *= HASH_FACTOR + GuidToInt(type.GUID); | ||||
| 				hashcode = HashCode.Combine(hashcode, type); | ||||
| 			} | ||||
| 
 | ||||
| 			return result; | ||||
| 			return hashcode; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,131 @@ | |||
| using System; | ||||
| using System.Collections.Generic; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
| 	internal class FilterStorage | ||||
| 	{ | ||||
| 		private EntityStorage EntityStorage; | ||||
| 		private TypeIndices ComponentTypeIndices; | ||||
| 		private Dictionary<FilterSignature, IndexableSet<int>> filterSignatureToEntityIDs = new Dictionary<FilterSignature, IndexableSet<int>>(); | ||||
| 		private Dictionary<int, HashSet<FilterSignature>> typeToFilterSignatures = new Dictionary<int, HashSet<FilterSignature>>(); | ||||
| 
 | ||||
| 		public FilterStorage(EntityStorage entityStorage, TypeIndices componentTypeIndices) | ||||
| 		{ | ||||
| 			EntityStorage = entityStorage; | ||||
| 			ComponentTypeIndices = componentTypeIndices; | ||||
| 		} | ||||
| 
 | ||||
| 		public Filter CreateFilter(HashSet<int> included, HashSet<int> excluded) | ||||
| 		{ | ||||
| 			var filterSignature = new FilterSignature(included, excluded); | ||||
| 			if (!filterSignatureToEntityIDs.ContainsKey(filterSignature)) | ||||
| 			{ | ||||
| 				filterSignatureToEntityIDs.Add(filterSignature, new IndexableSet<int>()); | ||||
| 
 | ||||
| 				foreach (var type in included) | ||||
| 				{ | ||||
| 					if (!typeToFilterSignatures.ContainsKey(type)) | ||||
| 					{ | ||||
| 						typeToFilterSignatures.Add(type, new HashSet<FilterSignature>()); | ||||
| 					} | ||||
| 
 | ||||
| 					typeToFilterSignatures[type].Add(filterSignature); | ||||
| 				} | ||||
| 
 | ||||
| 				foreach (var type in excluded) | ||||
| 				{ | ||||
| 					if (!typeToFilterSignatures.ContainsKey(type)) | ||||
| 					{ | ||||
| 						typeToFilterSignatures.Add(type, new HashSet<FilterSignature>()); | ||||
| 					} | ||||
| 
 | ||||
| 					typeToFilterSignatures[type].Add(filterSignature); | ||||
| 				} | ||||
| 			} | ||||
| 			return new Filter(this, included, excluded); | ||||
| 		} | ||||
| 
 | ||||
| 		public IEnumerable<Entity> FilterEntities(FilterSignature filterSignature) | ||||
| 		{ | ||||
| 			foreach (var id in filterSignatureToEntityIDs[filterSignature]) | ||||
| 			{ | ||||
| 				yield return new Entity(id); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public IEnumerable<Entity> FilterEntitiesRandom(FilterSignature filterSignature) | ||||
| 		{ | ||||
| 			foreach (var index in RandomGenerator.LinearCongruentialGenerator(FilterCount(filterSignature))) | ||||
| 			{ | ||||
| 				yield return new Entity(filterSignatureToEntityIDs[filterSignature][index]); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public Entity FilterNthEntity(FilterSignature filterSignature, int index) | ||||
| 		{ | ||||
| 			return new Entity(filterSignatureToEntityIDs[filterSignature][index]); | ||||
| 		} | ||||
| 
 | ||||
| 		public Entity FilterRandomEntity(FilterSignature filterSignature) | ||||
| 		{ | ||||
| 			var randomIndex = RandomGenerator.Next(FilterCount(filterSignature)); | ||||
| 			return new Entity(filterSignatureToEntityIDs[filterSignature][randomIndex]); | ||||
| 		} | ||||
| 
 | ||||
| 		public int FilterCount(FilterSignature filterSignature) | ||||
| 		{ | ||||
| 			return filterSignatureToEntityIDs[filterSignature].Count; | ||||
| 		} | ||||
| 
 | ||||
| 		public void Check(int entityID, int componentTypeIndex) | ||||
| 		{ | ||||
| 			if (typeToFilterSignatures.ContainsKey(componentTypeIndex)) | ||||
| 			{ | ||||
| 				foreach (var filterSignature in typeToFilterSignatures[componentTypeIndex]) | ||||
| 				{ | ||||
| 					CheckFilter(entityID, filterSignature); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public void Check<TComponent>(int entityID) where TComponent : unmanaged | ||||
| 		{ | ||||
| 			Check(entityID, ComponentTypeIndices.GetIndex<TComponent>()); | ||||
| 		} | ||||
| 
 | ||||
| 		private void CheckFilter(int entityID, FilterSignature filterSignature) | ||||
| 		{ | ||||
| 			foreach (var type in filterSignature.Included) | ||||
| 			{ | ||||
| 				if (!EntityStorage.HasComponent(entityID, type)) | ||||
| 				{ | ||||
| 					filterSignatureToEntityIDs[filterSignature].Remove(entityID); | ||||
| 					return; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			foreach (var type in filterSignature.Excluded) | ||||
| 			{ | ||||
| 				if (EntityStorage.HasComponent(entityID, type)) | ||||
| 				{ | ||||
| 					filterSignatureToEntityIDs[filterSignature].Remove(entityID); | ||||
| 					return; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			filterSignatureToEntityIDs[filterSignature].Add(entityID); | ||||
| 		} | ||||
| 
 | ||||
| 		public void RemoveEntity(int entityID, int componentTypeIndex) | ||||
| 		{ | ||||
| 			if (typeToFilterSignatures.ContainsKey(componentTypeIndex)) | ||||
| 			{ | ||||
| 				foreach (var filterSignature in typeToFilterSignatures[componentTypeIndex]) | ||||
| 				{ | ||||
| 					filterSignatureToEntityIDs[filterSignature].Remove(entityID); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -6,21 +6,34 @@ namespace MoonTools.ECS | |||
| { | ||||
| 	internal class RelationDepot | ||||
| 	{ | ||||
| 		private Dictionary<Type, RelationStorage> storages = new Dictionary<Type, RelationStorage>(); | ||||
| 		private TypeIndices RelationTypeIndices; | ||||
| 		private RelationStorage[] storages = new RelationStorage[256]; | ||||
| 
 | ||||
| 		private void Register<TRelationKind>() where TRelationKind : unmanaged | ||||
| 		public RelationDepot(TypeIndices relationTypeIndices) | ||||
| 		{ | ||||
| 			if (!storages.ContainsKey(typeof(TRelationKind))) | ||||
| 			RelationTypeIndices = relationTypeIndices; | ||||
| 		} | ||||
| 
 | ||||
| 		private void Register<TRelationKind>(int index) where TRelationKind : unmanaged | ||||
| 		{ | ||||
| 			if (index >= storages.Length) | ||||
| 			{ | ||||
| 				storages.Add(typeof(TRelationKind), new RelationStorage<TRelationKind>()); | ||||
| 				Array.Resize(ref storages, storages.Length * 2); | ||||
| 			} | ||||
| 
 | ||||
| 			storages[index] = new RelationStorage<TRelationKind>(); | ||||
| 		} | ||||
| 
 | ||||
| 		[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| 		private RelationStorage<TRelationKind> Lookup<TRelationKind>() where TRelationKind : unmanaged | ||||
| 		{ | ||||
| 			Register<TRelationKind>(); | ||||
| 			return (RelationStorage<TRelationKind>) storages[typeof(TRelationKind)]; | ||||
| 			var storageIndex = RelationTypeIndices.GetIndex<TRelationKind>(); | ||||
| 			// TODO: is there some way to avoid this null check? | ||||
| 			if (storages[storageIndex] == null) | ||||
| 			{ | ||||
| 				Register<TRelationKind>(storageIndex); | ||||
| 			} | ||||
| 			return (RelationStorage<TRelationKind>) storages[storageIndex]; | ||||
| 		} | ||||
| 
 | ||||
| 		public void Set<TRelationKind>(Relation relation, TRelationKind relationData) where TRelationKind : unmanaged | ||||
|  | @ -28,9 +41,9 @@ namespace MoonTools.ECS | |||
| 			Lookup<TRelationKind>().Set(relation, relationData); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Remove<TRelationKind>(Relation relation) where TRelationKind : unmanaged | ||||
| 		public (bool, bool) Remove<TRelationKind>(Relation relation) where TRelationKind : unmanaged | ||||
| 		{ | ||||
| 			Lookup<TRelationKind>().Remove(relation); | ||||
| 			return Lookup<TRelationKind>().Remove(relation); | ||||
| 		} | ||||
| 
 | ||||
| 		public void UnrelateAll<TRelationKind>(int entityID) where TRelationKind : unmanaged | ||||
|  | @ -38,13 +51,9 @@ namespace MoonTools.ECS | |||
| 			Lookup<TRelationKind>().UnrelateAll(entityID); | ||||
| 		} | ||||
| 
 | ||||
| 		// FIXME: optimize this | ||||
| 		public void OnEntityDestroy(int entityID) | ||||
| 		public void UnrelateAll(int entityID, int relationStorageIndex) | ||||
| 		{ | ||||
| 			foreach (var storage in storages.Values) | ||||
| 			{ | ||||
| 				storage.OnEntityDestroy(entityID); | ||||
| 			} | ||||
| 			storages[relationStorageIndex].UnrelateAll(entityID); | ||||
| 		} | ||||
| 
 | ||||
| 		public IEnumerable<(Entity, Entity, TRelationKind)> Relations<TRelationKind>() where TRelationKind : unmanaged | ||||
|  | @ -96,26 +105,5 @@ namespace MoonTools.ECS | |||
| 		{ | ||||
| 			return Lookup<TRelationKind>().InRelationCount(entityID); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Save(RelationDepotState state) | ||||
| 		{ | ||||
| 			foreach (var (type, storage) in storages) | ||||
| 			{ | ||||
| 				if (!state.StorageStates.ContainsKey(type)) | ||||
| 				{ | ||||
| 					state.StorageStates.Add(type, storage.CreateState()); | ||||
| 				} | ||||
| 
 | ||||
| 				storage.Save(state.StorageStates[type]); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public void Load(RelationDepotState state) | ||||
| 		{ | ||||
| 			foreach (var (type, storageState) in state.StorageStates) | ||||
| 			{ | ||||
| 				storages[type].Load(storageState); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -9,7 +9,7 @@ namespace MoonTools.ECS | |||
| 		public abstract RelationStorageState CreateState(); | ||||
| 		public abstract void Save(RelationStorageState state); | ||||
| 		public abstract void Load(RelationStorageState state); | ||||
| 		public abstract void OnEntityDestroy(int entityID); | ||||
| 		public abstract void UnrelateAll(int entityID); | ||||
| 	} | ||||
| 
 | ||||
| 	// Relation is the two entities, A related to B. | ||||
|  | @ -144,16 +144,27 @@ namespace MoonTools.ECS | |||
| 			return inRelations.ContainsKey(entityID) ? inRelations[entityID].Count : 0; | ||||
| 		} | ||||
| 
 | ||||
| 		public bool Remove(Relation relation) | ||||
| 		public (bool, bool) Remove(Relation relation) | ||||
| 		{ | ||||
| 			var aEmpty = false; | ||||
| 			var bEmpty = false; | ||||
| 
 | ||||
| 			if (outRelations.ContainsKey(relation.A.ID)) | ||||
| 			{ | ||||
| 				outRelations[relation.A.ID].Remove(relation.B.ID); | ||||
| 				if (outRelations[relation.A.ID].Count == 0) | ||||
| 				{ | ||||
| 					aEmpty = true; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			if (inRelations.ContainsKey(relation.B.ID)) | ||||
| 			{ | ||||
| 				inRelations[relation.B.ID].Remove(relation.A.ID); | ||||
| 				if (inRelations[relation.B.ID].Count == 0) | ||||
| 				{ | ||||
| 					bEmpty = true; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			if (indices.ContainsKey(relation)) | ||||
|  | @ -172,13 +183,12 @@ namespace MoonTools.ECS | |||
| 
 | ||||
| 				count -= 1; | ||||
| 				indices.Remove(relation); | ||||
| 				return true; | ||||
| 			} | ||||
| 
 | ||||
| 			return false; | ||||
| 			return (aEmpty, bEmpty); | ||||
| 		} | ||||
| 
 | ||||
| 		public void UnrelateAll(int entityID) | ||||
| 		public override void UnrelateAll(int entityID) | ||||
| 		{ | ||||
| 			if (outRelations.ContainsKey(entityID)) | ||||
| 			{ | ||||
|  | @ -203,11 +213,6 @@ namespace MoonTools.ECS | |||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public override void OnEntityDestroy(int entityID) | ||||
| 		{ | ||||
| 			UnrelateAll(entityID); | ||||
| 		} | ||||
| 
 | ||||
| 		private IndexableSet<int> AcquireHashSetFromPool() | ||||
| 		{ | ||||
| 			if (listPool.Count == 0) | ||||
|  |  | |||
|  | @ -6,5 +6,8 @@ namespace MoonTools.ECS | |||
|     { | ||||
| 		public int NextID; | ||||
| 		public List<int> availableIDs = new List<int>(); | ||||
| 
 | ||||
| 		public Dictionary<int, HashSet<int>> EntityToComponentTypeIndices = new Dictionary<int, HashSet<int>>(); | ||||
| 		public Dictionary<int, HashSet<int>> EntityToRelationTypeIndices = new Dictionary<int, HashSet<int>>(); | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -25,12 +25,21 @@ namespace MoonTools.ECS | |||
| 				throw new ArgumentException("This entity is not valid!"); | ||||
| 			} | ||||
| #endif | ||||
| 			if (EntityStorage.SetComponent(entity.ID, ComponentTypeIndices.GetIndex<TComponent>())) | ||||
| 			{ | ||||
| 				FilterStorage.Check<TComponent>(entity.ID); | ||||
| 			} | ||||
| 
 | ||||
| 			ComponentDepot.Set<TComponent>(entity.ID, component); | ||||
| 		} | ||||
| 
 | ||||
| 		protected void Remove<TComponent>(in Entity entity) where TComponent : unmanaged | ||||
| 		{ | ||||
| 			ComponentDepot.Remove<TComponent>(entity.ID); | ||||
| 			if (EntityStorage.RemoveComponent(entity.ID, ComponentTypeIndices.GetIndex<TComponent>())) | ||||
| 			{ | ||||
| 				ComponentDepot.Remove<TComponent>(entity.ID); | ||||
| 				FilterStorage.Check<TComponent>(entity.ID); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		protected ReadOnlySpan<TMessage> ReadMessages<TMessage>() where TMessage : unmanaged | ||||
|  | @ -71,23 +80,45 @@ namespace MoonTools.ECS | |||
| 		protected void Relate<TRelationKind>(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged | ||||
| 		{ | ||||
| 			RelationDepot.Set<TRelationKind>(new Relation(entityA, entityB), relationData); | ||||
| 			var relationTypeIndex = RelationTypeIndices.GetIndex<TRelationKind>(); | ||||
| 			EntityStorage.AddRelation(entityA.ID, relationTypeIndex); | ||||
| 			EntityStorage.AddRelation(entityB.ID, relationTypeIndex); | ||||
| 		} | ||||
| 
 | ||||
| 		protected void Unrelate<TRelationKind>(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged | ||||
| 		{ | ||||
| 			RelationDepot.Remove<TRelationKind>(new Relation(entityA, entityB)); | ||||
| 			var (aEmpty, bEmpty) = RelationDepot.Remove<TRelationKind>(new Relation(entityA, entityB)); | ||||
| 
 | ||||
| 			if (aEmpty) | ||||
| 			{ | ||||
| 				EntityStorage.RemoveRelation(entityA.ID, RelationTypeIndices.GetIndex<TRelationKind>()); | ||||
| 			} | ||||
| 
 | ||||
| 			if (bEmpty) | ||||
| 			{ | ||||
| 				EntityStorage.RemoveRelation(entityB.ID, RelationTypeIndices.GetIndex<TRelationKind>()); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		protected void UnrelateAll<TRelationKind>(in Entity entity) where TRelationKind : unmanaged | ||||
| 		{ | ||||
| 			RelationDepot.UnrelateAll<TRelationKind>(entity.ID); | ||||
| 			EntityStorage.RemoveRelation(entity.ID, RelationTypeIndices.GetIndex<TRelationKind>()); | ||||
| 		} | ||||
| 
 | ||||
| 		// FIXME: this is insanely inefficient | ||||
| 		protected void Destroy(in Entity entity) | ||||
| 		{ | ||||
| 			ComponentDepot.OnEntityDestroy(entity.ID); | ||||
| 			RelationDepot.OnEntityDestroy(entity.ID); | ||||
| 			foreach (var componentTypeIndex in EntityStorage.ComponentTypeIndices(entity.ID)) | ||||
| 			{ | ||||
| 				ComponentDepot.Remove(entity.ID, componentTypeIndex); | ||||
| 				FilterStorage.RemoveEntity(entity.ID, componentTypeIndex); | ||||
| 			} | ||||
| 
 | ||||
| 			foreach (var relationTypeIndex in EntityStorage.RelationTypeIndices(entity.ID)) | ||||
| 			{ | ||||
| 				RelationDepot.UnrelateAll(entity.ID, relationTypeIndex); | ||||
| 			} | ||||
| 
 | ||||
| 			EntityStorage.Destroy(entity); | ||||
| 		} | ||||
| 	} | ||||
|  |  | |||
|  | @ -0,0 +1,12 @@ | |||
| namespace MoonTools.ECS | ||||
| { | ||||
| 	public struct Template | ||||
| 	{ | ||||
| 		public int ID { get; } | ||||
| 
 | ||||
| 		internal Template(int id) | ||||
| 		{ | ||||
| 			ID = id; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,19 @@ | |||
| namespace MoonTools.ECS | ||||
| { | ||||
| 	public class TemplateStorage | ||||
| 	{ | ||||
| 		private int nextID = 0; | ||||
| 
 | ||||
| 		public Template Create() | ||||
| 		{ | ||||
| 			return new Template(NextID()); | ||||
| 		} | ||||
| 
 | ||||
| 		private int NextID() | ||||
| 		{ | ||||
| 			var id = nextID; | ||||
| 			nextID += 1; | ||||
| 			return id; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,31 @@ | |||
| using System; | ||||
| using System.Collections.Generic; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
| 	public class TypeIndices | ||||
| 	{ | ||||
| 		Dictionary<Type, int> TypeToIndex = new Dictionary<Type, int>(); | ||||
| 		int nextID = 0; | ||||
| 
 | ||||
| 		public int GetIndex<T>() where T : unmanaged | ||||
| 		{ | ||||
| 			if (!TypeToIndex.ContainsKey(typeof(T))) | ||||
| 			{ | ||||
| 				TypeToIndex.Add(typeof(T), nextID); | ||||
| 				nextID += 1; | ||||
| 			} | ||||
| 
 | ||||
| 			return TypeToIndex[typeof(T)]; | ||||
| 		} | ||||
| 
 | ||||
| 		public int GetIndex(Type type) | ||||
| 		{ | ||||
| 			return TypeToIndex[type]; | ||||
| 		} | ||||
| 
 | ||||
| #if DEBUG | ||||
| 		public IEnumerable<Type> Types => TypeToIndex.Keys; | ||||
| #endif | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										64
									
								
								src/World.cs
								
								
								
								
							
							
						
						
									
										64
									
								
								src/World.cs
								
								
								
								
							|  | @ -2,10 +2,25 @@ | |||
| { | ||||
| 	public class World | ||||
| 	{ | ||||
| 		internal readonly TypeIndices ComponentTypeIndices = new TypeIndices(); | ||||
| 		internal readonly TypeIndices RelationTypeIndices = new TypeIndices(); | ||||
| 		internal readonly EntityStorage EntityStorage = new EntityStorage(); | ||||
| 		internal readonly ComponentDepot ComponentDepot = new ComponentDepot(); | ||||
| 		internal readonly ComponentDepot ComponentDepot; | ||||
| 		internal readonly MessageDepot MessageDepot = new MessageDepot(); | ||||
| 		internal readonly RelationDepot RelationDepot = new RelationDepot(); | ||||
| 		internal readonly RelationDepot RelationDepot; | ||||
| 		internal readonly FilterStorage FilterStorage; | ||||
| 
 | ||||
| 		/* | ||||
| 		internal readonly TemplateStorage TemplateStorage = new TemplateStorage(); | ||||
| 		internal readonly ComponentDepot TemplateComponentDepot = new ComponentDepot(); | ||||
| 		*/ | ||||
| 
 | ||||
| 		public World() | ||||
| 		{ | ||||
| 			ComponentDepot = new ComponentDepot(ComponentTypeIndices); | ||||
| 			RelationDepot = new RelationDepot(RelationTypeIndices); | ||||
| 			FilterStorage = new FilterStorage(EntityStorage, ComponentTypeIndices); | ||||
| 		} | ||||
| 
 | ||||
| 		public Entity CreateEntity() | ||||
| 		{ | ||||
|  | @ -14,7 +29,31 @@ | |||
| 
 | ||||
| 		public void Set<TComponent>(Entity entity, in TComponent component) where TComponent : unmanaged | ||||
| 		{ | ||||
| 			ComponentDepot.Set(entity.ID, component); | ||||
| 			if (EntityStorage.SetComponent(entity.ID, ComponentTypeIndices.GetIndex<TComponent>())) | ||||
| 			{ | ||||
| 				FilterStorage.Check<TComponent>(entity.ID); | ||||
| 			} | ||||
| 
 | ||||
| 			ComponentDepot.Set<TComponent>(entity.ID, component); | ||||
| 		} | ||||
| 
 | ||||
| 		/* | ||||
| 		public Template CreateTemplate() | ||||
| 		{ | ||||
| 			return TemplateStorage.Create(); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Set<TComponent>(Template template, in TComponent component) where TComponent : unmanaged | ||||
| 		{ | ||||
| 			TemplateComponentDepot.Set(template.ID, component); | ||||
| 		} | ||||
| 		*/ | ||||
| 
 | ||||
| 		public Entity Instantiate(Template template) | ||||
| 		{ | ||||
| 			var entity = EntityStorage.Create(); | ||||
| 
 | ||||
| 			return entity; | ||||
| 		} | ||||
| 
 | ||||
| 		public void Send<TMessage>(in TMessage message) where TMessage : unmanaged | ||||
|  | @ -27,28 +66,9 @@ | |||
| 			MessageDepot.Clear(); | ||||
| 		} | ||||
| 
 | ||||
| 		public void DisableSerialization<TComponent>() where TComponent : unmanaged | ||||
| 		{ | ||||
| 			ComponentDepot.DisableSerialization<TComponent>(); | ||||
| 		} | ||||
| 
 | ||||
| 		public WorldState CreateState() | ||||
| 		{ | ||||
| 			return new WorldState(); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Save(WorldState state) | ||||
| 		{ | ||||
| 			ComponentDepot.Save(state.ComponentDepotState); | ||||
| 			EntityStorage.Save(state.EntityStorageState); | ||||
| 			RelationDepot.Save(state.RelationDepotState); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Load(WorldState state) | ||||
| 		{ | ||||
| 			ComponentDepot.Load(state.ComponentDepotState); | ||||
| 			EntityStorage.Load(state.EntityStorageState); | ||||
| 			RelationDepot.Load(state.RelationDepotState); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue