Storage refactor, snapshot system, experimental template system (#3)
- Major storage refactor to improve performance and reduce garbage collection - Snapshot system to facilitate rollback implementation - Experimental template system to instantiate entities based on a template. Use at your own risk, this may change significantly or be removed! Reviewed-on: #3pull/4/head
							parent
							
								
									71a95cb2d7
								
							
						
					
					
						commit
						f69d132a5e
					
				|  | @ -6,41 +6,36 @@ 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; | ||||
| 		} | ||||
| 
 | ||||
| 		private ComponentStorage Lookup(Type type) | ||||
| 		[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| 		internal void Register<TComponent>(int index) where TComponent : unmanaged | ||||
| 		{ | ||||
| 			return storages[type]; | ||||
| 			if (index >= storages.Length) | ||||
| 			{ | ||||
| 				Array.Resize(ref storages, storages.Length * 2); | ||||
| 			} | ||||
| 
 | ||||
| 			storages[index] = new ComponentStorage<TComponent>(); | ||||
| 		} | ||||
| 
 | ||||
| 		[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 +43,19 @@ 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 | ||||
| 		public ref readonly TComponent GetFirst<TComponent>() where TComponent : unmanaged | ||||
| 		{ | ||||
| 			return ref Lookup<TComponent>().Get(); | ||||
| 			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,208 +68,61 @@ 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); | ||||
| 			Lookup<TComponent>().Remove(entityID); | ||||
| 		} | ||||
| 
 | ||||
| 			// update filters | ||||
| 			if (existed) | ||||
| 		public void Clear() | ||||
| 		{ | ||||
| 			for (var i = 0; i < ComponentTypeIndices.Count; i += 1) | ||||
| 			{ | ||||
| 				if (typeToFilterSignatures.TryGetValue(typeof(TComponent), out var filterSignatures)) | ||||
| 				if (storages[i] != null) | ||||
| 				{ | ||||
| 					foreach (var filterSignature in filterSignatures) | ||||
| 					{ | ||||
| 						CheckFilter(filterSignature, entityID); | ||||
| 					} | ||||
| 					storages[i].Clear(); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		// TODO: is there some way to optimize this without complicating serialization? | ||||
| 		public void OnEntityDestroy(int entityID) | ||||
| 		// these methods used to implement snapshots, templates, and debugging | ||||
| 
 | ||||
| 		internal unsafe void* UntypedGet(int entityID, int componentTypeIndex) | ||||
| 		{ | ||||
| 			foreach (var type in storages.Keys) | ||||
| 			{ | ||||
| 				Remove(type, entityID); | ||||
| 			} | ||||
| 			return storages[componentTypeIndex].UntypedGet(entityID); | ||||
| 		} | ||||
| 
 | ||||
| 		public Filter CreateFilter(HashSet<Type> included, HashSet<Type> excluded) | ||||
| 		internal unsafe void Set(int entityID, int componentTypeIndex, void* component) | ||||
| 		{ | ||||
| 			var filterSignature = new FilterSignature(included, excluded); | ||||
| 			if (!filterSignatureToEntityIDs.ContainsKey(filterSignature)) | ||||
| 			{ | ||||
| 				filterSignatureToEntityIDs.Add(filterSignature, new IndexableSet<int>()); | ||||
| 			storages[componentTypeIndex].Set(entityID, component); | ||||
| 		} | ||||
| 
 | ||||
| 				foreach (var type in included) | ||||
| 		public void CreateMissingStorages(ComponentDepot other) | ||||
| 		{ | ||||
| 			for (var i = 0; i < ComponentTypeIndices.Count; i += 1) | ||||
| 			{ | ||||
| 				if (storages[i] == null && other.storages[i] != null) | ||||
| 				{ | ||||
| 					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]); | ||||
| 					storages[i] = other.storages[i].CreateStorage(); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		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); | ||||
| 			} | ||||
| 		} | ||||
| 		// this method is used to iterate components of an entity, only for use with a debug inspector | ||||
| 
 | ||||
| #if DEBUG | ||||
| 		public IEnumerable<object> Debug_GetAllComponents(int entityID) | ||||
| 		public object Debug_Get(int entityID, int componentTypeIndex) | ||||
| 		{ | ||||
| 			foreach (var (type, storage) in storages) | ||||
| 			{ | ||||
| 				if (storage.Has(entityID)) | ||||
| 				{ | ||||
| 					yield return storage.Debug_Get(entityID); | ||||
| 				} | ||||
| 			} | ||||
| 			return storages[componentTypeIndex].Debug_Get(entityID); | ||||
| 		} | ||||
| 
 | ||||
| 		public IEnumerable<Entity> Debug_GetEntities(Type componentType) | ||||
| 		public IEnumerable<int> Debug_GetEntityIDs(int componentTypeIndex) | ||||
| 		{ | ||||
| 			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; | ||||
| 				} | ||||
| 			} | ||||
| 			return storages[componentTypeIndex].Debug_GetEntityIDs(); | ||||
| 		} | ||||
| #endif | ||||
| 	} | ||||
|  |  | |||
|  | @ -1,17 +1,22 @@ | |||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Runtime.InteropServices; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
| 	internal abstract class ComponentStorage | ||||
| 	{ | ||||
| 		public abstract bool Has(int entityID); | ||||
| 		internal abstract unsafe void Set(int entityID, void* component); | ||||
| 		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); | ||||
| 		public abstract void Clear(); | ||||
| 
 | ||||
| 		// used for debugging and template instantiation | ||||
| 		internal abstract unsafe void* UntypedGet(int entityID); | ||||
| 		// used to create correctly typed storage on snapshot | ||||
| 		public abstract ComponentStorage CreateStorage(); | ||||
| #if DEBUG | ||||
| 		internal abstract object Debug_Get(int entityID); | ||||
| 		internal abstract IEnumerable<int> Debug_GetEntityIDs(); | ||||
| #endif | ||||
| 	} | ||||
| 
 | ||||
