using System; using System.Collections.Generic; using System.Runtime.CompilerServices; namespace MoonTools.ECS { internal class ComponentDepot { private Dictionary storages = new Dictionary(); private Dictionary> filterSignatureToEntityIDs = new Dictionary>(); private Dictionary> typeToFilterSignatures = new Dictionary>(); #if DEBUG private Dictionary singleComponentFilters = new Dictionary(); #endif private HashSet TypesWithDisabledSerialization = new HashSet(); [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void Register() where TComponent : unmanaged { if (!storages.ContainsKey(typeof(TComponent))) { storages.Add(typeof(TComponent), new ComponentStorage()); #if DEBUG singleComponentFilters.Add(typeof(TComponent), CreateFilter(new HashSet() { typeof(TComponent) }, new HashSet())); #endif } } private ComponentStorage Lookup(Type type) { return storages[type]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private ComponentStorage Lookup() where TComponent : unmanaged { // TODO: is it possible to optimize this? Register(); return (ComponentStorage) storages[typeof(TComponent)]; } public bool Some() where TComponent : unmanaged { return Lookup().Any(); } public bool Has(int entityID) where TComponent : unmanaged { return Lookup().Has(entityID); } private bool Has(Type type, int entityID) { return Lookup(type).Has(entityID); } public ref readonly TComponent Get(int entityID) where TComponent : unmanaged { return ref Lookup().Get(entityID); } public ref readonly TComponent Get() where TComponent : unmanaged { return ref Lookup().Get(); } public void Set(int entityID, in TComponent component) where TComponent : unmanaged { var existed = Lookup().Set(entityID, component); // update filters if (!existed) { if (typeToFilterSignatures.TryGetValue(typeof(TComponent), out var filterSignatures)) { foreach (var filterSignature in filterSignatures) { CheckFilter(filterSignature, entityID); } } } } public Entity GetSingletonEntity() where TComponent : unmanaged { return Lookup().FirstEntity(); } public ReadOnlySpan ReadComponents() where TComponent : unmanaged { return Lookup().AllComponents(); } private void Remove(Type type, int entityID) { 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); } } } } public void Remove(int entityID) where TComponent : unmanaged { var existed = Lookup().Remove(entityID); // update filters if (existed) { if (typeToFilterSignatures.TryGetValue(typeof(TComponent), out var filterSignatures)) { foreach (var filterSignature in filterSignatures) { CheckFilter(filterSignature, entityID); } } } } // TODO: is there some way to optimize this without complicating serialization? public void OnEntityDestroy(int entityID) { foreach (var type in storages.Keys) { Remove(type, entityID); } } public Filter CreateFilter(HashSet included, HashSet excluded) { var filterSignature = new FilterSignature(included, excluded); if (!filterSignatureToEntityIDs.ContainsKey(filterSignature)) { filterSignatureToEntityIDs.Add(filterSignature, new IndexableSet()); foreach (var type in included) { if (!typeToFilterSignatures.ContainsKey(type)) { typeToFilterSignatures.Add(type, new HashSet()); } typeToFilterSignatures[type].Add(filterSignature); } foreach (var type in excluded) { if (!typeToFilterSignatures.ContainsKey(type)) { typeToFilterSignatures.Add(type, new HashSet()); } typeToFilterSignatures[type].Add(filterSignature); } } return new Filter(this, included, excluded); } // FIXME: this dictionary should probably just store entities public IEnumerable FilterEntities(Filter filter) { foreach (var id in filterSignatureToEntityIDs[filter.Signature]) { yield return new Entity(id); } } public IEnumerable 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() 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(set.Count); } set.Save(state.FilterStates[signature]); } } } public void Load(ComponentDepotState state) { foreach (var (type, storageState) in state.StorageStates) { storages[type].Load(storageState); } foreach (var (signature, setState) in state.FilterStates) { filterSignatureToEntityIDs[signature].Load(setState); } } #if DEBUG public IEnumerable Debug_GetAllComponents(int entityID) { foreach (var (type, storage) in storages) { if (storage.Has(entityID)) { yield return storage.Debug_Get(entityID); } } } public IEnumerable Debug_GetEntities(Type componentType) { return singleComponentFilters[componentType].Entities; } public IEnumerable Debug_SearchComponentType(string typeString) { foreach (var type in storages.Keys) { if (type.ToString().ToLower().Contains(typeString.ToLower())) { yield return type; } } } #endif } }