474 lines
16 KiB
C#
474 lines
16 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.Threading;
|
|
|
|
namespace MfGames.Gallium
|
|
{
|
|
/// <summary>
|
|
/// A low-overhead entity with identification.
|
|
/// </summary>
|
|
public record Entity
|
|
{
|
|
/// <inheritdoc />
|
|
public virtual bool Equals(Entity? other)
|
|
{
|
|
if (ReferenceEquals(null, other))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (ReferenceEquals(this, other))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return this.Id == other.Id;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override int GetHashCode()
|
|
{
|
|
return this.Id;
|
|
}
|
|
|
|
private ImmutableDictionary<Type, object> Components { get; set; }
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
private static int nextId;
|
|
|
|
public Entity()
|
|
: this(Interlocked.Increment(ref nextId))
|
|
{
|
|
}
|
|
|
|
private Entity(int id)
|
|
{
|
|
this.Id = id;
|
|
this.Components = ImmutableDictionary.Create<Type, object>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether the entity has a specific type of
|
|
/// component registered.
|
|
/// </summary>
|
|
/// <typeparam name="T1">The component type.</typeparam>
|
|
/// <returns>True if the type exists, otherwise false.</returns>
|
|
public bool Has<T1>()
|
|
{
|
|
return this.Has(typeof(T1));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether the entity has components of the given types
|
|
/// registered.
|
|
/// </summary>
|
|
/// <typeparam name="T1">The first component type.</typeparam>
|
|
/// <typeparam name="T2">The second component type.</typeparam>
|
|
/// <returns>
|
|
/// True if there are components of the given type exists, otherwise
|
|
/// false.
|
|
/// </returns>
|
|
public bool HasAll<T1, T2>()
|
|
{
|
|
return this.HasAll(typeof(T1), typeof(T2));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether the entity has components of the given types
|
|
/// registered.
|
|
/// </summary>
|
|
/// <typeparam name="T1">The first component type.</typeparam>
|
|
/// <typeparam name="T2">The second component type.</typeparam>
|
|
/// <typeparam name="T3">The third component type.</typeparam>
|
|
/// <returns>
|
|
/// True if there are components of the given type exists, otherwise
|
|
/// false.
|
|
/// </returns>
|
|
public bool HasAll<T1, T2, T3>()
|
|
{
|
|
return this.HasAll(typeof(T1), typeof(T2), typeof(T3));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether the entity has components of the given types
|
|
/// registered.
|
|
/// </summary>
|
|
/// <typeparam name="T1">The first component type.</typeparam>
|
|
/// <typeparam name="T2">The second component type.</typeparam>
|
|
/// <typeparam name="T3">The third component type.</typeparam>
|
|
/// <typeparam name="T4">The third component type.</typeparam>
|
|
/// <returns>
|
|
/// True if there are components of the given type exists, otherwise
|
|
/// false.
|
|
/// </returns>
|
|
public bool HasAll<T1, T2, T3, T4>()
|
|
{
|
|
return this.HasAll(typeof(T1), typeof(T2), typeof(T3), typeof(T4));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether the entity has a specific type of
|
|
/// component registered.
|
|
/// </summary>
|
|
/// <param name="type">The component type.</param>
|
|
/// <returns>True if the type exists, otherwise false.</returns>
|
|
public bool Has(Type type)
|
|
{
|
|
return this.Components.ContainsKey(type);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether the entity has components for all the given
|
|
/// types.
|
|
/// </summary>
|
|
/// <param name="t1">The component type.</param>
|
|
/// <param name="t2">The component type.</param>
|
|
/// <returns>True if the type exists, otherwise false.</returns>
|
|
public bool HasAll(
|
|
Type t1,
|
|
Type t2)
|
|
{
|
|
return this.Has(t1) && this.Components.ContainsKey(t2);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether the entity has components for all the given
|
|
/// types.
|
|
/// </summary>
|
|
/// <param name="t1">The component type.</param>
|
|
/// <param name="t2">The component type.</param>
|
|
/// <param name="t3">The component type.</param>
|
|
/// <returns>True if the type exists, otherwise false.</returns>
|
|
public bool HasAll(
|
|
Type t1,
|
|
Type t2,
|
|
Type t3)
|
|
{
|
|
return this.HasAll(t1, t2) && this.Components.ContainsKey(t3);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether the entity has components for all the given
|
|
/// types.
|
|
/// </summary>
|
|
/// <param name="t1">The component type.</param>
|
|
/// <param name="t2">The component type.</param>
|
|
/// <param name="t3">The component type.</param>
|
|
/// <param name="t4">The component type.</param>
|
|
/// <returns>True if the type exists, otherwise false.</returns>
|
|
public bool HasAll(
|
|
Type t1,
|
|
Type t2,
|
|
Type t3,
|
|
Type t4)
|
|
{
|
|
return this.HasAll(t1, t2, t3) && this.Components.ContainsKey(t4);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves a registered component of the given type.
|
|
/// </summary>
|
|
/// <typeparam name="TType">The component type.</typeparam>
|
|
/// <returns>The registered object.</returns>
|
|
public TType Get<TType>()
|
|
{
|
|
return (TType)this.Components[typeof(TType)];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves a registered component of the given type and casts it to
|
|
/// TType.
|
|
/// </summary>
|
|
/// <param name="type">The component key.</param>
|
|
/// <typeparam name="TType">The component type.</typeparam>
|
|
/// <returns>The registered object.</returns>
|
|
public TType Get<TType>(Type type)
|
|
{
|
|
return (TType)this.Components[type];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the number of components registered in the entity.
|
|
/// </summary>
|
|
public int Count => this.Components.Count;
|
|
|
|
/// <summary>
|
|
/// Gets the given component type if inside the entity, otherwise the
|
|
/// default value.
|
|
/// </summary>
|
|
/// <typeparam name="TType">The component type.</typeparam>
|
|
/// <returns>The found component or default (typically null).</returns>
|
|
public TType? GetOptional<TType>()
|
|
{
|
|
return this.Has<TType>() ? this.Get<TType>() : default;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="value">The value if contained in the entity.</param>
|
|
/// <typeparam name="T1">The component type.</typeparam>
|
|
/// <returns>True if found, otherwise false.</returns>
|
|
public bool TryGet<T1>(out T1 value)
|
|
{
|
|
if (this.Has<T1>())
|
|
{
|
|
value = this.Get<T1>();
|
|
|
|
return true;
|
|
}
|
|
|
|
value = default!;
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="value1">The value if contained in the entity.</param>
|
|
/// <param name="value2">The value if contained in the entity.</param>
|
|
/// <typeparam name="T1">The first component type.</typeparam>
|
|
/// <typeparam name="T2">The second component type.</typeparam>
|
|
/// <returns>True if found, otherwise false.</returns>
|
|
public bool TryGet<T1, T2>(
|
|
out T1 value1,
|
|
out T2 value2)
|
|
{
|
|
if (this.HasAll<T1, T2>())
|
|
{
|
|
value1 = this.Get<T1>();
|
|
value2 = this.Get<T2>();
|
|
|
|
return true;
|
|
}
|
|
|
|
value1 = default!;
|
|
value2 = default!;
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="value1">The value if contained in the entity.</param>
|
|
/// <param name="value2">The value if contained in the entity.</param>
|
|
/// <param name="value3">The value if contained in the entity.</param>
|
|
/// <typeparam name="T1">The first component type.</typeparam>
|
|
/// <typeparam name="T2">The second component type.</typeparam>
|
|
/// <typeparam name="T3">The third component type.</typeparam>
|
|
/// <returns>True if found, otherwise false.</returns>
|
|
public bool TryGet<T1, T2, T3>(
|
|
out T1 value1,
|
|
out T2 value2,
|
|
out T3 value3)
|
|
{
|
|
if (this.HasAll<T1, T2, T3>())
|
|
{
|
|
value1 = this.Get<T1>();
|
|
value2 = this.Get<T2>();
|
|
value3 = this.Get<T3>();
|
|
|
|
return true;
|
|
}
|
|
|
|
value1 = default!;
|
|
value2 = default!;
|
|
value3 = default!;
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="value1">The value if contained in the entity.</param>
|
|
/// <param name="value2">The value if contained in the entity.</param>
|
|
/// <param name="value3">The value if contained in the entity.</param>
|
|
/// <param name="value4">The value if contained in the entity.</param>
|
|
/// <typeparam name="T1">The first component type.</typeparam>
|
|
/// <typeparam name="T2">The second component type.</typeparam>
|
|
/// <typeparam name="T3">The third component type.</typeparam>
|
|
/// <typeparam name="T4">The fourth component type.</typeparam>
|
|
/// <returns>True if found, otherwise false.</returns>
|
|
public bool TryGet<T1, T2, T3, T4>(
|
|
out T1 value1,
|
|
out T2 value2,
|
|
out T3 value3,
|
|
out T4 value4)
|
|
{
|
|
if (this.HasAll<T1, T2, T3, T4>())
|
|
{
|
|
value1 = this.Get<T1>();
|
|
value2 = this.Get<T2>();
|
|
value3 = this.Get<T3>();
|
|
value4 = this.Get<T4>();
|
|
|
|
return true;
|
|
}
|
|
|
|
value1 = default!;
|
|
value2 = default!;
|
|
value3 = default!;
|
|
value4 = default!;
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the component in the entity, regardless if there was a
|
|
/// component already registered.
|
|
/// </summary>
|
|
/// <param name="component">The component to register.</param>
|
|
/// <typeparam name="T1">The component type.</typeparam>
|
|
/// <returns>The entity for chaining.</returns>
|
|
/// <exception cref="ArgumentNullException"></exception>
|
|
public Entity Set<T1>(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),
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a component to the entity.
|
|
/// </summary>
|
|
/// <param name="component">The component to register.</param>
|
|
/// <typeparam name="T1">The component type.</typeparam>
|
|
/// <returns>
|
|
/// The same entity if the component is already registered, otherwise a
|
|
/// cloned entity with the new component.
|
|
/// </returns>
|
|
/// <exception cref="ArgumentNullException"></exception>
|
|
public Entity Add<T1>(T1 component)
|
|
{
|
|
if (component == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(component));
|
|
}
|
|
|
|
if (this.Has<T1>())
|
|
{
|
|
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),
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a component to the entity.
|
|
/// </summary>
|
|
/// <typeparam name="TType">The component type.</typeparam>
|
|
/// <returns>
|
|
/// The same entity if the component is already removed, otherwise a
|
|
/// cloned entity without the new component.
|
|
/// </returns>
|
|
/// <exception cref="ArgumentNullException"></exception>
|
|
public Entity Remove<TType>()
|
|
{
|
|
return this.Remove(typeof(TType));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a component to the entity.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// The same entity if the component is already removed, otherwise a
|
|
/// cloned entity without the new component.
|
|
/// </returns>
|
|
/// <param name="type">The component type to remove.</param>
|
|
public Entity Remove(Type type)
|
|
{
|
|
if (!this.Has(type))
|
|
{
|
|
return this;
|
|
}
|
|
|
|
return this with
|
|
{
|
|
Components = this.Components.Remove(type),
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the identifier of the entity. This should be treated as an
|
|
/// opaque field.
|
|
/// </summary>
|
|
public int Id { get; private init; }
|
|
|
|
/// <summary>
|
|
/// Creates a copy of the entity, including copying the identifier.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public Entity ExactCopy()
|
|
{
|
|
return this with { };
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a copy of the entity, including components, but with a new
|
|
/// identifier.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public Entity Copy()
|
|
{
|
|
return this with
|
|
{
|
|
Id = Interlocked.Increment(ref nextId),
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves a list of the component types currently registered in the
|
|
/// Entity.
|
|
/// </summary>
|
|
/// <returns>An enumerable of the various component keys.</returns>
|
|
public IEnumerable<Type> GetComponentTypes()
|
|
{
|
|
return this.Components.Keys;
|
|
}
|
|
}
|
|
}
|