| 	internal class ComponentStorage<TComponent> : ComponentStorage where TComponent : unmanaged | ||||
|  | @ -26,22 +31,20 @@ 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) | ||||
| 		internal override unsafe void* UntypedGet(int entityID) | ||||
| 		{ | ||||
| 			return components[entityIDToStorageIndex[entityID]]; | ||||
| 			fixed (void* p = &components[entityIDToStorageIndex[entityID]]) | ||||
| 			{ | ||||
| 				return p; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public ref readonly TComponent Get() | ||||
| 		public ref readonly TComponent GetFirst() | ||||
| 		{ | ||||
| #if DEBUG | ||||
| 			if (nextID == 0) | ||||
|  | @ -52,11 +55,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,21 +70,21 @@ namespace MoonTools.ECS | |||
| 
 | ||||
| 				entityIDToStorageIndex[entityID] = index; | ||||
| 				entityIDs[index] = entityID; | ||||
| 
 | ||||
| 				result = false; | ||||
| 			} | ||||
| 
 | ||||
| 			components[entityIDToStorageIndex[entityID]] = component; | ||||
| 		} | ||||
| 
 | ||||
| 			return result; | ||||
| 		internal override unsafe void Set(int entityID, void* component) | ||||
| 		{ | ||||
| 			Set(entityID, *((TComponent*) component)); | ||||
| 		} | ||||
| 
 | ||||
| 		// Returns true if the entity had this component. | ||||
| 		public override bool Remove(int entityID) | ||||
| 		{ | ||||
| 			if (entityIDToStorageIndex.ContainsKey(entityID)) | ||||
| 			if (entityIDToStorageIndex.TryGetValue(entityID, out int storageIndex)) | ||||
| 			{ | ||||
| 				var storageIndex = entityIDToStorageIndex[entityID]; | ||||
| 				entityIDToStorageIndex.Remove(entityID); | ||||
| 
 | ||||
| 				var lastElementIndex = nextID - 1; | ||||
|  | @ -106,7 +106,7 @@ namespace MoonTools.ECS | |||
| 			return false; | ||||
| 		} | ||||
| 
 | ||||
| 		public void Clear() | ||||
| 		public override void Clear() | ||||
| 		{ | ||||
| 			nextID = 0; | ||||
| 			entityIDToStorageIndex.Clear(); | ||||
|  | @ -128,43 +128,21 @@ namespace MoonTools.ECS | |||
| 			return new Entity(entityIDs[0]); | ||||
| 		} | ||||
| 
 | ||||
| 		public override ComponentStorageState CreateState() | ||||
| 		public override ComponentStorage<TComponent> CreateStorage() | ||||
| 		{ | ||||
| 			return ComponentStorageState.Create<TComponent>(nextID); | ||||
| 			return new ComponentStorage<TComponent>(); | ||||
| 		} | ||||
| 
 | ||||
| 		public override void Save(ComponentStorageState state) | ||||
| #if DEBUG | ||||
| 		internal override object Debug_Get(int entityID) | ||||
| 		{ | ||||
| 			ReadOnlySpan<byte> entityIDBytes = MemoryMarshal.Cast<int, byte>(new ReadOnlySpan<int>(entityIDs, 0, nextID)); | ||||
| 
 | ||||
| 			if (entityIDBytes.Length > state.EntityIDs.Length) | ||||
| 			{ | ||||
| 				Array.Resize(ref state.EntityIDs, entityIDBytes.Length); | ||||
| 			} | ||||
| 			entityIDBytes.CopyTo(state.EntityIDs); | ||||
| 
 | ||||
| 			ReadOnlySpan<byte> componentBytes = MemoryMarshal.Cast<TComponent, byte>(AllComponents()); | ||||
| 			if (componentBytes.Length > state.Components.Length) | ||||
| 			{ | ||||
| 				Array.Resize(ref state.Components, componentBytes.Length); | ||||
| 			} | ||||
| 			componentBytes.CopyTo(state.Components); | ||||
| 
 | ||||
| 			state.Count = nextID; | ||||
| 			return components[entityIDToStorageIndex[entityID]]; | ||||
| 		} | ||||
| 
 | ||||
| 		public override void Load(ComponentStorageState state) | ||||
| 		internal override IEnumerable<int> Debug_GetEntityIDs() | ||||
| 		{ | ||||
| 			state.EntityIDs.CopyTo(MemoryMarshal.Cast<int, byte>(entityIDs)); | ||||
| 			state.Components.CopyTo(MemoryMarshal.Cast<TComponent, byte>(components)); | ||||
| 
 | ||||
| 			entityIDToStorageIndex.Clear(); | ||||
| 			for (var i = 0; i < state.Count; i += 1) | ||||
| 			{ | ||||
| 				entityIDToStorageIndex[entityIDs[i]] = i; | ||||
| 			} | ||||
| 
 | ||||
| 			nextID = state.Count; | ||||
| 			return entityIDToStorageIndex.Keys; | ||||
| 		} | ||||
| #endif | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -13,19 +13,31 @@ namespace MoonTools.ECS | |||
| 		{ | ||||
| 		} | ||||
| 
 | ||||
| 		protected IEnumerable<object> Debug_GetAllComponents(Entity entity) | ||||
| 		protected IEnumerable<dynamic> 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); | ||||
| 			foreach (var entityID in ComponentDepot.Debug_GetEntityIDs(ComponentTypeIndices.GetIndex(componentType))) | ||||
| 			{ | ||||
| 				yield return new Entity(entityID); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		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; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,40 @@ | |||
| using System; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
| 	public class DynamicArray<T> where T : unmanaged | ||||
| 	{ | ||||
| 		private T[] Array; | ||||
| 		public int Count { get; private set; } | ||||
| 
 | ||||
| 		public Span<T> ToSpan() => new Span<T>(Array, 0, Count); | ||||
| 		public ReverseSpanEnumerator<T> GetEnumerator() => new ReverseSpanEnumerator<T>(new Span<T>(Array, 0, Count)); | ||||
| 
 | ||||
| 		public DynamicArray(int capacity = 16) | ||||
| 		{ | ||||
| 			Array = new T[capacity]; | ||||
| 			Count = 0; | ||||
| 		} | ||||
| 
 | ||||
| 		public ref T this[int i] | ||||
| 		{ | ||||
| 			get { return ref Array[i]; } | ||||
| 		} | ||||
| 
 | ||||
| 		public void Add(T item) | ||||
| 		{ | ||||
| 			if (Count >= Array.Length) | ||||
| 			{ | ||||
| 				global::System.Array.Resize(ref Array, Array.Length * 2); | ||||
| 			} | ||||
| 
 | ||||
| 			Array[Count] = item; | ||||
| 			Count += 1; | ||||
| 		} | ||||
| 
 | ||||
| 		public void Clear() | ||||
| 		{ | ||||
| 			Count = 0; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -35,5 +35,15 @@ namespace MoonTools.ECS | |||
| 		{ | ||||
| 			return !a.Equals(b); | ||||
| 		} | ||||
| 
 | ||||
| 		public static implicit operator int(Entity e) | ||||
| 		{ | ||||
| 			return e.ID; | ||||
| 		} | ||||
| 
 | ||||
| 		public static implicit operator Entity(int i) | ||||
| 		{ | ||||
| 			return new Entity(i); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -9,7 +9,12 @@ 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; | ||||
| 		internal TemplateStorage TemplateStorage => World.TemplateStorage; | ||||
| 		internal ComponentDepot TemplateComponentDepot => World.TemplateComponentDepot; | ||||
| 
 | ||||
| 		public EntityComponentReader(World world) | ||||
| 		{ | ||||
|  | @ -23,7 +28,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 +44,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 | ||||
|  | @ -46,12 +52,7 @@ namespace MoonTools.ECS | |||
| 			return ComponentDepot.GetSingletonEntity<TComponent>(); | ||||
| 		} | ||||
| 
 | ||||
| 		protected bool Exists(in Entity entity) | ||||
| 		{ | ||||
| 			return EntityStorage.Exists(entity); | ||||
| 		} | ||||
| 
 | ||||
| 		protected IEnumerable<(Entity, Entity, TRelationKind)> Relations<TRelationKind>() where TRelationKind : unmanaged | ||||
| 		protected ReverseSpanEnumerator<(Entity, Entity)> Relations<TRelationKind>() where TRelationKind : unmanaged | ||||
| 		{ | ||||
| 			return RelationDepot.Relations<TRelationKind>(); | ||||
| 		} | ||||
|  | @ -61,13 +62,18 @@ namespace MoonTools.ECS | |||
| 			return RelationDepot.Related<TRelationKind>(a.ID, b.ID); | ||||
| 		} | ||||
| 
 | ||||
| 		// relations go A->B, so given A, will give all outgoing B relations. | ||||
| 		protected IEnumerable<(Entity, TRelationKind)> OutRelations<TRelationKind>(in Entity entity) where TRelationKind : unmanaged | ||||
| 		protected TRelationKind GetRelationData<TRelationKind>(in Entity a, in Entity b) where TRelationKind : unmanaged | ||||
| 		{ | ||||
| 			return RelationDepot.Get<TRelationKind>(a, b); | ||||
| 		} | ||||
| 
 | ||||
| 		// relations go A->B, so given A, will give all entities in outgoing relations of this kind. | ||||
| 		protected ReverseSpanEnumerator<Entity> OutRelations<TRelationKind>(in Entity entity) where TRelationKind : unmanaged | ||||
| 		{ | ||||
| 			return RelationDepot.OutRelations<TRelationKind>(entity.ID); | ||||
| 		} | ||||
| 
 | ||||
| 		protected (Entity, TRelationKind) OutRelationSingleton<TRelationKind>(in Entity entity) where TRelationKind : unmanaged | ||||
| 		protected Entity OutRelationSingleton<TRelationKind>(in Entity entity) where TRelationKind : unmanaged | ||||
| 		{ | ||||
| 			return RelationDepot.OutRelationSingleton<TRelationKind>(entity.ID); | ||||
| 		} | ||||
|  | @ -82,13 +88,13 @@ namespace MoonTools.ECS | |||
| 			return RelationDepot.OutRelationCount<TRelationKind>(entity.ID); | ||||
| 		} | ||||
| 
 | ||||
| 		// Relations go A->B, so given B, will give all incoming A relations. | ||||
| 		protected IEnumerable<(Entity, TRelationKind)> InRelations<TRelationKind>(in Entity entity) where TRelationKind : unmanaged | ||||
| 		// Relations go A->B, so given B, will give all entities in incoming A relations of this kind. | ||||
| 		protected ReverseSpanEnumerator<Entity> InRelations<TRelationKind>(in Entity entity) where TRelationKind : unmanaged | ||||
| 		{ | ||||
| 			return RelationDepot.InRelations<TRelationKind>(entity.ID); | ||||
| 		} | ||||
| 
 | ||||
| 		protected (Entity, TRelationKind) InRelationSingleton<TRelationKind>(in Entity entity) where TRelationKind : unmanaged | ||||
| 		protected Entity InRelationSingleton<TRelationKind>(in Entity entity) where TRelationKind : unmanaged | ||||
| 		{ | ||||
| 			return RelationDepot.InRelationSingleton<TRelationKind>(entity.ID); | ||||
| 		} | ||||
|  |  | |||
|  | @ -5,12 +5,22 @@ namespace MoonTools.ECS | |||
| 	internal class EntityStorage | ||||
| 	{ | ||||
| 		private int nextID = 0; | ||||
| 		// FIXME: why is this duplicated? | ||||
| 		private readonly Stack<int> availableIDs = new Stack<int>(); | ||||
| 		// FIXME: this is only needed in debug mode | ||||
| 		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 int Count => nextID - availableIDs.Count; | ||||
| 
 | ||||
| 		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 +30,61 @@ 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 componentTypeIndex) | ||||
| 		{ | ||||
| 			state.NextID = nextID; | ||||
| 			state.availableIDs.Clear(); | ||||
| 			foreach (var id in availableIDs) | ||||
| 			{ | ||||
| 				state.availableIDs.Add(id); | ||||
| 			} | ||||
| 			return EntityToComponentTypeIndices[entityID].Add(componentTypeIndex); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Load(EntityStorageState state) | ||||
| 		public bool HasComponent(int entityID, int componentTypeIndex) | ||||
| 		{ | ||||
| 			nextID = state.NextID; | ||||
| 			return EntityToComponentTypeIndices[entityID].Contains(componentTypeIndex); | ||||
| 		} | ||||
| 
 | ||||
| 		// Returns true if the component existed. | ||||
| 		public bool RemoveComponent(int entityID, int componentTypeIndex) | ||||
| 		{ | ||||
| 			return EntityToComponentTypeIndices[entityID].Remove(componentTypeIndex); | ||||
| 		} | ||||
| 
 | ||||
| 		public void AddRelationKind(int entityID, int relationIndex) | ||||
| 		{ | ||||
| 			EntityToRelationTypeIndices[entityID].Add(relationIndex); | ||||
| 		} | ||||
| 
 | ||||
| 		public void RemoveRelation(int entityId, int relationIndex) | ||||
| 		{ | ||||
| 			EntityToRelationTypeIndices[entityId].Remove(relationIndex); | ||||
| 		} | ||||
| 
 | ||||
| 		public HashSet<int> ComponentTypeIndices(int entityID) | ||||
| 		{ | ||||
| 			return EntityToComponentTypeIndices[entityID]; | ||||
| 		} | ||||
| 
 | ||||
| 		public HashSet<int> RelationTypeIndices(int entityID) | ||||
| 		{ | ||||
| 			return EntityToRelationTypeIndices[entityID]; | ||||
| 		} | ||||
| 
 | ||||
| 		public void Clear() | ||||
| 		{ | ||||
| 			nextID = 0; | ||||
| 			foreach (var componentSet in EntityToComponentTypeIndices.Values) | ||||
| 			{ | ||||
| 				componentSet.Clear(); | ||||
| 			} | ||||
| 			foreach (var relationSet in EntityToRelationTypeIndices.Values) | ||||
| 			{ | ||||
| 				relationSet.Clear(); | ||||
| 			} | ||||
| 			availableIDs.Clear(); | ||||
| 			availableIDHash.Clear(); | ||||
| 			foreach (var id in state.availableIDs) | ||||
| 			{ | ||||
| 				availableIDs.Push(id); | ||||
| 				availableIDHash.Add(id); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		private int NextID() | ||||
|  |  | |||
|  | @ -0,0 +1,36 @@ | |||
| using System; | ||||
| using System.Runtime.CompilerServices; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
| 	public ref struct ReverseSpanEnumerator<T> | ||||
| 	{ | ||||
| 		private ReadOnlySpan<T> Span; | ||||
| 		private int index; | ||||
| 
 | ||||
| 		public ReverseSpanEnumerator<T> GetEnumerator() => this; | ||||
| 
 | ||||
| 		public T Current => Span[index]; | ||||
| 
 | ||||
| 		[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| 		public bool MoveNext() | ||||
| 		{ | ||||
| 			if (index > 0) | ||||
| 			{ | ||||
| 				index -= 1; | ||||
| 				return true; | ||||
| 			} | ||||
| 
 | ||||
| 			return false; | ||||
| 		} | ||||
| 
 | ||||
| 		[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| 		public ReverseSpanEnumerator(Span<T> span) | ||||
| 		{ | ||||
| 			Span = span; | ||||
| 			index = span.Length; | ||||
| 		} | ||||
| 
 | ||||
| 		public static ReverseSpanEnumerator<T> Empty => new ReverseSpanEnumerator<T>(); | ||||
| 	} | ||||
| } | ||||
|  | @ -6,22 +6,22 @@ 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 ReverseSpanEnumerator<Entity> Entities => FilterStorage.FilterEntities(Signature); | ||||
| 		public LinearCongruentialEnumerator 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); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -3,14 +3,12 @@ using System.Collections.Generic; | |||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
| 	public struct FilterSignature | ||||
| 	public struct FilterSignature : IEquatable<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; | ||||
|  | @ -26,26 +24,31 @@ namespace MoonTools.ECS | |||
| 			return Included.SetEquals(other.Included) && Excluded.SetEquals(other.Excluded); | ||||
| 		} | ||||
| 
 | ||||
| 		private int GuidToInt(Guid guid) | ||||
| 		{ | ||||
| 			return BitConverter.ToInt32(guid.ToByteArray()); | ||||
| 		} | ||||
| 
 | ||||
| 		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; | ||||
| 		} | ||||
| 
 | ||||
| 		public static bool operator ==(FilterSignature left, FilterSignature right) | ||||
| 		{ | ||||
| 			return left.Equals(right); | ||||
| 		} | ||||
| 
 | ||||
| 		public static bool operator !=(FilterSignature left, FilterSignature right) | ||||
| 		{ | ||||
| 			return !(left == right); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,146 @@ | |||
| using System; | ||||
| using System.Collections.Generic; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
| 	internal class FilterStorage | ||||
| 	{ | ||||
| 		private EntityStorage EntityStorage; | ||||
| 		private TypeIndices ComponentTypeIndices; | ||||
| 		private Dictionary<FilterSignature, IndexableSet<Entity>> filterSignatureToEntityIDs = new Dictionary<FilterSignature, IndexableSet<Entity>>(); | ||||
| 		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<Entity>()); | ||||
| 
 | ||||
| 				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 ReverseSpanEnumerator<Entity> FilterEntities(FilterSignature filterSignature) | ||||
| 		{ | ||||
| 			return filterSignatureToEntityIDs[filterSignature].GetEnumerator(); | ||||
| 		} | ||||
| 
 | ||||
| 		public LinearCongruentialEnumerator FilterEntitiesRandom(FilterSignature filterSignature) | ||||
| 		{ | ||||
| 			return RandomGenerator.LinearCongruentialGenerator(FilterCount(filterSignature)); | ||||
| 		} | ||||
| 
 | ||||
| 		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.TryGetValue(componentTypeIndex, out var filterSignatures)) | ||||
| 			{ | ||||
| 				foreach (var filterSignature in filterSignatures) | ||||
| 				{ | ||||
| 					CheckFilter(entityID, filterSignature); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public void Check<TComponent>(int entityID) where TComponent : unmanaged | ||||
| 		{ | ||||
| 			Check(entityID, ComponentTypeIndices.GetIndex<TComponent>()); | ||||
| 		} | ||||
| 
 | ||||
| 		public bool CheckSatisfied(int entityID, FilterSignature filterSignature) | ||||
| 		{ | ||||
| 			foreach (var type in filterSignature.Included) | ||||
| 			{ | ||||
| 				if (!EntityStorage.HasComponent(entityID, type)) | ||||
| 				{ | ||||
| 					return false; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			foreach (var type in filterSignature.Excluded) | ||||
| 			{ | ||||
| 				if (EntityStorage.HasComponent(entityID, type)) | ||||
| 				{ | ||||
| 					return false; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			return true; | ||||
| 		} | ||||
| 
 | ||||
| 		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.TryGetValue(componentTypeIndex, out var filterSignatures)) | ||||
| 			{ | ||||
| 				foreach (var filterSignature in filterSignatures) | ||||
| 				{ | ||||
| 					filterSignatureToEntityIDs[filterSignature].Remove(entityID); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -1,7 +0,0 @@ | |||
| namespace MoonTools.ECS | ||||
| { | ||||
| 	public interface IHasEntity | ||||
| 	{ | ||||
| 		Entity Entity { get; } | ||||
| 	} | ||||
| } | ||||
|  | @ -1,15 +1,15 @@ | |||
| using System; | ||||
| using System.Collections; | ||||
| using System.Collections.Generic; | ||||
| using System.Runtime.InteropServices; | ||||
| using System.Runtime.CompilerServices; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
| 	internal class IndexableSet<T> : IEnumerable<T> where T : unmanaged | ||||
| 	internal class IndexableSet<T> where T : unmanaged | ||||
| 	{ | ||||
| 		private Dictionary<T, int> indices; | ||||
| 		private T[] array; | ||||
| 		public int Count { get; private set; } | ||||
| 		public ReverseSpanEnumerator<T> GetEnumerator() => new ReverseSpanEnumerator<T>(new Span<T>(array, 0, Count)); | ||||
| 
 | ||||
| 		public IndexableSet(int size = 32) | ||||
| 		{ | ||||
|  | @ -64,52 +64,47 @@ namespace MoonTools.ECS | |||
| 			return true; | ||||
| 		} | ||||
| 
 | ||||
| 		public IEnumerator<T> GetEnumerator() | ||||
| 		{ | ||||
| 			for (var i = Count - 1; i >= 0; i -= 1) | ||||
| 			{ | ||||
| 				yield return array[i]; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		IEnumerator IEnumerable.GetEnumerator() | ||||
| 		{ | ||||
| 			for (var i = Count - 1; i >= 0; i -= 1) | ||||
| 			{ | ||||
| 				yield return array[i]; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public void Clear() | ||||
| 		{ | ||||
| 			Count = 0; | ||||
| 		} | ||||
| 
 | ||||
| 		public void Save(IndexableSetState<T> state) | ||||
| 		public struct Enumerator | ||||
| 		{ | ||||
| 			ReadOnlySpan<byte> arrayBytes = MemoryMarshal.Cast<T, byte>(array); | ||||
| 			/// <summary>The set being enumerated.</summary> | ||||
| 			private readonly IndexableSet<T> _set; | ||||
| 			/// <summary>The next index to yield.</summary> | ||||
| 			private int _index; | ||||
| 
 | ||||
| 			if (arrayBytes.Length > state.Array.Length) | ||||
| 			/// <summary>Initialize the enumerator.</summary> | ||||
| 			/// <param name="set">The set to enumerate.</param> | ||||
| 			[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| 			internal Enumerator(IndexableSet<T> set) | ||||
| 			{ | ||||
| 				Array.Resize(ref state.Array, arrayBytes.Length); | ||||
| 				_set = set; | ||||
| 				_index = _set.Count; | ||||
| 			} | ||||
| 
 | ||||
| 			arrayBytes.CopyTo(state.Array); | ||||
| 
 | ||||
| 			state.Count = Count; | ||||
| 		} | ||||
| 
 | ||||
| 		public void Load(IndexableSetState<T> state) | ||||
| 		{ | ||||
| 			state.Array.CopyTo(MemoryMarshal.Cast<T, byte>(array)); | ||||
| 
 | ||||
| 			indices.Clear(); | ||||
| 			for (var i = 0; i < state.Count; i += 1) | ||||
| 			/// <summary>Advances the enumerator to the next element of the span.</summary> | ||||
| 			[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| 			public bool MoveNext() | ||||
| 			{ | ||||
| 				indices[array[i]] = i; | ||||
| 				int index = _index - 1; | ||||
| 				if (index >= 0) | ||||
| 				{ | ||||
| 					_index = index; | ||||
| 					return true; | ||||
| 				} | ||||
| 
 | ||||
| 				return false; | ||||
| 			} | ||||
| 
 | ||||
| 			Count = state.Count; | ||||
| 			/// <summary>Gets the element at the current position of the enumerator.</summary> | ||||
| 			public T Current | ||||
| 			{ | ||||
| 				[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| 				get => _set[_index]; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -7,7 +7,7 @@ namespace MoonTools.ECS | |||
| 	{ | ||||
| 		private Dictionary<Type, MessageStorage> storages = new Dictionary<Type, MessageStorage>(); | ||||
| 
 | ||||
| 		private MessageStorage<TMessage> Lookup<TMessage>() where TMessage : struct | ||||
| 		private MessageStorage<TMessage> Lookup<TMessage>() where TMessage : unmanaged | ||||
| 		{ | ||||
| 			if (!storages.ContainsKey(typeof(TMessage))) | ||||
| 			{ | ||||
|  | @ -17,37 +17,42 @@ namespace MoonTools.ECS | |||
| 			return storages[typeof(TMessage)] as MessageStorage<TMessage>; | ||||
| 		} | ||||
| 
 | ||||
| 		public void Add<TMessage>(in TMessage message) where TMessage : struct | ||||
| 		public void Add<TMessage>(in TMessage message) where TMessage : unmanaged | ||||
| 		{ | ||||
| 			Lookup<TMessage>().Add(message); | ||||
| 		} | ||||
| 
 | ||||
| 		public bool Some<TMessage>() where TMessage : struct | ||||
| 		public void Add<TMessage>(int entityID, in TMessage message) where TMessage : unmanaged | ||||
| 		{ | ||||
| 			Lookup<TMessage>().Add(entityID, message); | ||||
| 		} | ||||
| 
 | ||||
| 		public bool Some<TMessage>() where TMessage : unmanaged | ||||
| 		{ | ||||
| 			return Lookup<TMessage>().Some(); | ||||
| 		} | ||||
| 
 | ||||
| 		public ReadOnlySpan<TMessage> All<TMessage>() where TMessage : struct | ||||
| 		public ReadOnlySpan<TMessage> All<TMessage>() where TMessage : unmanaged | ||||
| 		{ | ||||
| 			return Lookup<TMessage>().All(); | ||||
| 		} | ||||
| 
 | ||||
| 		public TMessage First<TMessage>() where TMessage : struct | ||||
| 		public TMessage First<TMessage>() where TMessage : unmanaged | ||||
| 		{ | ||||
| 			return Lookup<TMessage>().First(); | ||||
| 		} | ||||
| 
 | ||||
| 		public IEnumerable<TMessage> WithEntity<TMessage>(int entityID) where TMessage : struct, IHasEntity | ||||
| 		public ReverseSpanEnumerator<TMessage> WithEntity<TMessage>(int entityID) where TMessage : unmanaged | ||||
| 		{ | ||||
| 			return Lookup<TMessage>().WithEntity(entityID); | ||||
| 		} | ||||
| 
 | ||||
| 		public ref readonly TMessage FirstWithEntity<TMessage>(int entityID) where TMessage : struct, IHasEntity | ||||
| 		public ref readonly TMessage FirstWithEntity<TMessage>(int entityID) where TMessage : unmanaged | ||||
| 		{ | ||||
| 			return ref Lookup<TMessage>().FirstWithEntity(entityID); | ||||
| 		} | ||||
| 
 | ||||
| 		public bool SomeWithEntity<TMessage>(int entityID) where TMessage : struct, IHasEntity | ||||
| 		public bool SomeWithEntity<TMessage>(int entityID) where TMessage : unmanaged | ||||
| 		{ | ||||
| 			return Lookup<TMessage>().SomeWithEntity(entityID); | ||||
| 		} | ||||
|  |  | |||
|  | @ -8,12 +8,13 @@ namespace MoonTools.ECS | |||
| 		public abstract void Clear(); | ||||
| 	} | ||||
| 
 | ||||
| 	internal class MessageStorage<TMessage> : MessageStorage where TMessage : struct | ||||
| 	internal class MessageStorage<TMessage> : MessageStorage where TMessage : unmanaged | ||||
| 	{ | ||||
| 		private int count = 0; | ||||
| 		private int capacity = 128; | ||||
| 		private TMessage[] messages; | ||||
| 		private Dictionary<int, List<int>> entityToIndices = new Dictionary<int, List<int>>(); | ||||
| 		// duplicating storage here for fast iteration | ||||
| 		private Dictionary<int, DynamicArray<TMessage>> entityToMessages = new Dictionary<int, DynamicArray<TMessage>>(); | ||||
| 
 | ||||
| 		public MessageStorage() | ||||
| 		{ | ||||
|  | @ -29,20 +30,20 @@ namespace MoonTools.ECS | |||
| 			} | ||||
| 
 | ||||
| 			messages[count] = message; | ||||
| 
 | ||||
| 			if (message is IHasEntity entityMessage) | ||||
| 			{ | ||||
| 				if (!entityToIndices.ContainsKey(entityMessage.Entity.ID)) | ||||
| 				{ | ||||
| 					entityToIndices.Add(entityMessage.Entity.ID, new List<int>()); | ||||
| 				} | ||||
| 
 | ||||
| 				entityToIndices[entityMessage.Entity.ID].Add(count); | ||||
| 			} | ||||
| 
 | ||||
| 			count += 1; | ||||
| 		} | ||||
| 
 | ||||
| 		public void Add(int entityID, in TMessage message) | ||||
| 		{ | ||||
| 			if (!entityToMessages.ContainsKey(entityID)) | ||||
| 			{ | ||||
| 				entityToMessages.Add(entityID, new DynamicArray<TMessage>()); | ||||
| 			} | ||||
| 			entityToMessages[entityID].Add(message); | ||||
| 
 | ||||
| 			Add(message); | ||||
| 		} | ||||
| 
 | ||||
| 		public bool Some() | ||||
| 		{ | ||||
| 			return count > 0; | ||||
|  | @ -58,31 +59,32 @@ namespace MoonTools.ECS | |||
| 			return messages[0]; | ||||
| 		} | ||||
| 
 | ||||
| 		public IEnumerable<TMessage> WithEntity(int entityID) | ||||
| 		public ReverseSpanEnumerator<TMessage> WithEntity(int entityID) | ||||
| 		{ | ||||
| 			if (entityToIndices.ContainsKey(entityID)) | ||||
| 			if (entityToMessages.TryGetValue(entityID, out var messages)) | ||||
| 			{ | ||||
| 				foreach (var index in entityToIndices[entityID]) | ||||
| 				{ | ||||
| 					yield return messages[index]; | ||||
| 				} | ||||
| 				return messages.GetEnumerator(); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				return ReverseSpanEnumerator<TMessage>.Empty; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public ref readonly TMessage FirstWithEntity(int entityID) | ||||
| 		{ | ||||
| 			return ref messages[entityToIndices[entityID][0]]; | ||||
| 			return ref entityToMessages[entityID][0]; | ||||
| 		} | ||||
| 
 | ||||
| 		public bool SomeWithEntity(int entityID) | ||||
| 		{ | ||||
| 			return entityToIndices.ContainsKey(entityID) && entityToIndices[entityID].Count > 0; | ||||
| 			return entityToMessages.ContainsKey(entityID) && entityToMessages[entityID].Count > 0; | ||||
| 		} | ||||
| 
 | ||||
| 		public override void Clear() | ||||
| 		{ | ||||
| 			count = 0; | ||||
| 			foreach (var set in entityToIndices.Values) | ||||
| 			foreach (var set in entityToMessages.Values) | ||||
| 			{ | ||||
| 				set.Clear(); | ||||
| 			} | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Runtime.CompilerServices; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
|  | @ -20,7 +20,7 @@ namespace MoonTools.ECS | |||
| 		/// <summary> | ||||
| 		/// A psuedorandom nonrepeating sequence of integers from 0 to n. | ||||
| 		/// </summary> | ||||
| 		public static IEnumerable<int> LinearCongruentialGenerator(int n) | ||||
| 		public static LinearCongruentialEnumerator LinearCongruentialGenerator(int n) | ||||
| 		{ | ||||
| 			var x = Primes[random.Next(Primes.Length - 1)]; | ||||
| 			while (x % n == 0) | ||||
|  | @ -29,12 +29,44 @@ namespace MoonTools.ECS | |||
| 				x = Primes[random.Next(Primes.Length - 1)]; | ||||
| 			} | ||||
| 
 | ||||
| 			var start = random.Next(n); | ||||
| 			return new LinearCongruentialEnumerator(random.Next(n), x, n); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 			for (var i = start; i < start + n; i++) | ||||
| 	public struct LinearCongruentialEnumerator | ||||
| 	{ | ||||
| 		private readonly int start; | ||||
| 		private readonly int count; | ||||
| 		private readonly int prime; | ||||
| 		private int current; | ||||
| 
 | ||||
| 		public LinearCongruentialEnumerator GetEnumerator() => this; | ||||
| 
 | ||||
| 		[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| 		internal LinearCongruentialEnumerator(int start, int prime, int count) | ||||
| 		{ | ||||
| 			current = start; | ||||
| 			this.start = start; | ||||
| 			this.prime = prime; | ||||
| 			this.count = count; | ||||
| 		} | ||||
| 
 | ||||
| 		[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| 		public bool MoveNext() | ||||
| 		{ | ||||
| 			current += 1; | ||||
| 			if (current < start + count) | ||||
| 			{ | ||||
| 				yield return (i * x) % n; | ||||
| 				return true; | ||||
| 			} | ||||
| 
 | ||||
| 			return false; | ||||
| 		} | ||||
| 
 | ||||
| 		public int Current | ||||
| 		{ | ||||
| 			[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| 			get => (current * prime) % count; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -1,37 +0,0 @@ | |||
| using System; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
| 	internal struct Relation : IEquatable<Relation> | ||||
| 	{ | ||||
| 		public Entity A { get; } | ||||
| 		public Entity B { get; } | ||||
| 
 | ||||
| 		internal Relation(Entity entityA, Entity entityB) | ||||
| 		{ | ||||
| 			A = entityA; | ||||
| 			B = entityB; | ||||
| 		} | ||||
| 
 | ||||
| 		internal Relation(int idA, int idB) | ||||
| 		{ | ||||
| 			A = new Entity(idA); | ||||
| 			B = new Entity(idB); | ||||
| 		} | ||||
| 
 | ||||
| 		public override bool Equals(object? obj) | ||||
| 		{ | ||||
| 			return obj is Relation relation && Equals(relation); | ||||
| 		} | ||||
| 
 | ||||
| 		public bool Equals(Relation other) | ||||
| 		{ | ||||
| 			return A.ID == other.A.ID && B.ID == other.B.ID; | ||||
| 		} | ||||
| 
 | ||||
| 		public override int GetHashCode() | ||||
| 		{ | ||||
| 			return HashCode.Combine(A.ID, B.ID); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -6,31 +6,49 @@ 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 | ||||
| 		public void Set<TRelationKind>(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged | ||||
| 		{ | ||||
| 			Lookup<TRelationKind>().Set(relation, relationData); | ||||
| 			Lookup<TRelationKind>().Set(entityA, entityB, relationData); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Remove<TRelationKind>(Relation relation) where TRelationKind : unmanaged | ||||
| 		public TRelationKind Get<TRelationKind>(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged | ||||
| 		{ | ||||
| 			Lookup<TRelationKind>().Remove(relation); | ||||
| 			return Lookup<TRelationKind>().Get(entityA, entityB); | ||||
| 		} | ||||
| 
 | ||||
| 		public (bool, bool) Remove<TRelationKind>(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged | ||||
| 		{ | ||||
| 			return Lookup<TRelationKind>().Remove(entityA, entityB); | ||||
| 		} | ||||
| 
 | ||||
| 		public void UnrelateAll<TRelationKind>(int entityID) where TRelationKind : unmanaged | ||||
|  | @ -38,31 +56,22 @@ namespace MoonTools.ECS | |||
| 			Lookup<TRelationKind>().UnrelateAll(entityID); | ||||
| 		} | ||||
| 
 | ||||
| 		// FIXME: optimize this | ||||
| 		public void OnEntityDestroy(int entityID) | ||||
| 		{ | ||||
| 			foreach (var storage in storages.Values) | ||||
| 			{ | ||||
| 				storage.OnEntityDestroy(entityID); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public IEnumerable<(Entity, Entity, TRelationKind)> Relations<TRelationKind>() where TRelationKind : unmanaged | ||||
| 		public ReverseSpanEnumerator<(Entity, Entity)> Relations<TRelationKind>() where TRelationKind : unmanaged | ||||
| 		{ | ||||
| 			return Lookup<TRelationKind>().All(); | ||||
| 		} | ||||
| 
 | ||||
| 		public bool Related<TRelationKind>(int idA, int idB) where TRelationKind : unmanaged | ||||
| 		{ | ||||
| 			return Lookup<TRelationKind>().Has(new Relation(idA, idB)); | ||||
| 			return Lookup<TRelationKind>().Has((idA, idB)); | ||||
| 		} | ||||
| 
 | ||||
| 		public IEnumerable<(Entity, TRelationKind)> OutRelations<TRelationKind>(int entityID) where TRelationKind : unmanaged | ||||
| 		public ReverseSpanEnumerator<Entity> OutRelations<TRelationKind>(int entityID) where TRelationKind : unmanaged | ||||
| 		{ | ||||
| 			return Lookup<TRelationKind>().OutRelations(entityID); | ||||
| 		} | ||||
| 
 | ||||
| 		public (Entity, TRelationKind) OutRelationSingleton<TRelationKind>(int entityID) where TRelationKind : unmanaged | ||||
| 		public Entity OutRelationSingleton<TRelationKind>(int entityID) where TRelationKind : unmanaged | ||||
| 		{ | ||||
| 			return Lookup<TRelationKind>().OutFirst(entityID); | ||||
| 		} | ||||
|  | @ -77,12 +86,12 @@ namespace MoonTools.ECS | |||
| 			return Lookup<TRelationKind>().HasOutRelation(entityID); | ||||
| 		} | ||||
| 
 | ||||
| 		public IEnumerable<(Entity, TRelationKind)> InRelations<TRelationKind>(int entityID) where TRelationKind : unmanaged | ||||
| 		public ReverseSpanEnumerator<Entity> InRelations<TRelationKind>(int entityID) where TRelationKind : unmanaged | ||||
| 		{ | ||||
| 			return Lookup<TRelationKind>().InRelations(entityID); | ||||
| 		} | ||||
| 
 | ||||
| 		public (Entity, TRelationKind) InRelationSingleton<TRelationKind>(int entityID) where TRelationKind : unmanaged | ||||
| 		public Entity InRelationSingleton<TRelationKind>(int entityID) where TRelationKind : unmanaged | ||||
| 		{ | ||||
| 			return Lookup<TRelationKind>().InFirst(entityID); | ||||
| 		} | ||||
|  | @ -97,24 +106,52 @@ 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()); | ||||
| 				} | ||||
| 		// untyped methods used for destroying and snapshots | ||||
| 
 | ||||
| 				storage.Save(state.StorageStates[type]); | ||||
| 		public unsafe void Set(int entityA, int entityB, int relationTypeIndex, void* relationData) | ||||
| 		{ | ||||
| 			storages[relationTypeIndex].Set(entityA, entityB, relationData); | ||||
| 		} | ||||
| 
 | ||||
| 		public int GetStorageIndex(int relationTypeIndex, int entityA, int entityB) | ||||
| 		{ | ||||
| 			return storages[relationTypeIndex].GetStorageIndex(entityA, entityB); | ||||
| 		} | ||||
| 
 | ||||
| 		public unsafe void* Get(int relationTypeIndex, int relationStorageIndex) | ||||
| 		{ | ||||
| 			return storages[relationTypeIndex].Get(relationStorageIndex); | ||||
| 		} | ||||
| 
 | ||||
| 		public void UnrelateAll(int entityID, int relationTypeIndex) | ||||
| 		{ | ||||
| 			storages[relationTypeIndex].UnrelateAll(entityID); | ||||
| 		} | ||||
| 
 | ||||
| 		public ReverseSpanEnumerator<Entity> OutRelations(int entityID, int relationTypeIndex) | ||||
| 		{ | ||||
| 			return storages[relationTypeIndex].OutRelations(entityID); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Clear() | ||||
| 		{ | ||||
| 			for (var i = 0; i < RelationTypeIndices.Count; i += 1) | ||||
| 			{ | ||||
| 				if (storages[i] != null) | ||||
| 				{ | ||||
| 					storages[i].Clear(); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public void Load(RelationDepotState state) | ||||
| 		public void CreateMissingStorages(RelationDepot other) | ||||
| 		{ | ||||
| 			foreach (var (type, storageState) in state.StorageStates) | ||||
| 			for (var i = 0; i < RelationTypeIndices.Count; i += 1) | ||||
| 			{ | ||||
| 				storages[type].Load(storageState); | ||||
| 				if (storages[i] == null && other.storages[i] != null) | ||||
| 				{ | ||||
| 					storages[i] = other.storages[i].CreateStorage(); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  |  | |||
|  | @ -1,15 +1,17 @@ | |||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Runtime.InteropServices; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
| 	internal abstract class RelationStorage | ||||
| 	{ | ||||
| 		public abstract RelationStorageState CreateState(); | ||||
| 		public abstract void Save(RelationStorageState state); | ||||
| 		public abstract void Load(RelationStorageState state); | ||||
| 		public abstract void OnEntityDestroy(int entityID); | ||||
| 		public abstract unsafe void Set(int entityA, int entityB, void* relationData); | ||||
| 		public abstract int GetStorageIndex(int entityA, int entityB); | ||||
| 		public abstract unsafe void* Get(int relationStorageIndex); | ||||
| 		public abstract void UnrelateAll(int entityID); | ||||
| 		public abstract ReverseSpanEnumerator<Entity> OutRelations(int entityID); | ||||
| 		public abstract RelationStorage CreateStorage(); | ||||
| 		public abstract void Clear(); | ||||
| 	} | ||||
| 
 | ||||
| 	// Relation is the two entities, A related to B. | ||||
|  | @ -17,33 +19,30 @@ namespace MoonTools.ECS | |||
| 	internal class RelationStorage<TRelation> : RelationStorage where TRelation : unmanaged | ||||
| 	{ | ||||
| 		private int count = 0; | ||||
| 		private Dictionary<Relation, int> indices = new Dictionary<Relation, int>(16); | ||||
| 		private Relation[] relations = new Relation[16]; | ||||
| 		private Dictionary<(Entity, Entity), int> indices = new Dictionary<(Entity, Entity), int>(16); | ||||
| 		private (Entity, Entity)[] relations = new (Entity, Entity)[16]; | ||||
| 		private TRelation[] relationDatas = new TRelation[16]; | ||||
| 		private Dictionary<int, IndexableSet<int>> outRelations = new Dictionary<int, IndexableSet<int>>(16); | ||||
| 		private Dictionary<int, IndexableSet<int>> inRelations = new Dictionary<int, IndexableSet<int>>(16); | ||||
| 		private Stack<IndexableSet<int>> listPool = new Stack<IndexableSet<int>>(); | ||||
| 		private Dictionary<int, IndexableSet<Entity>> outRelations = new Dictionary<int, IndexableSet<Entity>>(16); | ||||
| 		private Dictionary<int, IndexableSet<Entity>> inRelations = new Dictionary<int, IndexableSet<Entity>>(16); | ||||
| 		private Stack<IndexableSet<Entity>> listPool = new Stack<IndexableSet<Entity>>(); | ||||
| 
 | ||||
| 		public IEnumerable<(Entity, Entity, TRelation)> All() | ||||
| 		public ReverseSpanEnumerator<(Entity, Entity)> All() | ||||
| 		{ | ||||
| 			for (var i = 0; i < count; i += 1) | ||||
| 			{ | ||||
| 				var relation = relations[i]; | ||||
| 				yield return (relation.A, relation.B, relationDatas[i]); | ||||
| 			} | ||||
| 			return new ReverseSpanEnumerator<(Entity, Entity)>(new Span<(Entity, Entity)>(relations, 0, count)); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Set(Relation relation, TRelation relationData) | ||||
| 		public void Set(in Entity entityA, in Entity entityB, TRelation relationData) | ||||
| 		{ | ||||
| 			if (indices.ContainsKey(relation)) | ||||
| 			var relation = (entityA, entityB); | ||||
| 
 | ||||
| 			if (indices.TryGetValue(relation, out var index)) | ||||
| 			{ | ||||
| 				var index = indices[relation]; | ||||
| 				relationDatas[index] = relationData; | ||||
| 				return; | ||||
| 			} | ||||
| 
 | ||||
| 			var idA = relation.A.ID; | ||||
| 			var idB = relation.B.ID; | ||||
| 			var idA = entityA.ID; | ||||
| 			var idB = entityB.ID; | ||||
| 
 | ||||
| 			if (!outRelations.ContainsKey(idA)) | ||||
| 			{ | ||||
|  | @ -69,34 +68,37 @@ namespace MoonTools.ECS | |||
| 			count += 1; | ||||
| 		} | ||||
| 
 | ||||
| 		public bool Has(Relation relation) | ||||
| 		public TRelation Get(in Entity entityA, in Entity entityB) | ||||
| 		{ | ||||
| 			return relationDatas[indices[(entityA, entityB)]]; | ||||
| 		} | ||||
| 
 | ||||
| 		public bool Has((Entity, Entity) relation) | ||||
| 		{ | ||||
| 			return indices.ContainsKey(relation); | ||||
| 		} | ||||
| 
 | ||||
| 		// FIXME: creating the new Relation in here is slightly deranged | ||||
| 		public IEnumerable<(Entity, TRelation)> OutRelations(int entityID) | ||||
| 		public override ReverseSpanEnumerator<Entity> OutRelations(int entityID) | ||||
| 		{ | ||||
| 			if (outRelations.ContainsKey(entityID)) | ||||
| 			if (outRelations.TryGetValue(entityID, out var entityOutRelations)) | ||||
| 			{ | ||||
| 				foreach (var id in outRelations[entityID]) | ||||
| 				{ | ||||
| 					var relation = new Relation(entityID, id); | ||||
| 					yield return (relation.B, relationDatas[indices[relation]]); | ||||
| 				} | ||||
| 				return entityOutRelations.GetEnumerator(); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				return ReverseSpanEnumerator<Entity>.Empty; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public (Entity, TRelation) OutFirst(int entityID) | ||||
| 		public Entity OutFirst(int entityID) | ||||
| 		{ | ||||
| #if DEBUG | ||||
| 			if (!outRelations.ContainsKey(entityID)) | ||||
| 			if (!outRelations.ContainsKey(entityID) || outRelations[entityID].Count == 0) | ||||
| 			{ | ||||
| 				throw new KeyNotFoundException("No out relations to this entity!"); | ||||
| 			} | ||||
| #endif | ||||
| 			var relation = new Relation(entityID, outRelations[entityID][0]); | ||||
| 			return (relation.B, relationDatas[indices[relation]]); | ||||
| 			return outRelations[entityID][0]; | ||||
| 		} | ||||
| 
 | ||||
| 		public bool HasOutRelation(int entityID) | ||||
|  | @ -106,32 +108,31 @@ namespace MoonTools.ECS | |||
| 
 | ||||
| 		public int OutRelationCount(int entityID) | ||||
| 		{ | ||||
| 			return outRelations.ContainsKey(entityID) ? outRelations[entityID].Count : 0; | ||||
| 			return outRelations.TryGetValue(entityID, out var entityOutRelations) ? entityOutRelations.Count : 0; | ||||
| 		} | ||||
| 
 | ||||
| 		public IEnumerable<(Entity, TRelation)> InRelations(int entityID) | ||||
| 		public ReverseSpanEnumerator<Entity> InRelations(int entityID) | ||||
| 		{ | ||||
| 			if (inRelations.ContainsKey(entityID)) | ||||
| 			if (inRelations.TryGetValue(entityID, out var entityInRelations)) | ||||
| 			{ | ||||
| 				foreach (var id in inRelations[entityID]) | ||||
| 				{ | ||||
| 					var relation = new Relation(id, entityID); | ||||
| 					yield return (relation.A, relationDatas[indices[relation]]); | ||||
| 				} | ||||
| 				return entityInRelations.GetEnumerator(); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				return ReverseSpanEnumerator<Entity>.Empty; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public (Entity, TRelation) InFirst(int entityID) | ||||
| 		public Entity InFirst(int entityID) | ||||
| 		{ | ||||
| #if DEBUG | ||||
| 			if (!inRelations.ContainsKey(entityID)) | ||||
| 			if (!inRelations.ContainsKey(entityID) || inRelations[entityID].Count == 0) | ||||
| 			{ | ||||
| 				throw new KeyNotFoundException("No out relations to this entity!"); | ||||
| 			} | ||||
| #endif | ||||
| 
 | ||||
| 			var relation = new Relation(inRelations[entityID][0], entityID); | ||||
| 			return (relation.A, relationDatas[indices[relation]]); | ||||
| 			return inRelations[entityID][0]; | ||||
| 		} | ||||
| 
 | ||||
| 		public bool HasInRelation(int entityID) | ||||
|  | @ -141,24 +142,35 @@ namespace MoonTools.ECS | |||
| 
 | ||||
| 		public int InRelationCount(int entityID) | ||||
| 		{ | ||||
| 			return inRelations.ContainsKey(entityID) ? inRelations[entityID].Count : 0; | ||||
| 			return inRelations.TryGetValue(entityID, out var entityInRelations) ? entityInRelations.Count : 0; | ||||
| 		} | ||||
| 
 | ||||
| 		public bool Remove(Relation relation) | ||||
| 		public (bool, bool) Remove(in Entity entityA, in Entity entityB) | ||||
| 		{ | ||||
| 			if (outRelations.ContainsKey(relation.A.ID)) | ||||
| 			var aEmpty = false; | ||||
| 			var bEmpty = false; | ||||
| 			var relation = (entityA, entityB); | ||||
| 
 | ||||
| 			if (outRelations.TryGetValue(entityA.ID, out var entityOutRelations)) | ||||
| 			{ | ||||
| 				outRelations[relation.A.ID].Remove(relation.B.ID); | ||||
| 				entityOutRelations.Remove(entityB.ID); | ||||
| 				if (outRelations[entityA.ID].Count == 0) | ||||
| 				{ | ||||
| 					aEmpty = true; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			if (inRelations.ContainsKey(relation.B.ID)) | ||||
| 			if (inRelations.TryGetValue(entityB.ID, out var entityInRelations)) | ||||
| 			{ | ||||
| 				inRelations[relation.B.ID].Remove(relation.A.ID); | ||||
| 				entityInRelations.Remove(entityA.ID); | ||||
| 				if (inRelations[entityB.ID].Count == 0) | ||||
| 				{ | ||||
| 					bEmpty = true; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			if (indices.ContainsKey(relation)) | ||||
| 			if (indices.TryGetValue(relation, out var index)) | ||||
| 			{ | ||||
| 				var index = indices[relation]; | ||||
| 				var lastElementIndex = count - 1; | ||||
| 
 | ||||
| 				// move an element into the hole | ||||
|  | @ -172,111 +184,93 @@ namespace MoonTools.ECS | |||
| 
 | ||||
| 				count -= 1; | ||||
| 				indices.Remove(relation); | ||||
| 				return true; | ||||
| 			} | ||||
| 
 | ||||
| 			return false; | ||||
| 			return (aEmpty, bEmpty); | ||||
| 		} | ||||
| 
 | ||||
| 		public void UnrelateAll(int entityID) | ||||
| 		{ | ||||
| 			if (outRelations.ContainsKey(entityID)) | ||||
| 			{ | ||||
| 				foreach (var entityB in outRelations[entityID]) | ||||
| 				{ | ||||
| 					Remove(new Relation(entityID, entityB)); | ||||
| 				} | ||||
| 
 | ||||
| 				ReturnHashSetToPool(outRelations[entityID]); | ||||
| 				outRelations.Remove(entityID); | ||||
| 			} | ||||
| 
 | ||||
| 			if (inRelations.ContainsKey(entityID)) | ||||
| 			{ | ||||
| 				foreach (var entityA in inRelations[entityID]) | ||||
| 				{ | ||||
| 					Remove(new Relation(entityA, entityID)); | ||||
| 				} | ||||
| 
 | ||||
| 				ReturnHashSetToPool(inRelations[entityID]); | ||||
| 				inRelations.Remove(entityID); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public override void OnEntityDestroy(int entityID) | ||||
| 		{ | ||||
| 			UnrelateAll(entityID); | ||||
| 		} | ||||
| 
 | ||||
| 		private IndexableSet<int> AcquireHashSetFromPool() | ||||
| 		private IndexableSet<Entity> AcquireHashSetFromPool() | ||||
| 		{ | ||||
| 			if (listPool.Count == 0) | ||||
| 			{ | ||||
| 				listPool.Push(new IndexableSet<int>()); | ||||
| 				listPool.Push(new IndexableSet<Entity>()); | ||||
| 			} | ||||
| 
 | ||||
| 			return listPool.Pop(); | ||||
| 		} | ||||
| 
 | ||||
| 		private void ReturnHashSetToPool(IndexableSet<int> hashSet) | ||||
| 		private void ReturnHashSetToPool(IndexableSet<Entity> hashSet) | ||||
| 		{ | ||||
| 			hashSet.Clear(); | ||||
| 			listPool.Push(hashSet); | ||||
| 		} | ||||
| 
 | ||||
| 		public override RelationStorageState CreateState() | ||||
| 		// untyped methods used for internal implementation | ||||
| 
 | ||||
| 		public override unsafe void Set(int entityA, int entityB, void* relationData) | ||||
| 		{ | ||||
| 			return RelationStorageState.Create<TRelation>(count); | ||||
| 			Set(entityA, entityB, *((TRelation*) relationData)); | ||||
| 		} | ||||
| 
 | ||||
| 		public override void Save(RelationStorageState state) | ||||
| 		public override int GetStorageIndex(int entityA, int entityB) | ||||
| 		{ | ||||
| 			ReadOnlySpan<byte> relationBytes = MemoryMarshal.Cast<Relation, byte>(relations); | ||||
| 
 | ||||
| 			if (relationBytes.Length > state.Relations.Length) | ||||
| 			{ | ||||
| 				Array.Resize(ref state.Relations, relationBytes.Length); | ||||
| 			} | ||||
| 			relationBytes.CopyTo(state.Relations); | ||||
| 
 | ||||
| 			ReadOnlySpan<byte> relationDataBytes = MemoryMarshal.Cast<TRelation, byte>(relationDatas); | ||||
| 
 | ||||
| 			if (relationDataBytes.Length > state.RelationDatas.Length) | ||||
| 			{ | ||||
| 				Array.Resize(ref state.RelationDatas, relationDataBytes.Length); | ||||
| 			} | ||||
| 			relationDataBytes.CopyTo(state.RelationDatas); | ||||
| 
 | ||||
| 			state.Count = count; | ||||
| 			return indices[(entityA, entityB)]; | ||||
| 		} | ||||
| 
 | ||||
| 		public override void Load(RelationStorageState state) | ||||
| 		public override unsafe void* Get(int relationStorageIndex) | ||||
| 		{ | ||||
| 			state.Relations.CopyTo(MemoryMarshal.Cast<Relation, byte>(relations)); | ||||
| 			state.RelationDatas.CopyTo(MemoryMarshal.Cast<TRelation, byte>(relationDatas)); | ||||
| 			fixed (void* p = &relations[relationStorageIndex]) | ||||
| 			{ | ||||
| 				return p; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public override void UnrelateAll(int entityID) | ||||
| 		{ | ||||
| 			if (outRelations.TryGetValue(entityID, out var entityOutRelations)) | ||||
| 			{ | ||||
| 				foreach (var entityB in entityOutRelations) | ||||
| 				{ | ||||
| 					Remove(entityID, entityB); | ||||
| 				} | ||||
| 
 | ||||
| 				ReturnHashSetToPool(entityOutRelations); | ||||
| 				outRelations.Remove(entityID); | ||||
| 			} | ||||
| 
 | ||||
| 			if (inRelations.TryGetValue(entityID, out var entityInRelations)) | ||||
| 			{ | ||||
| 				foreach (var entityA in entityInRelations) | ||||
| 				{ | ||||
| 					Remove(entityA, entityID); | ||||
| 				} | ||||
| 
 | ||||
| 				ReturnHashSetToPool(entityInRelations); | ||||
| 				inRelations.Remove(entityID); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public override RelationStorage<TRelation> CreateStorage() | ||||
| 		{ | ||||
| 			return new RelationStorage<TRelation>(); | ||||
| 		} | ||||
| 
 | ||||
| 		public override void Clear() | ||||
| 		{ | ||||
| 			count = 0; | ||||
| 			indices.Clear(); | ||||
| 			outRelations.Clear(); | ||||
| 			inRelations.Clear(); | ||||
| 			for (var i = 0; i < state.Count; i += 1) | ||||
| 
 | ||||
| 			foreach (var set in inRelations.Values) | ||||
| 			{ | ||||
| 				var relation = relations[i]; | ||||
| 				indices[relation] = i; | ||||
| 
 | ||||
| 				if (!outRelations.ContainsKey(relation.A.ID)) | ||||
| 				{ | ||||
| 					outRelations[relation.A.ID] = AcquireHashSetFromPool(); | ||||
| 				} | ||||
| 				outRelations[relation.A.ID].Add(relation.B.ID); | ||||
| 
 | ||||
| 				if (!inRelations.ContainsKey(relation.B.ID)) | ||||
| 				{ | ||||
| 					inRelations[relation.B.ID] = AcquireHashSetFromPool(); | ||||
| 				} | ||||
| 				inRelations[relation.B.ID].Add(relation.A.ID); | ||||
| 				ReturnHashSetToPool(set); | ||||
| 			} | ||||
| 			inRelations.Clear(); | ||||
| 
 | ||||
| 			count = state.Count; | ||||
| 			foreach (var set in outRelations.Values) | ||||
| 			{ | ||||
| 				ReturnHashSetToPool(set); | ||||
| 			} | ||||
| 			outRelations.Clear(); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,122 @@ | |||
| using System; | ||||
| using System.Collections.Generic; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
| 	public class Snapshot | ||||
| 	{ | ||||
| 		private World World; | ||||
| 		private Filter? Filter; | ||||
| 
 | ||||
| 		private EntityStorage SnapshotEntityStorage; | ||||
| 		private ComponentDepot SnapshotComponentDepot; | ||||
| 		private RelationDepot SnapshotRelationDepot; | ||||
| 
 | ||||
| 		private List<int> SnapshotToWorldID = new List<int>(); | ||||
| 		private Dictionary<int, int> WorldToSnapshotID = new Dictionary<int, int>(); | ||||
| 
 | ||||
| 		internal Snapshot(World world) | ||||
| 		{ | ||||
| 			World = world; | ||||
| 			SnapshotEntityStorage = new EntityStorage(); | ||||
| 			SnapshotComponentDepot = new ComponentDepot(World.ComponentTypeIndices); | ||||
| 			SnapshotRelationDepot = new RelationDepot(World.RelationTypeIndices); | ||||
| 		} | ||||
| 
 | ||||
| 		public unsafe void Take(Filter filter) | ||||
| 		{ | ||||
| 			Clear(); | ||||
| 			Filter = filter; | ||||
| 			SnapshotComponentDepot.CreateMissingStorages(World.ComponentDepot); | ||||
| 			SnapshotRelationDepot.CreateMissingStorages(World.RelationDepot); | ||||
| 
 | ||||
| 			foreach (var worldEntity in filter.Entities) | ||||
| 			{ | ||||
| 				var snapshotEntity = SnapshotEntityStorage.Create(); | ||||
| 				WorldToSnapshotID.Add(worldEntity.ID, snapshotEntity.ID); | ||||
| 
 | ||||
| 				foreach (var componentTypeIndex in World.EntityStorage.ComponentTypeIndices(worldEntity.ID)) | ||||
| 				{ | ||||
| 					SnapshotEntityStorage.SetComponent(snapshotEntity.ID, componentTypeIndex); | ||||
| 					SnapshotComponentDepot.Set(snapshotEntity.ID, componentTypeIndex, World.ComponentDepot.UntypedGet(worldEntity.ID, componentTypeIndex)); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			foreach (var worldEntity in filter.Entities) | ||||
| 			{ | ||||
| 				var snapshotEntityID = WorldToSnapshotID[worldEntity.ID]; | ||||
| 
 | ||||
| 				foreach (var relationTypeIndex in World.EntityStorage.RelationTypeIndices(worldEntity.ID)) | ||||
| 				{ | ||||
| 					SnapshotEntityStorage.AddRelationKind(snapshotEntityID, relationTypeIndex); | ||||
| 
 | ||||
| 					foreach (var otherEntityID in World.RelationDepot.OutRelations(worldEntity.ID, relationTypeIndex)) | ||||
| 					{ | ||||
| #if DEBUG | ||||
| 						if (!World.FilterStorage.CheckSatisfied(otherEntityID, Filter.Signature)) | ||||
| 						{ | ||||
| 							throw new InvalidOperationException($"Snapshot entity {worldEntity.ID} is related to non-snapshot entity {otherEntityID}!"); | ||||
| 						} | ||||
| #endif | ||||
| 						var relationStorageIndex = World.RelationDepot.GetStorageIndex(relationTypeIndex, worldEntity, otherEntityID); | ||||
| 						var otherSnapshotID = WorldToSnapshotID[otherEntityID]; | ||||
| 						SnapshotEntityStorage.AddRelationKind(otherSnapshotID, relationTypeIndex); | ||||
| 						SnapshotRelationDepot.Set(snapshotEntityID, otherSnapshotID, relationTypeIndex, World.RelationDepot.Get(relationTypeIndex, relationStorageIndex)); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public unsafe void Restore() | ||||
| 		{ | ||||
| 			if (Filter == null) | ||||
| 			{ | ||||
| 				return; | ||||
| 			} | ||||
| 
 | ||||
| 			foreach (var entity in Filter.Entities) | ||||
| 			{ | ||||
| 				World.Destroy(entity); | ||||
| 			} | ||||
| 
 | ||||
| 			for (var i = 0; i < SnapshotEntityStorage.Count; i += 1) | ||||
| 			{ | ||||
| 				var entity = World.CreateEntity(); | ||||
| 				SnapshotToWorldID.Add(entity.ID); | ||||
| 
 | ||||
| 				foreach (var componentTypeIndex in SnapshotEntityStorage.ComponentTypeIndices(i)) | ||||
| 				{ | ||||
| 					World.EntityStorage.SetComponent(entity.ID, componentTypeIndex); | ||||
| 					World.FilterStorage.Check(entity.ID, componentTypeIndex); | ||||
| 					World.ComponentDepot.Set(entity.ID, componentTypeIndex, SnapshotComponentDepot.UntypedGet(i, componentTypeIndex)); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			for (var i = 0; i < SnapshotEntityStorage.Count; i += 1) | ||||
| 			{ | ||||
| 				var worldID = SnapshotToWorldID[i]; | ||||
| 
 | ||||
| 				foreach (var relationTypeIndex in SnapshotEntityStorage.RelationTypeIndices(i)) | ||||
| 				{ | ||||
| 					World.EntityStorage.AddRelationKind(worldID, relationTypeIndex); | ||||
| 
 | ||||
| 					foreach (var otherEntityID in SnapshotRelationDepot.OutRelations(i, relationTypeIndex)) | ||||
| 					{ | ||||
| 						var relationStorageIndex = SnapshotRelationDepot.GetStorageIndex(relationTypeIndex, i, otherEntityID); | ||||
| 						var otherEntityWorldID = SnapshotToWorldID[otherEntityID]; | ||||
| 						World.RelationDepot.Set(worldID, otherEntityWorldID, relationTypeIndex, SnapshotRelationDepot.Get(relationTypeIndex, relationStorageIndex)); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		private void Clear() | ||||
| 		{ | ||||
| 			SnapshotEntityStorage.Clear(); | ||||
| 			SnapshotComponentDepot.Clear(); | ||||
| 			SnapshotRelationDepot.Clear(); | ||||
| 			SnapshotToWorldID.Clear(); | ||||
| 			WorldToSnapshotID.Clear(); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -1,11 +0,0 @@ | |||
| using System; | ||||
| using System.Collections.Generic; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
| 	internal class ComponentDepotState | ||||
| 	{ | ||||
| 		public Dictionary<Type, ComponentStorageState> StorageStates = new Dictionary<Type, ComponentStorageState>(); | ||||
| 		public Dictionary<FilterSignature, IndexableSetState<int>> FilterStates = new Dictionary<FilterSignature, IndexableSetState<int>>(); | ||||
| 	} | ||||
| } | ||||
|  | @ -1,27 +0,0 @@ | |||
| using System.Collections.Generic; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
|     internal class ComponentStorageState | ||||
|     { | ||||
| 		public int Count; | ||||
| 		public byte[] EntityIDs; | ||||
| 		public byte[] Components; | ||||
| 
 | ||||
|         public unsafe static ComponentStorageState Create<TComponent>(int count) where TComponent : unmanaged | ||||
|         { | ||||
| 			return new ComponentStorageState( | ||||
|                 count, | ||||
|                 count * sizeof(int), | ||||
|                 count * sizeof(TComponent) | ||||
|             ); | ||||
| 		} | ||||
| 
 | ||||
|         private ComponentStorageState(int count, int entityIDSize, int componentSize) | ||||
|         { | ||||
| 			Count = count; | ||||
| 			EntityIDs = new byte[entityIDSize]; | ||||
| 			Components = new byte[componentSize]; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -1,10 +0,0 @@ | |||
| using System.Collections.Generic; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
|     internal class EntityStorageState | ||||
|     { | ||||
| 		public int NextID; | ||||
| 		public List<int> availableIDs = new List<int>(); | ||||
| 	} | ||||
| } | ||||
|  | @ -1,16 +0,0 @@ | |||
| using System.Collections.Generic; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
|     internal class IndexableSetState<T> where T : unmanaged | ||||
|     { | ||||
| 		public int Count; | ||||
| 		public byte[] Array; | ||||
| 
 | ||||
|         public unsafe IndexableSetState(int count) | ||||
|         { | ||||
| 			Count = count; | ||||
| 			Array = new byte[sizeof(T) * count]; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -1,10 +0,0 @@ | |||
| using System; | ||||
| using System.Collections.Generic; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
|     internal class RelationDepotState | ||||
|     { | ||||
| 		public Dictionary<Type, RelationStorageState> StorageStates = new Dictionary<Type, RelationStorageState>(); | ||||
| 	} | ||||
| } | ||||
|  | @ -1,27 +0,0 @@ | |||
| using System.Collections.Generic; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
| 	internal class RelationStorageState | ||||
| 	{ | ||||
| 		public int Count; | ||||
| 		public byte[] Relations; | ||||
| 		public byte[] RelationDatas; | ||||
| 
 | ||||
| 		public unsafe static RelationStorageState Create<TRelation>(int count) where TRelation : unmanaged | ||||
| 		{ | ||||
| 			return new RelationStorageState( | ||||
| 				count, | ||||
| 				count * sizeof(Relation), | ||||
| 				count * sizeof(TRelation) | ||||
| 			); | ||||
| 		} | ||||
| 
 | ||||
| 		private RelationStorageState(int count, int relationSize, int relationDataSize) | ||||
| 		{ | ||||
| 			Count = count; | ||||
| 			Relations = new byte[relationSize]; | ||||
| 			RelationDatas = new byte[relationDataSize]; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -1,16 +0,0 @@ | |||
| namespace MoonTools.ECS | ||||
| { | ||||
|     public class WorldState | ||||
|     { | ||||
| 		internal readonly ComponentDepotState ComponentDepotState; | ||||
| 		internal readonly EntityStorageState EntityStorageState; | ||||
| 		internal readonly RelationDepotState RelationDepotState; | ||||
| 
 | ||||
| 		public WorldState() | ||||
|         { | ||||
| 			ComponentDepotState = new ComponentDepotState(); | ||||
| 			EntityStorageState = new EntityStorageState(); | ||||
| 			RelationDepotState = new RelationDepotState(); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -11,28 +11,24 @@ namespace MoonTools.ECS | |||
| 
 | ||||
| 		public abstract void Update(TimeSpan delta); | ||||
| 
 | ||||
| 		protected Entity CreateEntity() | ||||
| 		{ | ||||
| 			return EntityStorage.Create(); | ||||
| 		} | ||||
| 		protected Entity CreateEntity() => World.CreateEntity(); | ||||
| 
 | ||||
| 		protected void Set<TComponent>(in Entity entity, in TComponent component) where TComponent : unmanaged | ||||
| 		{ | ||||
| #if DEBUG | ||||
| 			// check for use after destroy | ||||
| 			if (!Exists(entity)) | ||||
| 			{ | ||||
| 				throw new ArgumentException("This entity is not valid!"); | ||||
| 			} | ||||
| #endif | ||||
| 			ComponentDepot.Set<TComponent>(entity.ID, component); | ||||
| 		} | ||||
| 		protected void Set<TComponent>(in Entity entity, in TComponent component) where TComponent : unmanaged => World.Set<TComponent>(entity, 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 void Set<TComponent>(in Template template, in TComponent component) where TComponent : unmanaged => World.Set<TComponent>(template, component); | ||||
| 
 | ||||
| 		// This feature is EXPERIMENTAL. USe at your own risk!! | ||||
| 		protected Entity Instantiate(in Template template) => World.Instantiate(template); | ||||
| 
 | ||||
| 		protected ReadOnlySpan<TMessage> ReadMessages<TMessage>() where TMessage : unmanaged | ||||
| 		{ | ||||
| 			return MessageDepot.All<TMessage>(); | ||||
|  | @ -48,17 +44,17 @@ namespace MoonTools.ECS | |||
| 			return MessageDepot.Some<TMessage>(); | ||||
| 		} | ||||
| 
 | ||||
| 		protected IEnumerable<TMessage> ReadMessagesWithEntity<TMessage>(in Entity entity) where TMessage : unmanaged, IHasEntity | ||||
| 		protected ReverseSpanEnumerator<TMessage> ReadMessagesWithEntity<TMessage>(in Entity entity) where TMessage : unmanaged | ||||
| 		{ | ||||
| 			return MessageDepot.WithEntity<TMessage>(entity.ID); | ||||
| 		} | ||||
| 
 | ||||
| 		protected ref readonly TMessage ReadMessageWithEntity<TMessage>(in Entity entity) where TMessage : unmanaged, IHasEntity | ||||
| 		protected ref readonly TMessage ReadMessageWithEntity<TMessage>(in Entity entity) where TMessage : unmanaged | ||||
| 		{ | ||||
| 			return ref MessageDepot.FirstWithEntity<TMessage>(entity.ID); | ||||
| 		} | ||||
| 
 | ||||
| 		protected bool SomeMessageWithEntity<TMessage>(in Entity entity) where TMessage : unmanaged, IHasEntity | ||||
| 		protected bool SomeMessageWithEntity<TMessage>(in Entity entity) where TMessage : unmanaged | ||||
| 		{ | ||||
| 			return MessageDepot.SomeWithEntity<TMessage>(entity.ID); | ||||
| 		} | ||||
|  | @ -68,27 +64,40 @@ namespace MoonTools.ECS | |||
| 			MessageDepot.Add(message); | ||||
| 		} | ||||
| 
 | ||||
| 		protected void Send<TMessage>(in Entity entity, in TMessage message) where TMessage : unmanaged | ||||
| 		{ | ||||
| 			MessageDepot.Add(entity.ID, message); | ||||
| 		} | ||||
| 
 | ||||
| 		protected void Relate<TRelationKind>(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged | ||||
| 		{ | ||||
| 			RelationDepot.Set<TRelationKind>(new Relation(entityA, entityB), relationData); | ||||
| 			RelationDepot.Set(entityA, entityB, relationData); | ||||
| 			var relationTypeIndex = RelationTypeIndices.GetIndex<TRelationKind>(); | ||||
| 			EntityStorage.AddRelationKind(entityA.ID, relationTypeIndex); | ||||
| 			EntityStorage.AddRelationKind(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>(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); | ||||
| 			EntityStorage.Destroy(entity); | ||||
| 		} | ||||
| 		protected void Destroy(in Entity entity) => World.Destroy(entity); | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,13 @@ | |||
| namespace MoonTools.ECS | ||||
| { | ||||
| 	// This feature is EXPERIMENTAL. Use at your own risk!! | ||||
| 	public struct Template | ||||
| 	{ | ||||
| 		public int ID { get; } | ||||
| 
 | ||||
| 		internal Template(int id) | ||||
| 		{ | ||||
| 			ID = id; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,34 @@ | |||
| using System.Collections.Generic; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
| 	public class TemplateStorage | ||||
| 	{ | ||||
| 		private int nextID = 0; | ||||
| 
 | ||||
| 		private Dictionary<int, HashSet<int>> TemplateToComponentTypeIndices = new Dictionary<int, HashSet<int>>(); | ||||
| 
 | ||||
| 		public Template Create() | ||||
| 		{ | ||||
| 			TemplateToComponentTypeIndices.Add(nextID, new HashSet<int>()); | ||||
| 			return new Template(NextID()); | ||||
| 		} | ||||
| 
 | ||||
| 		public bool SetComponent(int templateID, int componentTypeIndex) | ||||
| 		{ | ||||
| 			return TemplateToComponentTypeIndices[templateID].Add(componentTypeIndex); | ||||
| 		} | ||||
| 
 | ||||
| 		public HashSet<int> ComponentTypeIndices(int templateID) | ||||
| 		{ | ||||
| 			return TemplateToComponentTypeIndices[templateID]; | ||||
| 		} | ||||
| 
 | ||||
| 		private int NextID() | ||||
| 		{ | ||||
| 			var id = nextID; | ||||
| 			nextID += 1; | ||||
| 			return id; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,33 @@ | |||
| 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 Count => TypeToIndex.Count; | ||||
| 
 | ||||
| 		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 Dictionary<Type, int>.KeyCollection Types => TypeToIndex.Keys; | ||||
| #endif | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										104
									
								
								src/World.cs
								
								
								
								
							
							
						
						
									
										104
									
								
								src/World.cs
								
								
								
								
							|  | @ -1,11 +1,28 @@ | |||
| namespace MoonTools.ECS | ||||
| using System; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
| 	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; | ||||
| 		public FilterBuilder FilterBuilder => new FilterBuilder(FilterStorage, ComponentTypeIndices); | ||||
| 
 | ||||
| 		internal readonly TemplateStorage TemplateStorage = new TemplateStorage(); | ||||
| 		internal readonly ComponentDepot TemplateComponentDepot; | ||||
| 
 | ||||
| 		public World() | ||||
| 		{ | ||||
| 			ComponentDepot = new ComponentDepot(ComponentTypeIndices); | ||||
| 			RelationDepot = new RelationDepot(RelationTypeIndices); | ||||
| 			FilterStorage = new FilterStorage(EntityStorage, ComponentTypeIndices); | ||||
| 			TemplateComponentDepot = new ComponentDepot(ComponentTypeIndices); | ||||
| 		} | ||||
| 
 | ||||
| 		public Entity CreateEntity() | ||||
| 		{ | ||||
|  | @ -14,7 +31,46 @@ | |||
| 
 | ||||
| 		public void Set<TComponent>(Entity entity, in TComponent component) where TComponent : unmanaged | ||||
| 		{ | ||||
| 			ComponentDepot.Set(entity.ID, component); | ||||
| #if DEBUG | ||||
| 			// check for use after destroy | ||||
| 			if (!EntityStorage.Exists(entity)) | ||||
| 			{ | ||||
| 				throw new InvalidOperationException("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); | ||||
| 		} | ||||
| 
 | ||||
| 		public Template CreateTemplate() | ||||
| 		{ | ||||
| 			return TemplateStorage.Create(); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Set<TComponent>(in Template template, in TComponent component) where TComponent : unmanaged | ||||
| 		{ | ||||
| 			var componentTypeIndex = ComponentTypeIndices.GetIndex<TComponent>(); | ||||
| 			TemplateStorage.SetComponent(template.ID, componentTypeIndex); | ||||
| 			TemplateComponentDepot.Set(template.ID, component); | ||||
| 			ComponentDepot.Register<TComponent>(componentTypeIndex); | ||||
| 		} | ||||
| 
 | ||||
| 		public unsafe Entity Instantiate(in Template template) | ||||
| 		{ | ||||
| 			var entity = EntityStorage.Create(); | ||||
| 
 | ||||
| 			foreach (var componentTypeIndex in TemplateStorage.ComponentTypeIndices(template.ID)) | ||||
| 			{ | ||||
| 				EntityStorage.SetComponent(entity.ID, componentTypeIndex); | ||||
| 				FilterStorage.Check(entity.ID, componentTypeIndex); | ||||
| 				ComponentDepot.Set(entity.ID, componentTypeIndex, TemplateComponentDepot.UntypedGet(template.ID, componentTypeIndex)); | ||||
| 			} | ||||
| 
 | ||||
| 			return entity; | ||||
| 		} | ||||
| 
 | ||||
| 		public void Send<TMessage>(in TMessage message) where TMessage : unmanaged | ||||
|  | @ -22,33 +78,31 @@ | |||
| 			MessageDepot.Add(message); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Destroy(in Entity entity) | ||||
| 		{ | ||||
| 			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); | ||||
| 		} | ||||
| 
 | ||||
| 
 | ||||
| 		public void FinishUpdate() | ||||
| 		{ | ||||
| 			MessageDepot.Clear(); | ||||
| 		} | ||||
| 
 | ||||
| 		public void DisableSerialization<TComponent>() where TComponent : unmanaged | ||||
| 		public Snapshot CreateSnapshot() | ||||
| 		{ | ||||
| 			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); | ||||
| 			return new Snapshot(this); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue