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;
}
}
}