Add ultimate xr

This commit is contained in:
2024-08-06 21:58:35 +02:00
parent 864033bf10
commit 7165bacd9d
3952 changed files with 2162037 additions and 35 deletions

View File

@@ -0,0 +1,113 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IUxrUniqueId.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UnityEngine;
namespace UltimateXR.Core.Unique
{
/// <summary>
/// Interface for components that can be uniquely identified using an Id.<br />
/// To leverage the implementation of this interface, consider using <see cref="UxrUniqueIdImplementer{T}" />.<br />
/// <see cref="UxrUniqueIdImplementer.TryGetComponentById" /> can be used at runtime to get a component based on the
/// unique Id.<br />
/// </summary>
public interface IUxrUniqueId
{
#region Public Types & Data
/// <summary>
/// Gets the component unique Id.
/// Use <see cref="ChangeUniqueId" /> to change the unique ID at runtime.
/// </summary>
Guid UniqueId { get; }
/// <summary>
/// Gets the Guid that was used as parameter for <see cref="CombineUniqueId" />. <see cref="Guid.Empty" /> if the
/// UniqueId was not combined.
/// </summary>
Guid CombineIdSource { get; }
/// <summary>
/// Gets the prefab Id assigned by Unity if the component is in a prefab. If the component is in an instance in the
/// scene it gets the Id of the prefab it was originally instantiated from.
/// </summary>
string UnityPrefabId { get; }
/// <summary>
/// Gets the Component. We consider a component a type that inherits from Behaviour instead of Component, which
/// simplifies some things like access to enabled or isActiveAndEnabled.
/// </summary>
Behaviour Component { get; }
/// <summary>
/// Gets the GameObject.
/// </summary>
GameObject GameObject { get; }
/// <summary>
/// Gets the Transform.
/// </summary>
Transform Transform { get; }
/// <summary>
/// Gets whether the unique ID of the component is generated based on the full type name instead of randomly.
/// This can be used, for example, to ensure singletons have always the same Unique ID.
/// </summary>
bool UniqueIdIsTypeName { get; }
#endregion
#region Public Methods
/// <summary>
/// Registers the component, making sure that its Unique ID is available to exchange synchronization messages.
/// If the component was already registered, the call is ignored.
/// Components are registered automatically without user intervention, unless they are instantiated at runtime and
/// initially disabled. Use this method to register the component ahead of time when it's initially disabled if
/// necessary.
/// </summary>
void RegisterIfNecessary();
/// <summary>
/// Unregisters the component, removing the Unique ID from the internal list. Components are unregistered manually,
/// use this method to unregister the ID ahead of time if necessary.
/// </summary>
void Unregister();
/// <summary>
/// Tries to change the component's unique Id.
/// </summary>
/// <param name="newUniqueId">New id</param>
/// <returns>
/// The new unique ID. If the requested unique already existed, the returned value will be different
/// to make sure it is unique.
/// </returns>
Guid ChangeUniqueId(Guid newUniqueId);
/// <summary>
/// Changes the component's unique Id by combining it with another Id, optionally also changing all other components in
/// the same GameObject and its children. The combination is a mathematical operation that will use the original Id and
/// the provided Id to generate a new Id.<br />
/// When instantiating a same prefab multiple times, for example a player prefab in a multiplayer environment, the
/// different instances require new Ids that have to be the same on all devices.
/// All instances share the same source Ids coming from the prefab, but will require different Ids to tell one from
/// another once instantiated. Furthermore, these Ids need to be the same on all devices to make sure they can be
/// synchronized correctly.
/// The combination operation will use a Guid to change the component, ensuring that by using the same ID on other
/// devices the resulting Ids will be the same. When using a recursive combination and the GameObject has multiple
/// components implementing the <see cref="IUxrUniqueId" /> interface, the result will be the same no matter which
/// component the combination gets called on.
/// </summary>
/// <param name="guid">Id to combine the the original Ids with</param>
/// <param name="recursive">
/// Whether to also change the unique Ids of the components in the same GameObject and all children, based on the new
/// unique Id.
/// </param>
void CombineUniqueId(Guid guid, bool recursive = true);
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c39331d0ed37c6745923cff1ba812430
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,199 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrUniqueIdImplementer.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using UltimateXR.Core.Components;
using UltimateXR.Core.Settings;
using UnityEngine;
namespace UltimateXR.Core.Unique
{
/// <summary>
/// Base class for <see cref="UxrUniqueIdImplementer{T}" />.
/// </summary>
public abstract class UxrUniqueIdImplementer
{
#region Public Types & Data
/// <summary>
/// Gets all registered components that implement the <see cref="IUxrUniqueId" /> interface.
/// This includes all <see cref="UxrComponent" /> components and all user custom classes that implement
/// <see cref="IUxrUniqueId" /> using <see cref="UxrUniqueIdImplementer{T}" />.
/// </summary>
public static IEnumerable<IUxrUniqueId> AllComponents
{
get
{
foreach (KeyValuePair<Type, UxrUniqueIdImplementer> pair in s_implementerTypes)
{
foreach (IUxrUniqueId unique in pair.Value.GetAllComponentsInternal())
{
yield return unique;
}
}
}
}
/// <summary>
/// Gets or sets the unique Id used for combination by <see cref="UxrUniqueIdImplementer{T}.CombineUniqueId"/>.
/// </summary>
public Guid CombineIdSource { get; set; }
/// <summary>
/// Gets or sets the original unique Id.
/// </summary>
public Guid OriginalUniqueId { get; set; }
/// <summary>
/// Gets or sets the component that was initialized.
/// </summary>
public IUxrUniqueId InitializedComponent { get; set; }
#endregion
#region Public Methods
/// <summary>
/// Generates a new unique Id.
/// </summary>
/// <returns>Unique Id</returns>
public static Guid GetNewUniqueId()
{
return Guid.NewGuid();
}
/// <summary>
/// Finds a component using its unique ID. The component should implement the <see cref="IUxrUniqueId" /> interface,
/// such
/// as <see cref="UxrComponent" />.
/// </summary>
/// <param name="id">Component's unique ID</param>
/// <param name="component">Returns the component or null if the ID wasn't found</param>
/// <returns>Whether the given ID was found and a component is returned</returns>
public static bool TryGetComponentById(Guid id, out IUxrUniqueId component)
{
// Iterate over component types. By default, it will contain the UxrComponent implementer only.
foreach (KeyValuePair<Type, UxrUniqueIdImplementer> pair in s_implementerTypes)
{
if (pair.Value.TryGetComponentByIdInternal(id, out component))
{
return true;
}
}
component = null;
return false;
}
/// <summary>
/// Finds a component using its unique ID. The component should implement the <see cref="IUxrUniqueId" /> interface,
/// such
/// as <see cref="UxrComponent" />.
/// </summary>
/// <param name="id">Component's unique ID</param>
/// <param name="component">Returns the component or null if the ID wasn't found</param>
/// <returns>Whether the given ID was found and a component is returned</returns>
public static bool TryGetComponentById<T>(Guid id, out T component) where T : Component, IUxrUniqueId
{
foreach (KeyValuePair<Type, UxrUniqueIdImplementer> pair in s_implementerTypes)
{
if (pair.Value.TryGetComponentByIdInternal(id, out IUxrUniqueId unique))
{
component = unique as T;
if (component == null)
{
if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.CoreModule} {nameof(UxrUniqueIdImplementer)}.{nameof(TryGetComponentById)} type mismatch with ID {id}. Expected type is ({typeof(T).Name}) and actual type is ({unique.GetType().Name}).");
}
return false;
}
}
}
component = default;
return false;
}
#endregion
#region Protected Methods
/// <summary>
/// Tries to find a component using the unique ID in an <see cref="UxrUniqueIdImplementer{T}" /> implementation.
/// </summary>
/// <param name="id">ID</param>
/// <param name="component">Returns the component</param>
/// <returns></returns>
protected abstract bool TryGetComponentByIdInternal(Guid id, out IUxrUniqueId component);
/// <summary>
/// Returns all components of the type in an <see cref="UxrUniqueIdImplementer{T}" /> implementation.
/// </summary>
/// <returns>Returns all registered components of the type in an <see cref="UxrUniqueIdImplementer{T}" /> implementation</returns>
protected abstract IEnumerable<IUxrUniqueId> GetAllComponentsInternal();
/// <summary>
/// Registers an implementer type if it wasn't already registered. This allows to statically get components of custom
/// types based on their ID using <see cref="TryGetComponentById" />.
/// Also registers the implementer so that it can be retrieved using the component as key.
/// </summary>
/// <param name="targetComponent">The target component</param>
/// <param name="implementer">The implementer</param>
protected void RegisterImplementer<T>(T targetComponent, UxrUniqueIdImplementer<T> implementer) where T : Component, IUxrUniqueId
{
if (!s_implementerTypes.ContainsKey(typeof(T)))
{
s_implementerTypes.Add(typeof(T), implementer);
}
if (!s_allImplementers.ContainsKey(targetComponent))
{
s_allImplementers.Add(targetComponent, implementer);
}
}
/// <summary>
/// Unregisters the implementer.
/// </summary>
/// <param name="targetComponent">The target component</param>
/// <param name="implementer">The implementer</param>
/// <typeparam name="T">The component type</typeparam>
protected void UnregisterImplementer<T>(T targetComponent, UxrUniqueIdImplementer<T> implementer) where T : Component, IUxrUniqueId
{
s_allImplementers.Remove(targetComponent);
}
#endregion
#region Protected Types & Data
/// <summary>
/// Static dictionary of path collisions to ensure unique IDs are generated.
/// </summary>
protected static readonly Dictionary<Guid, int> s_idCollisions = new Dictionary<Guid, int>();
#endregion
#region Private Types & Data
/// <summary>
/// Contains the first implementer registered for each type, including <see cref="UxrComponent" /> and any user custom
/// types.
/// </summary>
private static readonly Dictionary<Type, UxrUniqueIdImplementer> s_implementerTypes = new Dictionary<Type, UxrUniqueIdImplementer>();
/// <summary>
/// Contains the implementer for each component.
/// </summary>
private static readonly Dictionary<IUxrUniqueId, UxrUniqueIdImplementer> s_allImplementers = new Dictionary<IUxrUniqueId, UxrUniqueIdImplementer>();
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2f925cc341c6f0f43a5f3770f2f4086b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,503 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrUniqueIdImplementer_1.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using UltimateXR.Core.Components;
using UltimateXR.Extensions.System;
using UltimateXR.Extensions.Unity;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace UltimateXR.Core.Unique
{
/// <summary>
/// Helper class simplifying the implementation of the <see cref="IUxrUniqueId" /> interface.
/// This class includes functionality to leverage the generation of unique IDs and to keep track of
/// all components using their ID. Any component can be retrieved using the ID only.
/// In scenarios where custom classes cannot inherit from <see cref="UxrComponent" /> to benefit from
/// these unique ID capabilities, this class is designed to implement the interface.
/// </summary>
public class UxrUniqueIdImplementer<T> : UxrUniqueIdImplementer where T : Component, IUxrUniqueId
{
#region Public Types & Data
/// <summary>
/// Gets the global dictionary where all components of type T are indexed by their id.
/// </summary>
public static IReadOnlyDictionary<Guid, T> ComponentsById => s_componentsById;
/// <summary>
/// Gets whether the component was unregistered using <see cref="Unregister"/>.
/// </summary>
public bool IsUnregistered { get; private set; }
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="targetComponent">Target component for all the methods called on this object</param>
public UxrUniqueIdImplementer(T targetComponent)
{
_targetComponent = targetComponent;
RegisterImplementer(targetComponent, this);
}
/// <summary>
/// Default constructor is private to use public constructor with target component.
/// </summary>
private UxrUniqueIdImplementer()
{
}
#endregion
#region Public Methods
/// <summary>
/// Checks if the component has been registered, and registers it if not.
/// The main reason why it exists is to make sure that it has been called if ChangeUniqueId()
/// changes the ID. Since this might happen right after instantiation, before Awake() gets called at
/// the end of the frame, it might need to be called there before.
/// </summary>
/// <param name="component">Component to initialize</param>
/// <param name="getImplementer">A function that gets the implementer from a component</param>
/// <param name="assignId">
/// An action that allows to assign a unique ID to a component. This avoids exposing the
/// unique ID as public.
/// </param>
/// <param name="onChanging">
/// An optional delegate that will be called right before assigning a new ID to a component.
/// The parameters passed are the component, the old ID and the new ID.
/// </param>
/// <param name="onChanged">
/// An optional delegate that will be called right after a new ID has been assigned to a component.
/// The parameters passed are the component, the old ID and the new ID.
/// </param>
/// <param name="onRegistering">
/// An optional delegate that will be called right before registering a new component with its
/// ID. The parameter passed is the component.
/// </param>
/// <param name="onRegistered">
/// An optional delegate that will be called right after registering a new component with its
/// ID. The parameter passed is the component.
/// </param>
/// <param name="defaultGuid">
/// The unique Id to assign if the unique ID is not initialized.
/// This parameter will be ignored if the component uses <see cref="IUxrUniqueId.UniqueIdIsTypeName" />.
/// If the parameter is the default value, it will generate a new Id based on the unique scene path.
/// </param>
public void InitializeUniqueIdIfNecessary(T component,
Func<T, UxrUniqueIdImplementer> getImplementer,
Action<T, Guid> assignId,
Action<T, Guid, Guid> onChanging = null,
Action<T, Guid, Guid> onChanged = null,
Action<T> onRegistering = null,
Action<T> onRegistered = null,
Guid defaultGuid = default)
{
UxrUniqueIdImplementer implementer = getImplementer(component);
if (ReferenceEquals(implementer.InitializedComponent, component))
{
return;
}
// Not yet generated and serialized?
if (component.UniqueId == default)
{
if (component.UniqueIdIsTypeName)
{
// Unique ID will be based on the type name. Good for singletons.
assignId(component, component.GetType().FullName.GetGuid());
}
else if (defaultGuid != default)
{
// Assign user-defined Guid. If it causes collisions, these will be handled in RegisterUniqueId().
assignId(component, defaultGuid);
}
else
{
// Fallback: Generate ID using unique scene path.
// Warning: This is not 100% safe for several reasons:
// -In Unity root GameObjects don't have a sibling index at runtime
// -Index in root GameObjects is not consistent across platforms
// -Can create collisions when instantiating, but these are taken care of in RegisterUniqueId().
assignId(component, component.GetUniqueScenePath().GetGuid());
}
}
// Store original unique ID
implementer.OriginalUniqueId = component.UniqueId;
// Register, taking care of collisions
RegisterUniqueId(component, assignId, onChanging, onChanged, onRegistering, onRegistered, component.UniqueId);
implementer.InitializedComponent = component;
}
/// <summary>
/// Tries to change the object's unique Id, ensuring no collisions with an existing Id.
/// </summary>
/// <param name="newUniqueId">New id to try to assign</param>
/// <param name="getImplementer">A function that gets the implementer from a component</param>
/// <param name="assignId">
/// An action that allows to assign a unique ID to a component. This avoids exposing the
/// unique ID as public.
/// </param>
/// <param name="onChanging">
/// An optional delegate that will be called right before assigning a new ID to a component.
/// The parameters passed are the component, the old ID and the new ID.
/// </param>
/// <param name="onChanged">
/// An optional delegate that will be called right after a new ID has been assigned to a component.
/// The parameters passed are the component, the old ID and the new ID.
/// </param>
/// <param name="onRegistering">
/// An optional delegate that will be called right before registering a new component with its
/// ID. The parameter passed is the component.
/// </param>
/// <param name="onRegistered">
/// An optional delegate that will be called right after registering a new component with its
/// ID. The parameter passed is the component.
/// </param>
/// <returns>
/// The new unique ID. If the requested unique already existed, the returned value will be different
/// to make sure it is unique
/// </returns>
public Guid ChangeUniqueId(Guid newUniqueId,
Func<T, UxrUniqueIdImplementer> getImplementer,
Action<T, Guid> assignId,
Action<T, Guid, Guid> onChanging = null,
Action<T, Guid, Guid> onChanged = null,
Action<T> onRegistering = null,
Action<T> onRegistered = null)
{
// If called during edit-time, simply generate unique IDs
if (!Application.isPlaying)
{
assignId.Invoke(_targetComponent, GetNewUniqueId());
return _targetComponent.UniqueId;
}
// At runtime, generate new ID for the component.
// Make sure original ID has been initialized.
InitializeUniqueIdIfNecessary(_targetComponent, getImplementer, assignId, onChanging, onChanged, onRegistering, onRegistered, newUniqueId);
// Register new ID.
RegisterUniqueId(_targetComponent, assignId, onChanging, onChanged, onRegistering, onRegistered, newUniqueId);
return _targetComponent.UniqueId;
}
/// <summary>
/// <see cref="IUxrUniqueId.CombineUniqueId" />.
/// </summary>
/// <param name="guid">Id to combine the existing Ids with</param>
/// <param name="getImplementer">A function that gets the implementer from a component</param>
/// <param name="assignId">
/// An action that allows to assign a unique ID to a component. This avoids exposing the
/// unique ID as public.
/// </param>
/// <param name="onChanging">
/// An optional delegate that will be called right before assigning a new ID to a component.
/// The parameters passed are the component, the old ID and the new ID.
/// </param>
/// <param name="onChanged">
/// An optional delegate that will be called right after a new ID has been assigned to a component.
/// The parameters passed are the component, the old ID and the new ID.
/// </param>
/// <param name="onRegistering">
/// An optional delegate that will be called right before registering a new component with its
/// ID. The parameter passed is the component.
/// </param>
/// <param name="onRegistered">
/// An optional delegate that will be called right after registering a new component with its
/// ID. The parameter passed is the component.
/// </param>
/// <param name="recursive">
/// Whether to change also the unique Ids of the child components in the same GameObject
/// and all children.
/// </param>
public void CombineUniqueId(Guid guid,
Func<T, UxrUniqueIdImplementer> getImplementer,
Action<T, Guid> assignId,
Action<T, Guid, Guid> onChanging,
Action<T, Guid, Guid> onChanged,
Action<T> onRegistering,
Action<T> onRegistered,
bool recursive)
{
T[] components = recursive ? _targetComponent.GetComponentsInChildren<T>(true) : new[] { _targetComponent };
foreach (T unique in components)
{
// Make sure original ID has been initialized.
InitializeUniqueIdIfNecessary(unique, getImplementer, assignId, onChanging, onChanged, onRegistering, onRegistered);
UxrUniqueIdImplementer implementer = getImplementer(unique);
if (implementer != null)
{
Guid originalUniqueId = implementer.OriginalUniqueId != default ? implementer.OriginalUniqueId : unique.UniqueId;
implementer.CombineIdSource = guid;
// Ensure the same ID on all devices using the combination.
// We also replace the OriginalUniqueId because it works better when using CombineUniqueId multiple times over a hierarchy.
Guid combinedGuid = GuidExt.Combine(originalUniqueId, guid);
RegisterUniqueId(unique, assignId, onChanging, onChanged, onRegistering, onRegistered, combinedGuid);
implementer.OriginalUniqueId = combinedGuid;
}
}
}
/// <summary>
/// To be called from the component's OnValidate() Unity method. This will update come key variables used for unique ID
/// generation.
/// </summary>
/// <param name="assignId">
/// An action that allows to assign a unique ID to a component. This avoids exposing the
/// unique ID as public.
/// </param>
/// <param name="refIsInPrefab">
/// Reference to the component boolean that will tell whether the component lies in a prefab or
/// is instantiated in the scene
/// </param>
/// <param name="refPrefabGuid">
/// Reference to the string that will tell the GUID assigned by Unity to the prefab, if the
/// component lies in a prefab
/// </param>
public void NotifyOnValidate(Action<T, Guid> assignId, ref bool refIsInPrefab, ref string refPrefabGuid)
{
#if UNITY_EDITOR
Guid InternalGetUniqueId()
{
if (_targetComponent.UniqueIdIsTypeName)
{
// Unique ID will be based on the type name. This is useful for singletons that are instantiated dynamically, in order to ensure same values on all devices.
return _targetComponent.GetType().FullName.GetGuid();
}
// Unique ID is generated randomly.
return GetNewUniqueId();
}
if (EditorPrefs.GetBool(UxrConstants.Editor.AutomaticIdGenerationPrefs, true) &&
!EditorApplication.isPlayingOrWillChangePlaymode &&
!EditorApplication.isCompiling &&
!BuildPipeline.isBuildingPlayer &&
!EditorApplication.isUpdating)
{
if (_targetComponent.gameObject.scene.name != null && !_targetComponent.gameObject.scene.isLoaded)
{
// Returning from play-mode, re-loading after build, among others.
return;
}
bool setDirty = false;
if (_targetComponent.UniqueId == default)
{
// Generate unique ID
assignId(_targetComponent, InternalGetUniqueId());
setDirty = true;
}
// Check if a prefab was instantiated or an object was made prefab, to generate a different ID.
// We want to avoid multiple instantiated prefabs to share the same ID.
try
{
// Sometimes, in prefab mode, GetPrefabGuid() can throw an exception when accessing
// PrefabStageUtility.GetCurrentPrefabStage().prefabContentsRoot during OnValidate().
// It seems to happen when loading the prefab for the first time.
if (_targetComponent.GetPrefabGuid(out string prefabGuid, out string _))
{
if (_targetComponent.IsInPrefab() != refIsInPrefab || prefabGuid != refPrefabGuid)
{
assignId(_targetComponent, InternalGetUniqueId());
refIsInPrefab = _targetComponent.IsInPrefab();
refPrefabGuid = prefabGuid;
setDirty = true;
}
}
}
catch (Exception)
{
// Ignore exception.
}
if (setDirty)
{
EditorUtility.SetDirty(_targetComponent);
}
}
#endif
}
/// <summary>
/// Unregisters the target component unique ID.
/// </summary>
public void Unregister()
{
s_componentsById.Remove(_targetComponent.UniqueId);
UnregisterImplementer(_targetComponent, this);
IsUnregistered = true;
}
#endregion
#region Protected Overrides UxrUniqueIdImplementer
/// <inheritdoc />
protected override bool TryGetComponentByIdInternal(Guid id, out IUxrUniqueId component)
{
if (s_componentsById.TryGetValue(id, out T c))
{
component = c;
return true;
}
component = null;
return false;
}
/// <inheritdoc />
protected override IEnumerable<IUxrUniqueId> GetAllComponentsInternal()
{
foreach (KeyValuePair<Guid, T> pair in s_componentsById)
{
yield return pair.Value;
}
}
#endregion
#region Private Methods
/// <summary>
/// Returns a generated unique ID based on the given component, taking care of collisions.
/// </summary>
/// <param name="component">The component to process the unique ID for</param>
/// <param name="assignId">
/// An action that allows to assign a unique ID to a component. This avoids exposing the
/// unique ID as public.
/// </param>
/// <param name="onChanging">
/// An optional delegate that will be called right before assigning a new ID to a component.
/// The parameters passed are the component, the old ID and the new ID.
/// </param>
/// <param name="onChanged">
/// An optional delegate that will be called right after a new ID has been assigned to a component.
/// The parameters passed are the component, the old ID and the new ID.
/// </param>
/// <param name="onRegistering">
/// An optional delegate that will be called right before registering a new component with its
/// ID. The parameter passed is the component.
/// </param>
/// <param name="onRegistered">
/// An optional delegate that will be called right after registering a new component with its
/// ID. The parameter passed is the component.
/// </param>
/// <param name="requestedId">
/// If non-null, it will try to assign this Id. If it already exists or is null, it will generate a new one.
/// </param>
/// <returns>New generated ID</returns>
private static Guid RegisterUniqueId(T component,
Action<T, Guid> assignId,
Action<T, Guid, Guid> onChanging,
Action<T, Guid, Guid> onChanged,
Action<T> onRegistering,
Action<T> onRegistered,
Guid requestedId)
{
Guid unprocessedId = requestedId == default ? GetNewUniqueId() : requestedId;
Guid newId = unprocessedId;
Guid unregisterId = default;
// First unregister if it was already registered
if (component.UniqueId != default && ComponentsById.TryGetValue(component.UniqueId, out T existingComponent) && component == existingComponent)
{
unregisterId = component.UniqueId;
}
// Try to get new unique ID
if (ComponentsById.ContainsKey(newId))
{
// Handle collisions
int collisionIterations = 0;
while (ComponentsById.ContainsKey(newId))
{
int collisionCount = s_idCollisions[unprocessedId];
collisionCount++;
collisionIterations++;
newId = GuidExt.Combine(unprocessedId, $"Collision{collisionCount}".GetGuid());
s_idCollisions[unprocessedId] = collisionCount;
}
s_idCollisions[newId] = 0;
}
else
{
// New unique ID not in use: Initialize or update collision dictionary for this ID
if (!s_idCollisions.TryAdd(unprocessedId, 0))
{
s_idCollisions[unprocessedId]++;
}
}
// Call ID change events
if (unregisterId != default)
{
onChanging?.Invoke(component, unregisterId, newId);
s_componentsById.Remove(unregisterId);
}
// Register new ID
onRegistering?.Invoke(component);
assignId.Invoke(component, newId);
s_componentsById[newId] = component;
onRegistered?.Invoke(component);
// Call ID change events
if (unregisterId != default)
{
onChanged?.Invoke(component, unregisterId, newId);
}
return newId;
}
#endregion
#region Private Types & Data
private static readonly Dictionary<Guid, T> s_componentsById = new Dictionary<Guid, T>();
private readonly T _targetComponent;
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 27a132f406a841f3ac4e3f825af67c63
timeCreated: 1705434583