using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; namespace MfGames.Gallium { /// /// A low-overhead entity with identification. /// public record Entity { /// public virtual bool Equals(Entity? other) { if (ReferenceEquals(null, other)) { return false; } if (ReferenceEquals(this, other)) { return true; } return this.Id == other.Id; } /// public override int GetHashCode() { return this.Id; } private ImmutableDictionary Components { get; set; } /// /// The internal ID to ensure the entities are unique. Since we are not /// worried about serialization or using the identifiers from one call /// to another, we can use a simple interlocked identifier instead of /// a factory or provider method. /// private static int nextId; public Entity() : this(Interlocked.Increment(ref nextId)) { } private Entity(int id) { this.Id = id; this.Components = ImmutableDictionary.Create(); } /// /// Gets a value indicating whether the entity has a specific type of /// component registered. /// /// The component type. /// True if the type exists, otherwise false. public bool Has() { return this.Has(typeof(T1)); } /// /// Gets a value indicating whether the entity has components of the given types /// registered. /// /// The first component type. /// The second component type. /// /// True if there are components of the given type exists, otherwise /// false. /// public bool HasAll() { return this.HasAll(typeof(T1), typeof(T2)); } /// /// Gets a value indicating whether the entity has components of the given types /// registered. /// /// The first component type. /// The second component type. /// The third component type. /// /// True if there are components of the given type exists, otherwise /// false. /// public bool HasAll() { return this.HasAll(typeof(T1), typeof(T2), typeof(T3)); } /// /// Gets a value indicating whether the entity has components of the given types /// registered. /// /// The first component type. /// The second component type. /// The third component type. /// The third component type. /// /// True if there are components of the given type exists, otherwise /// false. /// public bool HasAll() { return this.HasAll(typeof(T1), typeof(T2), typeof(T3), typeof(T4)); } /// /// Gets a value indicating whether the entity has a specific type of /// component registered. /// /// The component type. /// True if the type exists, otherwise false. public bool Has(Type type) { return this.Components.ContainsKey(type); } /// /// Gets a value indicating whether the entity has components for all the given /// types. /// /// The component type. /// The component type. /// True if the type exists, otherwise false. public bool HasAll( Type t1, Type t2) { return this.Has(t1) && this.Components.ContainsKey(t2); } /// /// Gets a value indicating whether the entity has components for all the given /// types. /// /// The component type. /// The component type. /// The component type. /// True if the type exists, otherwise false. public bool HasAll( Type t1, Type t2, Type t3) { return this.HasAll(t1, t2) && this.Components.ContainsKey(t3); } /// /// Gets a value indicating whether the entity has components for all the given /// types. /// /// The component type. /// The component type. /// The component type. /// The component type. /// True if the type exists, otherwise false. public bool HasAll( Type t1, Type t2, Type t3, Type t4) { return this.HasAll(t1, t2, t3) && this.Components.ContainsKey(t4); } /// /// Retrieves a registered component of the given type. /// /// The component type. /// The registered object. public TType Get() { return (TType)this.Components[typeof(TType)]; } /// /// Retrieves a registered component of the given type and casts it to /// TType. /// /// The component key. /// The component type. /// The registered object. public TType Get(Type type) { return (TType)this.Components[type]; } /// /// Gets the number of components registered in the entity. /// public int Count => this.Components.Count; /// /// Gets the given component type if inside the entity, otherwise the /// default value. /// /// The component type. /// The found component or default (typically null). public TType? GetOptional() { return this.Has() ? this.Get() : default; } /// /// Attempts to get the value, if present. If not, this returns false /// and the value is undefined. Otherwise, this method returns true /// and the actual value inside that variable. /// /// The value if contained in the entity. /// The component type. /// True if found, otherwise false. public bool TryGet(out T1 value) { if (this.Has()) { value = this.Get(); return true; } value = default!; return false; } /// /// Attempts to get the values, if present. If not, this returns false /// and the value is undefined. Otherwise, this method returns true /// and the actual value inside that variable. /// /// The value if contained in the entity. /// The value if contained in the entity. /// The first component type. /// The second component type. /// True if found, otherwise false. public bool TryGet( out T1 value1, out T2 value2) { if (this.HasAll()) { value1 = this.Get(); value2 = this.Get(); return true; } value1 = default!; value2 = default!; return false; } /// /// Attempts to get the values, if present. If not, this returns false /// and the value is undefined. Otherwise, this method returns true /// and the actual value inside that variable. /// /// The value if contained in the entity. /// The value if contained in the entity. /// The value if contained in the entity. /// The first component type. /// The second component type. /// The third component type. /// True if found, otherwise false. public bool TryGet( out T1 value1, out T2 value2, out T3 value3) { if (this.HasAll()) { value1 = this.Get(); value2 = this.Get(); value3 = this.Get(); return true; } value1 = default!; value2 = default!; value3 = default!; return false; } /// /// Attempts to get the values, if present. If not, this returns false /// and the value is undefined. Otherwise, this method returns true /// and the actual value inside that variable. /// /// The value if contained in the entity. /// The value if contained in the entity. /// The value if contained in the entity. /// The value if contained in the entity. /// The first component type. /// The second component type. /// The third component type. /// The fourth component type. /// True if found, otherwise false. public bool TryGet( out T1 value1, out T2 value2, out T3 value3, out T4 value4) { if (this.HasAll()) { value1 = this.Get(); value2 = this.Get(); value3 = this.Get(); value4 = this.Get(); return true; } value1 = default!; value2 = default!; value3 = default!; value4 = default!; return false; } /// /// Sets the component in the entity, regardless if there was a /// component already registered. /// /// The component to register. /// The component type. /// The entity for chaining. /// public Entity Set(T1 component) { if (component == null) { throw new ArgumentNullException(nameof(component)); } if (this.Components.TryGetValue(typeof(T1), out object? value) && value is T1 && value.Equals(component)) { return this; } return this with { Components = this.Components.SetItem(typeof(T1), component), }; } /// /// Adds a component to the entity. /// /// The component to register. /// The component type. /// /// The same entity if the component is already registered, otherwise a /// cloned entity with the new component. /// /// public Entity Add(T1 component) { if (component == null) { throw new ArgumentNullException(nameof(component)); } if (this.Has()) { throw new ArgumentException( "An element with the same type (" + typeof(T1).FullName + ") already exists.", nameof(component)); } if (this.Components.TryGetValue(typeof(T1), out object? value) && value is T1 && value.Equals(component)) { return this; } return this with { Components = this.Components.Add(typeof(T1), component), }; } /// /// Removes a component to the entity. /// /// The component type. /// /// The same entity if the component is already removed, otherwise a /// cloned entity without the new component. /// /// public Entity Remove() { return this.Remove(typeof(TType)); } /// /// Removes a component to the entity. /// /// /// The same entity if the component is already removed, otherwise a /// cloned entity without the new component. /// /// The component type to remove. public Entity Remove(Type type) { if (!this.Has(type)) { return this; } return this with { Components = this.Components.Remove(type), }; } /// /// Gets the identifier of the entity. This should be treated as an /// opaque field. /// public int Id { get; private init; } /// /// Creates a copy of the entity, including copying the identifier. /// /// public Entity ExactCopy() { return this with { }; } /// /// Creates a copy of the entity, including components, but with a new /// identifier. /// /// public Entity Copy() { return this with { Id = Interlocked.Increment(ref nextId), }; } /// /// Retrieves a list of the component types currently registered in the /// Entity. /// /// An enumerable of the various component keys. public IEnumerable GetComponentTypes() { return this.Components.Keys; } } }