using System; using System.Linq; using Svelto.DataStructures; using Svelto.ECS; using TechbloxModdingAPI.Common; namespace TechbloxModdingAPI.Utility { public ref struct OptionalRef where T : struct, IBaseEntityComponent { private readonly EGID entityId; private readonly State state; private readonly uint index; private NB array; private MB managedArray; private readonly EntityInitializer initializer; //The possible fields are: (index && (array || managedArray)) || initializer private readonly EcsObjectBase obj; public OptionalRef(NB array, uint index, EGID entityId = default) { state = State.Native; this.array = array; this.index = index; this.entityId = entityId; initializer = default; managedArray = default; obj = default; } public OptionalRef(MB array, uint index, EGID entityId = default) { state = State.Managed; managedArray = array; this.index = index; this.entityId = entityId; initializer = default; this.array = default; obj = default; } /// /// Wraps the initializer data, if present. /// /// The object with the initializer /// Whether the struct is unmanaged public OptionalRef(EcsObjectBase obj, bool unmanaged, EGID entityId = default) { this.entityId = entityId; if (obj.InitData.Valid) { initializer = obj.InitData.Initializer(obj.Id); state = (unmanaged ? State.Native : State.Managed) | State.Initializer; } else { initializer = default; state = State.Empty; } array = default; index = default; managedArray = default; this.obj = obj; } /// /// Returns the value or a default value if empty. Supports objects that are being initialized. /// /// The value or the default value public ref T Get() { CompRefCache.Default = default; //The default value can be changed by mods if (state == State.Empty) return ref CompRefCache.Default; // If initializing the entity, check if the component is allowed by the descriptor, otherwise it could cause // issues in the game with Add() calls running unexpectedly if ((state & State.Initializer) != State.Empty && obj.AllowedEntityComponents.Contains(typeof(T))) return ref initializer.GetOrAdd(); if ((state & State.Native) != State.Empty) return ref array[index]; return ref managedArray[index]; } /// /// A non-by-ref view that allows getting and setting the value. /// public T Value { get => Get(); set => Get() = value; } /// /// The ID of the entity this component belongs to or default if it doesn't exist. /// public EGID EGID => entityId; public bool Exists => state != State.Empty; public T? Nullable() => this ? Get() : default; public static implicit operator T(OptionalRef opt) => opt.Get(); public static implicit operator bool(OptionalRef opt) => opt.state != State.Empty; public static implicit operator EGID(OptionalRef opt) => opt.entityId; /// /// Creates an instance of a struct T that can be referenced. /// private struct CompRefCache { public static T Default; } /// /// A byte that holds state in its bits. /// [Flags] private enum State : byte { Empty, Native, Managed, Initializer = 4 } } }