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,3 @@
fileFormatVersion: 2
guid: 1ffe6caaf52d499d93f347aa1c9726f5
timeCreated: 1643819050

View File

@@ -0,0 +1,30 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IUxrPrecacheable.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using UnityEngine;
namespace UltimateXR.Core.Caching
{
/// <summary>
/// The <see cref="IUxrPrecacheable" /> interface is used in components that need to create instances at runtime and
/// want a way to precache them so that there aren't any hiccups on instantiation.
/// The <see cref="UxrManager" /> will look for <see cref="IUxrPrecacheable" /> components when the scene is loaded and
/// will instantiate and render the objects specified by <see cref="PrecachedInstances" /> a certain amount of frames
/// while the screen is still black.
/// This will make sure their resources (meshes, textures) are cached in order to minimize instantiation delays.
/// </summary>
public interface IUxrPrecacheable
{
#region Public Types & Data
/// <summary>
/// Gets the GameObjects, usually prefabs, that will be precached when the scene is loaded.
/// </summary>
IEnumerable<GameObject> PrecachedInstances { get; }
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 454ce8b03452c5b46bc1b817c1e84ed0
timeCreated: 1537182951
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d137c9b1b724424489edfe2883262f1e
timeCreated: 1643819108

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 342bbee74e734c30970bd3ce59da488b
timeCreated: 1644566116

View File

@@ -0,0 +1,155 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrAvatarComponent.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using System.Linq;
using UltimateXR.Avatar;
using UltimateXR.Extensions.Unity;
using UnityEngine;
namespace UltimateXR.Core.Components.Composite
{
/// <summary>
/// Generic base class for components that are added to an <see cref="UxrAvatar" /> and we want to keep track of.
/// It allows to enumerate components using static methods.
/// This class could have instead inherited from <see cref="UxrComponent{TP,TC}" /> but we avoided
/// this to not have redundancy between Avatar/Parent properties and methods.
/// </summary>
/// <typeparam name="T">Component type</typeparam>
public abstract class UxrAvatarComponent<T> : UxrComponent<T> where T : UxrAvatarComponent<T>
{
#region Public Types & Data
/// <summary>
/// Gets all the components, enabled or not, of this specific type that belong to the local avatar.
/// </summary>
/// <remarks>
/// Components that have never been enabled are not returned. Components are automatically registered through their
/// Awake() call, which is never called if the object has never been enabled. In this case it is recommended to resort
/// to <see cref="UnityEngine.GameObject.GetComponentsInChildren{T}(bool)">GetComponentsInChildren</see>.
/// </remarks>
public static IEnumerable<T> AllComponentsInLocalAvatar => AllComponents.Where(c => c.Avatar != null && c.Avatar.AvatarMode == UxrAvatarMode.Local);
/// <summary>
/// Gets all the enabled components of this specific type that belong to the local avatar.
/// </summary>
public static IEnumerable<T> EnabledComponentsInLocalAvatar => AllComponents.Where(c => c.Avatar != null && c.Avatar.AvatarMode == UxrAvatarMode.Local && c.isActiveAndEnabled);
/// <summary>
/// Gets the local avatar or null if there is none.
/// </summary>
public static UxrAvatar LocalAvatar
{
get
{
T component = AllComponents.FirstOrDefault(c => c.Avatar != null && c.Avatar.AvatarMode == UxrAvatarMode.Local);
return component == null ? null : component.Avatar;
}
}
/// <summary>
/// Gets all the components, enabled of not, of this specific type that belong to this instance of the avatar.
/// </summary>
/// <remarks>
/// Components that have never been enabled are not returned. Components are automatically registered through their
/// Awake() call, which is never called if the object has never been enabled. In this case it is recommended to resort
/// to <see cref="GameObject.GetComponentsInChildren{T}(bool)" />.
/// </remarks>
public IEnumerable<T> AllComponentsInAvatar => GetComponents(Avatar, true);
/// <summary>
/// Gets only the enabled components of this specific type that belong to this instance of the avatar.
/// </summary>
public IEnumerable<T> EnabledComponentsInAvatar => GetComponents(Avatar);
/// <summary>
/// Gets the <see cref="UxrAvatar" /> the component belongs to.
/// </summary>
public UxrAvatar Avatar
{
get
{
if (_avatar == null)
{
_avatar = this.SafeGetComponentInParent<UxrAvatar>();
}
return _avatar;
}
}
#endregion
#region Public Methods
/// <summary>
/// Gets the components of a specific avatar.
/// </summary>
/// <param name="avatar">Avatar to get the components from</param>
/// <param name="includeDisabled">Whether to include disabled components or not</param>
/// <returns>Components meeting the criteria</returns>
/// <remarks>
/// When using the <paramref name="includeDisabled" /> parameter, components that have never been enabled are not
/// returned. Components are automatically registered through their Awake() call, which is never called if the object
/// has never been enabled. In this case it is recommended to resort to
/// <see cref="GameObject.GetComponentsInChildren{T}(bool)" />.
/// </remarks>
public static IEnumerable<T> GetComponents(UxrAvatar avatar, bool includeDisabled = false)
{
if (includeDisabled)
{
return AllComponents.Where(c => c.Avatar == avatar);
}
return AllComponents.Where(c => c.Avatar == avatar && c.isActiveAndEnabled);
}
/// <summary>
/// Gets the components of a specific avatar.
/// </summary>
/// <param name="avatar">Avatar to get the components from</param>
/// <param name="includeDisabled">Whether to include disabled components or not</param>
/// <returns>Components meeting the criteria</returns>
/// <remarks>
/// When using the <paramref name="includeDisabled" /> parameter, components that have never been enabled are not
/// returned. Components are automatically registered through their Awake() call, which is never called if the object
/// has never been enabled. In this case it is recommended to resort to
/// <see cref="GameObject.GetComponentsInChildren{T}(bool)" />.
/// </remarks>
public static IEnumerable<TC> GetComponents<TC>(UxrAvatar avatar, bool includeDisabled = false) where TC : T
{
if (includeDisabled)
{
return AllComponents.OfType<TC>().Where(c => c.Avatar == avatar);
}
return AllComponents.OfType<TC>().Where(c => c.Avatar == avatar && c.isActiveAndEnabled);
}
#endregion
#region Unity
/// <summary>
/// Pre-caches the avatar component.
/// </summary>
protected override void Awake()
{
base.Awake();
if (_avatar == null)
{
_avatar = GetComponentInParent<UxrAvatar>();
}
}
#endregion
#region Private Types & Data
private UxrAvatar _avatar;
#endregion
}
}

View File

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

View File

@@ -0,0 +1,320 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrabbableObjectComponent.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Settings;
using UltimateXR.Manipulation;
using UltimateXR.Extensions.Unity;
using UnityEngine;
namespace UltimateXR.Core.Components.Composite
{
/// <summary>
/// Generic base class for components belonging to an object that also has a <see cref="UxrGrabbableObject" />
/// or in any of its parents. It allows to leverage some of the work related to accessing the
/// <see cref="UxrGrabbableObject" /> component and processing the events without the need to subscribe or
/// unsubscribe to them. Instead, events can be processed by overriding the different event triggers
/// (OnXXX methods).
/// <para>
/// The component has also all the benefits derived from <see cref="UxrComponent" />.
/// </para>
/// </summary>
/// <typeparam name="T">Component type</typeparam>
public abstract class UxrGrabbableObjectComponent<T> : UxrComponent<T> where T : UxrGrabbableObjectComponent<T>
{
#region Public Types & Data
/// <summary>
/// Gets whether the grabbable object is currently being grabbed.
/// </summary>
public bool IsBeingGrabbed => GrabbableObject && GrabbableObject.IsBeingGrabbed;
/// <summary>
/// Gets the grabbable object component.
/// </summary>
public UxrGrabbableObject GrabbableObject
{
get
{
if (_grabbableObject == null)
{
_grabbableObject = this.SafeGetComponentInParent<UxrGrabbableObject>();
}
return _grabbableObject;
}
private set => _grabbableObject = value;
}
#endregion
#region Unity
/// <summary>
/// Caches the grabbable object component.
/// </summary>
protected override void Awake()
{
base.Awake();
GrabbableObject = GetComponentInParent<UxrGrabbableObject>();
if (GrabbableObject == null && IsGrabbableObjectRequired)
{
if (UxrGlobalSettings.Instance.LogLevelManipulation >= UxrLogLevel.Errors)
{
Debug.LogError($"Component {nameof(T)} requires a {nameof(UxrGrabbableObject)} component in the same object or any of its parents.");
}
}
}
/// <summary>
/// Subscribes to events.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
if (GrabbableObject)
{
GrabbableObject.Grabbing += GrabbableObject_Grabbing;
GrabbableObject.Grabbed += GrabbableObject_Grabbed;
GrabbableObject.Releasing += GrabbableObject_Releasing;
GrabbableObject.Released += GrabbableObject_Released;
GrabbableObject.Placing += GrabbableObject_Placing;
GrabbableObject.Placed += GrabbableObject_Placed;
GrabbableObject.ConstraintsApplying += GrabbableObject_ConstraintsApplying;
GrabbableObject.ConstraintsApplied += GrabbableObject_ConstraintsApplied;
GrabbableObject.ConstraintsFinished += GrabbableObject_ConstraintsFinished;
}
}
/// <summary>
/// Unsubscribes from events.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
if (GrabbableObject)
{
GrabbableObject.Grabbing -= GrabbableObject_Grabbing;
GrabbableObject.Grabbed -= GrabbableObject_Grabbed;
GrabbableObject.Releasing -= GrabbableObject_Releasing;
GrabbableObject.Released -= GrabbableObject_Released;
GrabbableObject.Placing -= GrabbableObject_Placing;
GrabbableObject.Placed -= GrabbableObject_Placed;
GrabbableObject.ConstraintsApplying -= GrabbableObject_ConstraintsApplying;
GrabbableObject.ConstraintsApplied -= GrabbableObject_ConstraintsApplied;
GrabbableObject.ConstraintsFinished -= GrabbableObject_ConstraintsFinished;
}
}
#endregion
#region Event Handling Methods
/// <summary>
/// Event handling method for the <see cref="UxrGrabbableObject.Grabbing" /> event. It will call the overridable event
/// trigger so that child classes don't need to subscribe to the event and can override the method instead.
/// </summary>
/// <param name="sender">The event sender</param>
/// <param name="e">The event parameters</param>
private void GrabbableObject_Grabbing(object sender, UxrManipulationEventArgs e)
{
OnObjectGrabbing(e);
}
/// <summary>
/// Event handling method for the <see cref="UxrGrabbableObject.Grabbed" /> event. It will call the overridable event
/// trigger so that child classes don't need to subscribe to the event and can override the method instead.
/// </summary>
/// <param name="sender">The event sender</param>
/// <param name="e">The event parameters</param>
private void GrabbableObject_Grabbed(object sender, UxrManipulationEventArgs e)
{
OnObjectGrabbed(e);
}
/// <summary>
/// Event handling method for the <see cref="UxrGrabbableObject.Releasing" /> event. It will call the overridable event
/// trigger so that child classes don't need to subscribe to the event and can override the method instead.
/// </summary>
/// <param name="sender">The event sender</param>
/// <param name="e">The event parameters</param>
private void GrabbableObject_Releasing(object sender, UxrManipulationEventArgs e)
{
OnObjectReleasing(e);
}
/// <summary>
/// Event handling method for the <see cref="UxrGrabbableObject.Released" /> event. It will call the overridable event
/// trigger so that child classes don't need to subscribe to the event and can override the method instead.
/// </summary>
/// <param name="sender">The event sender</param>
/// <param name="e">The event parameters</param>
private void GrabbableObject_Released(object sender, UxrManipulationEventArgs e)
{
OnObjectReleased(e);
}
/// <summary>
/// Event handling method for the <see cref="UxrGrabbableObject.Placing" /> event. It will call the overridable event
/// trigger so that child classes don't need to subscribe to the event and can override the method instead.
/// </summary>
/// <param name="sender">The event sender</param>
/// <param name="e">The event parameters</param>
private void GrabbableObject_Placing(object sender, UxrManipulationEventArgs e)
{
OnObjectPlacing(e);
}
/// <summary>
/// Event handling method for the <see cref="UxrGrabbableObject.Placed" /> event. It will call the overridable event
/// trigger so that child classes don't need to subscribe to the event and can override the method instead.
/// </summary>
/// <param name="sender">The event sender</param>
/// <param name="e">The event parameters</param>
private void GrabbableObject_Placed(object sender, UxrManipulationEventArgs e)
{
OnObjectPlaced(e);
}
/// <summary>
/// Event handling method for the <see cref="UxrGrabbableObject.ConstraintsApplying" /> event. It will call the
/// overridable event trigger so that child classes don't need to subscribe to the event and can override the method instead.
/// </summary>
/// <param name="sender">The event sender</param>
/// <param name="e">The event parameters</param>
private void GrabbableObject_ConstraintsApplying(object sender, UxrApplyConstraintsEventArgs e)
{
OnObjectConstraintsApplying(e);
}
/// <summary>
/// Event handling method for the <see cref="UxrGrabbableObject.ConstraintsApplied" /> event. It will call the
/// overridable event trigger so that child classes don't need to subscribe to the event and can override the method instead.
/// </summary>
/// <param name="sender">The event sender</param>
/// <param name="e">The event parameters</param>
private void GrabbableObject_ConstraintsApplied(object sender, UxrApplyConstraintsEventArgs e)
{
OnObjectConstraintsApplied(e);
}
/// <summary>
/// Event handling method for the <see cref="UxrGrabbableObject.ConstraintsFinished" /> event. It will call the
/// overridable event trigger so that child classes don't need to subscribe to the event and can override the method instead.
/// </summary>
/// <param name="sender">The event sender</param>
/// <param name="e">The event parameters</param>
private void GrabbableObject_ConstraintsFinished(object sender, UxrApplyConstraintsEventArgs e)
{
OnObjectConstraintsFinished(e);
}
#endregion
#region Event Trigger Methods
/// <summary>
/// Overridable event trigger method for the <see cref="UxrGrabbableObject.Grabbing" /> event that can be used to
/// handle it without requiring to subscribe/unsubscribe.
/// </summary>
/// <param name="e">Event parameters</param>
protected virtual void OnObjectGrabbing(UxrManipulationEventArgs e)
{
}
/// <summary>
/// Overridable event trigger method for the <see cref="UxrGrabbableObject.Grabbed" /> event that can be used to
/// handle it without requiring to subscribe/unsubscribe.
/// </summary>
/// <param name="e">Event parameters</param>
protected virtual void OnObjectGrabbed(UxrManipulationEventArgs e)
{
}
/// <summary>
/// Overridable event trigger method for the <see cref="UxrGrabbableObject.Releasing" /> event that can be used to
/// handle it without requiring to subscribe/unsubscribe.
/// </summary>
/// <param name="e">Event parameters</param>
protected virtual void OnObjectReleasing(UxrManipulationEventArgs e)
{
}
/// <summary>
/// Overridable event trigger method for the <see cref="UxrGrabbableObject.Released" /> event that can be used to
/// handle it without requiring to subscribe/unsubscribe.
/// </summary>
/// <param name="e">Event parameters</param>
protected virtual void OnObjectReleased(UxrManipulationEventArgs e)
{
}
/// <summary>
/// Overridable event trigger method for the <see cref="UxrGrabbableObject.Placing" /> event that can be used to
/// handle it without requiring to subscribe/unsubscribe.
/// </summary>
/// <param name="e">Event parameters</param>
protected virtual void OnObjectPlacing(UxrManipulationEventArgs e)
{
}
/// <summary>
/// Overridable event trigger method for the <see cref="UxrGrabbableObject.Placed" /> event that can be used to
/// handle it without requiring to subscribe/unsubscribe.
/// </summary>
/// <param name="e">Event parameters</param>
protected virtual void OnObjectPlaced(UxrManipulationEventArgs e)
{
}
/// <summary>
/// Overridable event trigger method for the <see cref="UxrGrabbableObject.ConstraintsApplying" /> event that can be
/// used to handle it without requiring to subscribe/unsubscribe.
/// </summary>
/// <param name="e">Event parameters</param>
protected virtual void OnObjectConstraintsApplying(UxrApplyConstraintsEventArgs e)
{
}
/// <summary>
/// Overridable event trigger method for the <see cref="UxrGrabbableObject.ConstraintsApplied" /> event that can be
/// used to handle it without requiring to subscribe/unsubscribe.
/// </summary>
/// <param name="e">Event parameters</param>
protected virtual void OnObjectConstraintsApplied(UxrApplyConstraintsEventArgs e)
{
}
/// <summary>
/// Overridable event trigger method for the <see cref="UxrGrabbableObject.ConstraintsFinished" /> event that can be
/// used to handle it without requiring to subscribe/unsubscribe.
/// </summary>
/// <param name="e">Event parameters</param>
protected virtual void OnObjectConstraintsFinished(UxrApplyConstraintsEventArgs e)
{
}
#endregion
#region Protected Types & Data
/// <summary>
/// Gets whether the grabbable object component is required or it's not. By default it is required but it can be
/// overriden in child classes so that it is optional.
/// </summary>
protected virtual bool IsGrabbableObjectRequired => true;
#endregion
#region Private Types & Data
private UxrGrabbableObject _grabbableObject;
#endregion
}
}

View File

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

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8dbb0a618ef647dd974f5201e2e40347
timeCreated: 1644249537

View File

@@ -0,0 +1,14 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IUxrSingleton.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Core.Components.Singleton
{
/// <summary>
/// Interface for all classes that are singletons.
/// </summary>
public interface IUxrSingleton
{
}
}

View File

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

View File

@@ -0,0 +1,36 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrAbstractSingleton.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Core.Components.Singleton
{
/// <summary>
/// Singleton base class.
/// </summary>
public abstract class UxrAbstractSingleton : UxrComponent
{
#region Protected Overrides UxrComponent
/// <summary>
/// Singletons generate unique ID based on full type name. This ensures that the IDs are the same on all devices and
/// message exchanges will work correctly.
/// </summary>
protected override bool UniqueIdIsTypeName => true;
/// <inheritdoc />
protected override int SerializationOrder => UxrConstants.Serialization.SerializationOrderSingleton;
#endregion
#region Protected Types & Data
/// <summary>
/// Whether the singleton requires <see cref="UnityEngine.Object.DontDestroyOnLoad" /> applied to the GameObject so
/// that it doesn't get destroyed when a new scene is loaded.
/// </summary>
protected virtual bool NeedsDontDestroyOnLoad => true;
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e12d9e4e2a7b44819a93a23b5d53ddcc
timeCreated: 1644249666

View File

@@ -0,0 +1,239 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrAbstractSingleton.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Threading;
using UltimateXR.Core.Settings;
using UltimateXR.Core.Threading;
using UnityEngine;
using UnityEngine.Assertions;
namespace UltimateXR.Core.Components.Singleton
{
/// <summary>
/// <para>
/// Base class for singletons that can be used with abstract classes.
/// </para>
/// <para>
/// The difference with <see cref="UxrSingleton{T}" /> is that <see cref="UxrSingleton{T}" /> guarantees
/// that an instance will always be available in the scene by instantiating the component if it's not found.
/// This means <see cref="UxrSingleton{T}.Instance" /> will always be non-null and can be used with or
/// without an instance available in the scene. <see cref="UxrSingleton{T}" /> also allows to use automatic
/// prefab instantiation if a compatible singleton prefab is present in a special Resources folder.
/// Since abstract classes can't be instantiated, <see cref="Instance" /> in <see cref="UxrAbstractSingleton{T}" />
/// will be non-null only if a child component is available somewhere in the scene.
/// </para>
/// <para>
/// For design purposes, a singleton may still be desirable when programming an abstract class, hence
/// this <see cref="UxrAbstractSingleton{T}" /> component base class.
/// </para>
/// </summary>
/// <typeparam name="T">Class the singleton is for</typeparam>
/// <remarks>
/// <list type="bullet">
/// <item>Make sure to call base.Awake() first in child classes where <see cref="Awake" /> is used.</item>
/// <item>Use <see cref="HasInstance" /> to check whether the instance exists.</item>
/// </list>
/// </remarks>
public abstract class UxrAbstractSingleton<T> : UxrAbstractSingleton, IUxrSingleton where T : UxrAbstractSingleton<T>
{
#region Public Types & Data
/// <summary>
/// Gets the unique, global instance of the given component.
/// </summary>
public static T Instance
{
get
{
if (s_instance is null)
{
UxrMonoDispatcher.RunOnMainThread(FindInstance);
while (s_instance is null && !UxrMonoDispatcher.IsCurrentThreadMain)
{
Thread.Sleep(25);
}
}
return s_instance;
}
}
/// <summary>
/// Gets whether there is a singleton instance available.
/// </summary>
public static bool HasInstance => s_instance != null;
/// <summary>
/// Gets or sets whether the singleton has been initialized.
/// </summary>
public bool IsInitialized { get; private set; }
#endregion
#region Unity
/// <summary>
/// Tries to set the singleton instance.
/// </summary>
protected override void Awake()
{
base.Awake();
TrySetInstance(this);
}
/// <summary>
/// Destroys the singleton instance.
/// </summary>
protected override void OnDestroy()
{
base.OnDestroy();
if (ReferenceEquals(this, s_instance))
{
Release();
}
}
#endregion
#region Protected Methods
/// <summary>
/// Gets the singleton instance.
/// </summary>
/// <returns></returns>
protected static T GetInstance()
{
return s_instance;
}
/// <summary>
/// Tries to set the singleton instance.
/// </summary>
/// <param name="value">Candidate to set as singleton instance</param>
/// <returns>Whether the instance was set</returns>
protected static bool TrySetInstance(UxrAbstractSingleton<T> value)
{
if (value is null)
{
return false;
}
if (ReferenceEquals(s_instance, value))
{
return true;
}
if (!(s_instance is null))
{
if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Warnings)
{
Debug.LogWarning($"{UxrConstants.CoreModule} {typeof(T).Name} singleton already added. Destroying second instance @ {value.gameObject}", value);
}
Destroy(value);
return false;
}
Assert.IsTrue(value is T, $"Incoherent types: {value.GetType().Name} vs {typeof(T).Name}");
if (Application.isPlaying && value.NeedsDontDestroyOnLoad)
{
DontDestroyOnLoad(value.gameObject);
}
s_instance = (T)value;
s_instance.Init();
if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.CoreModule} {typeof(T).Name} singleton successfully initialized", s_instance);
}
return true;
}
/// <summary>
/// The default internal initialization. Child classes can override this method if they require initialization code.
/// </summary>
/// <param name="initializedCallback">Callback called when the initialization finished.</param>
protected virtual void InitInternal(Action initializedCallback)
{
initializedCallback?.Invoke();
}
/// <summary>
/// The default internal release. Child classes can override this method if they required deallocation code.
/// </summary>
/// <param name="releasedCallback">Callback called when the releasing finished.</param>
protected virtual void ReleaseInternal(Action releasedCallback)
{
releasedCallback?.Invoke();
}
#endregion
#region Private Methods
/*
/// <summary>
/// Called by Unity before a scene is loaded. Calls <see cref="TryFindInstance" />.
/// </summary>
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void BeforeSceneLoad()
{
TryFindInstance();
}
*/
/// <summary>
/// Tries to find a pre-existing instance in the scene and set it as the singleton instance.
/// </summary>
/// <returns></returns>
private static bool TryFindInstance()
{
return TrySetInstance(FindObjectOfType<T>());
}
/// <summary>
/// Tries to find a pre-existing instance in the scene.
/// </summary>
private static void FindInstance()
{
TryFindInstance();
}
/// <summary>
/// Initializes the singleton.
/// </summary>
private void Init()
{
InitInternal(() => IsInitialized = true);
}
/// <summary>
/// Releases any resources allocated by the singleton.
/// </summary>
private void Release()
{
ReleaseInternal(() =>
{
if (!IsApplicationQuitting)
{
s_instance = null;
}
});
}
#endregion
#region Private Types & Data
private static T s_instance;
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8e9a4c7a7122d8c419e61e67a6e8d4e0
timeCreated: 1644249666

View File

@@ -0,0 +1,54 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrAsyncInitAbstractSingleton.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Threading;
using System.Threading.Tasks;
using UltimateXR.Core.Threading.TaskControllers;
namespace UltimateXR.Core.Components.Singleton
{
/// <summary>
/// Same as <see cref="UxrAsyncInitSingleton{T}" /> but allows asynchronous initialization.
/// This can be useful where singletons require initialization through config files that are loaded asynchronously from
/// disk or through network.
/// </summary>
/// <typeparam name="T">Type the singleton is for</typeparam>
public abstract class UxrAsyncInitAbstractSingleton<T> : UxrAbstractSingleton<T> where T : UxrAsyncInitAbstractSingleton<T>
{
#region Protected Overrides UxrAbstractSingleton<T>
/// <summary>
/// Initializes the singleton asynchronously. Calls <see cref="InitAsync" /> which is required to be implemented in
/// child classes.
/// </summary>
/// <param name="initializedCallback">Callback required to run when the initialization finished.</param>
protected override void InitInternal(Action initializedCallback)
{
_initController = (UxrTaskController)InitAsync;
_initController.Completed += (o, e) => initializedCallback?.Invoke();
_initController.Start();
}
#endregion
#region Protected Methods
/// <summary>
/// Initializes the singleton asynchronously.
/// </summary>
/// <param name="ct">Allows to cancel the asynchronous process if necessary</param>
/// <returns>Task representing the initialization</returns>
protected abstract Task InitAsync(CancellationToken ct = default);
#endregion
#region Private Types & Data
private UxrTaskController _initController;
#endregion
}
}

View File

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

View File

@@ -0,0 +1,54 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrAsyncInitSingleton.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Threading;
using System.Threading.Tasks;
using UltimateXR.Core.Threading.TaskControllers;
namespace UltimateXR.Core.Components.Singleton
{
/// <summary>
/// Same as <see cref="UxrSingleton{T}" /> but allows asynchronous initialization.
/// This can be useful where singletons require initialization through config files that are loaded asynchronously from
/// disk or through network.
/// </summary>
/// <typeparam name="T">Type the singleton is for</typeparam>
public abstract class UxrAsyncInitSingleton<T> : UxrSingleton<T> where T : UxrAsyncInitSingleton<T>
{
#region Protected Overrides UxrAbstractSingleton<T>
/// <summary>
/// Initializes the singleton asynchronously. Calls <see cref="InitAsync" /> which is required to be implemented in
/// child classes.
/// </summary>
/// <param name="initializedCallback">Callback required to run when the initialization finished.</param>
protected override void InitInternal(Action initializedCallback)
{
_initController = (UxrTaskController)InitAsync;
_initController.Completed += (o, e) => initializedCallback?.Invoke();
_initController.Start();
}
#endregion
#region Protected Methods
/// <summary>
/// Initializes the singleton asynchronously.
/// </summary>
/// <param name="ct">Allows to cancel the asynchronous process if necessary</param>
/// <returns>Task representing the initialization</returns>
protected abstract Task InitAsync(CancellationToken ct = default);
#endregion
#region Private Types & Data
private UxrTaskController _initController;
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 512ee11458e34e01ad20c7f127ee1b78
timeCreated: 1611582011

View File

@@ -0,0 +1,185 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrSingleton.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.IO;
using System.Threading;
using UltimateXR.Core.Settings;
using UltimateXR.Core.Threading;
using UltimateXR.Extensions.System.IO;
using UnityEngine;
namespace UltimateXR.Core.Components.Singleton
{
/// <summary>
/// <para>
/// An improved singleton implementation over <see cref="UxrAbstractSingleton{T}" /> for non-abstract classes.
/// <see cref="UxrSingleton{T}" /> guarantees that an <see cref="Instance" /> will always be available by
/// instantiating the singleton if it wasn't found in the scene. Additionally, it can instantiate a prefab
/// if there is one available in a well-known location.
/// </para>
/// <para>
/// The steps followed by this singleton implementation to assign the instance are the:
/// </para>
/// <para>
/// <list type="number">
/// <item>
/// The singleton component is searched in the scene to see if it was pre-instantiated or is already
/// available.
/// </item>
/// <item>
/// If not found, the component tries to be instantiated in the scene using a prefab in a well known
/// Resources folder. The well known path is <see cref="UxrConstants.Paths.SingletonResources" /> in any
/// Resources folder and the prefab name is the singleton class name.
/// A prefab allows to assign initial properties to the component and also hang additional resources
/// (meshes, textures) from the GameObject if needed.
/// </item>
/// <item>
/// If not found, a new <see cref="GameObject" /> is instantiated and the singleton is added using
/// <see cref="GameObject.AddComponent{T}" />.
/// </item>
/// </list>
/// </para>
/// </summary>
/// <remarks>
/// The singleton can be a pre-existing component in a scene. If not, <see cref="Instance" /> takes care of
/// instancing it and make it the singleton.
/// This singleton can only be used in sealed classes. For use in abstract classes check
/// <see cref="UxrAbstractSingleton{T}" /> instead.
/// </remarks>
/// <typeparam name="T">
/// Type the singleton is for. This template can only be used with a hierarchy where <typeparamref name="T" /> is
/// specified at its lowers level (sealed). For use in abstract classes, check <see cref="UxrAbstractSingleton{T}" />.
/// </typeparam>
public abstract class UxrSingleton<T> : UxrAbstractSingleton<T> where T : UxrSingleton<T>
{
#region Public Types & Data
/// <inheritdoc cref="UxrAbstractSingleton{T}.Instance" />
public new static T Instance
{
get
{
if (GetInstance() is null)
{
UxrMonoDispatcher.RunOnMainThread(FindOrAddInstance);
while (GetInstance() is null && !UxrMonoDispatcher.IsCurrentThreadMain)
{
Thread.Sleep(25);
}
}
return GetInstance();
}
}
#endregion
#region Public Methods
/// <summary>
/// Dummy method forcing <see cref="Instance" /> to run the instance finding/creation process.
/// </summary>
public void Poke()
{
}
#endregion
#region Private Methods
/// <summary>
/// Tries to find a pre-existing instance in the scene and set it as the singleton instance.
/// </summary>
/// <returns></returns>
private static bool TryFindInstance()
{
return TrySetInstance(FindObjectOfType<T>());
}
/// <summary>
/// <para>
/// Tries to find the singleton instance in the scene. If it was not found, a new singleton is instantiated
/// and assigned as the singleton instance. The following steps are used:
/// </para>
/// <list type="number">
/// <item>
/// The singleton component is searched in the scene to see if it was pre-instantiated or is already
/// available.
/// </item>
/// <item>
/// If not found, the component tries to be instantiated in the scene using a prefab in a well known
/// Resources folder. The well known path is <see cref="UxrConstants.Paths.SingletonResources" /> in any
/// Resources
/// folder and the prefab name is the singleton class name.
/// A prefab can be used to assign initial properties to the component and also hang additional resources
/// (meshes, textures) from the GameObject.
/// </item>
/// <item>
/// If not found, a new <see cref="GameObject" /> is instantiated and the singleton is added using
/// <see cref="GameObject.AddComponent{T}" />.
/// </item>
/// </list>
/// </summary>
private static void FindOrAddInstance()
{
// First: Try to find instance in scene
if (TryFindInstance())
{
return;
}
// Second: Try to instantiate it from Resources singleton folder
T prefab = Resources.Load<T>(ResourcesPrefabPath);
T instance = null;
if (prefab != null)
{
if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.CoreModule} {typeof(T).Name} was able to load prefab from Resources folder: {ResourcesPrefabPath}");
}
instance = Instantiate(prefab);
if (instance == null)
{
if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.CoreModule} {typeof(T).Name}: Unable to instantiate prefab. Instance is null. Path is {ResourcesPrefabPath}", prefab);
}
}
instance.name = prefab.name;
}
// Third: Try to instantiate it by creating a GameObject and adding the component
if (instance == null)
{
instance = new GameObject(typeof(T).Name).AddComponent<T>();
}
// Try to set it as the current singleton instance
if (!TrySetInstance(instance))
{
if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.CoreModule} {typeof(T).Name} singleton failed to initialize", instance);
}
}
}
#endregion
#region Private Types & Data
private static string ResourcesPrefabPath => PathExt.Combine(UxrConstants.Paths.SingletonResources, typeof(T).Name);
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 9a6ca3fc203c1924db6eeaa1fb9cab40
timeCreated: 1519373620
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,221 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrComponent_1.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using UltimateXR.Core.Components.Composite;
using UnityEngine;
namespace UltimateXR.Core.Components
{
/// <summary>
/// Like <see cref="UxrComponent"/> but it allows to enumerate all components of a specific type.
/// </summary>
/// <typeparam name="T">Component type</typeparam>
/// <remarks>
/// Components get registered through their Awake() call. This means that components get registered
/// the first time they are enabled. Disabled objects that have been enabled at some point are enumerated, but objects
/// that have never been enabled don't get enumerated, which means that they will not appear in
/// <see cref="AllComponents" />.
/// </remarks>
/// <seealso cref="UxrAvatarComponent{T}" />
public abstract class UxrComponent<T> : UxrComponent where T : UxrComponent<T>
{
#region Public Types & Data
/// <summary>
/// Called before registering a component.
/// </summary>
public new static event Action<T> GlobalRegistering;
/// <summary>
/// Called when a component was registered.
/// </summary>
public new static event Action<T> GlobalRegistered;
/// <summary>
/// Called before unregistering a component.
/// </summary>
public new static event Action<T> GlobalUnregistering;
/// <summary>
/// Called when a component was unregistered.
/// </summary>
public new static event Action<T> GlobalUnregistered;
/// <summary>
/// Called when a component was enabled.
/// </summary>
public new static event Action<T> GlobalEnabled;
/// <summary>
/// Called when a component was disabled.
/// </summary>
public new static event Action<T> GlobalDisabled;
/// <summary>
/// Gets all the components of this specific type, enabled or not, in all open scenes.
/// </summary>
/// <remarks>
/// Components that have never been enabled are not returned. Components are automatically registered through their
/// Awake() call, which is never called if the object has never been enabled. In this case it is recommended to resort
/// to <see cref="GameObject.GetComponentsInChildren{T}(bool)" /> or
/// <see cref="UnityEngine.Object.FindObjectsOfType{T}(bool)" />.
/// </remarks>
public new static IEnumerable<T> AllComponents => s_typeComponents;
/// <summary>
/// Gets all components of this specific type that are enabled, in all open scenes.
/// </summary>
public new static IEnumerable<T> EnabledComponents => s_typeComponents.Where(c => c.isActiveAndEnabled);
#endregion
#region Public Methods
/// <summary>
/// Sorts the internal list of components. This is useful if iterating over the components requires a certain order.
/// </summary>
/// <param name="comparison">Comparison to use for sorting</param>
public static void SortComponents(Comparison<T> comparison)
{
s_typeComponents.Sort(comparison);
}
/// <summary>
/// Destroys all components.
/// </summary>
public new static void DestroyAllComponents()
{
foreach (T component in s_typeComponents)
{
Destroy(component);
}
}
/// <summary>
/// Destroys all gameObjects the components belong to.
/// </summary>
public new static void DestroyAllGameObjects()
{
foreach (T component in s_typeComponents)
{
Destroy(component.gameObject);
}
}
#endregion
#region Unity
/// <summary>
/// Registers itself in the static list of components.
/// </summary>
protected override void Awake()
{
base.Awake();
OnRegistering();
s_typeComponents.Add((T)this);
OnRegistered();
}
/// <summary>
/// Removes itself from the static list of components.
/// </summary>
protected override void OnDestroy()
{
base.OnDestroy();
OnUnregistering();
s_typeComponents.Remove((T)this);
OnUnregistered();
}
/// <summary>
/// Triggers enabled events.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
OnEnabled();
}
/// <summary>
/// Triggers disabled events.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
OnDisabled();
}
#endregion
#region Event Trigger Methods
/// <summary>
/// <see cref="GlobalRegistering" /> event trigger.
/// </summary>
private void OnRegistering()
{
GlobalRegistering?.Invoke(this as T);
}
/// <summary>
/// <see cref="GlobalRegistered" /> event trigger.
/// </summary>
private void OnRegistered()
{
GlobalRegistered?.Invoke(this as T);
}
/// <summary>
/// <see cref="GlobalUnregistering" /> event trigger.
/// </summary>
private void OnUnregistering()
{
GlobalUnregistering?.Invoke(this as T);
}
/// <summary>
/// <see cref="GlobalUnregistered" /> event trigger.
/// </summary>
private void OnUnregistered()
{
GlobalUnregistered?.Invoke(this as T);
}
/// <summary>
/// <see cref="GlobalEnabled" /> event trigger.
/// </summary>
private void OnEnabled()
{
GlobalEnabled?.Invoke(this as T);
}
/// <summary>
/// <see cref="GlobalDisabled" /> event trigger.
/// </summary>
private void OnDisabled()
{
GlobalDisabled?.Invoke(this as T);
}
#endregion
#region Private Types & Data
/// <summary>
/// Static list containing all registered components of this type.
/// </summary>
private static readonly List<T> s_typeComponents = new List<T>();
#endregion
}
}

View File

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

View File

@@ -0,0 +1,82 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrComponent_2.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using System.Linq;
using UltimateXR.Avatar;
using UltimateXR.Core.Components.Composite;
using UltimateXR.Extensions.Unity;
using UnityEngine;
namespace UltimateXR.Core.Components
{
/// <summary>
/// <para>
/// Like <see cref="UxrComponent{T}" /> but the component belongs to a hierarchy
/// with a parent that has a component of a certain type <typeparamref name="TP" />.
/// This allows to enumerate and keep track of only the components that hang from the hierarchy
/// under each parent component separately.
/// </para>
/// <para>
/// In the case of keeping track of all components of a same type that are in or hang from an avatar (
/// <see cref="UxrAvatar" />) there is a special component for that in <see cref="UxrAvatarComponent{T}" />.
/// </para>
/// </summary>
/// <typeparam name="TP">Parent component type</typeparam>
/// <typeparam name="TC">Component type</typeparam>
public abstract class UxrComponent<TP, TC> : UxrComponent<TC>
where TP : Component
where TC : UxrComponent<TC>
{
#region Public Types & Data
/// <summary>
/// Gets all the components, enabled of not, of this specific type that hang from the same parent.
/// </summary>
/// <remarks>
/// Components that have never been enabled are not returned. Components are automatically registered through their
/// Awake() call, which is never called if the object has never been enabled. In this case it is recommended to resort
/// to <see cref="GameObject.GetComponentsInChildren{T}(bool)" />.
/// </remarks>
public IEnumerable<TC> AllChildrenFromParent => GetParentChildren(Parent, true);
/// <summary>
/// Gets only the enabled components of this specific type that hang from the same parent.
/// </summary>
public IEnumerable<TC> EnabledChildrenFromParent => GetParentChildren(Parent);
/// <summary>
/// Parent the component belongs to.
/// </summary>
public TP Parent => gameObject.SafeGetComponentInParent<TP>();
#endregion
#region Public Methods
/// <summary>
/// Gets the children from a specific parent.
/// </summary>
/// <param name="parent">Parent to get the components from</param>
/// <param name="includeDisabled">Whether to include disabled components or not</param>
/// <returns>Components meeting the criteria</returns>
/// <remarks>
/// When using the <paramref name="includeDisabled" /> parameter, components that have never been enabled are not
/// returned. Components are automatically registered through their Awake() call, which is never called if the object
/// has never been enabled. In this case it is recommended to resort to
/// <see cref="GameObject.GetComponentsInChildren{T}(bool)" />.
/// </remarks>
public static IEnumerable<TC> GetParentChildren(TP parent, bool includeDisabled = false)
{
if (includeDisabled)
{
return AllComponents.Where(c => c is UxrComponent<TP, TC> child && child.Parent == parent);
}
return AllComponents.Where(c => c is UxrComponent<TP, TC> child && child.Parent == parent && c.isActiveAndEnabled);
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,20 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrSyncObject.StateSave.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Core.Components
{
public partial class UxrSyncObject
{
#region Protected Overrides UxrComponent
/// <inheritdoc />
protected override bool SaveStateWhenDisabled => _syncWhileDisabled;
/// <inheritdoc />
protected override bool SerializeActiveAndEnabledState => _syncActiveAndEnabled;
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 115582f99ade41d98a39ff08c14120ff
timeCreated: 1715540449

View File

@@ -0,0 +1,56 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrSyncObject.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.StateSave;
using UltimateXR.Core.Unique;
using UnityEngine;
namespace UltimateXR.Core.Components
{
/// <summary>
/// Minimal <see cref="UxrComponent" /> component that can be used to identify GameObjects that don't have any other
/// <see cref="UxrComponent" />. Additionally, it can control which Transform to sync, if any.
/// This can be useful in multiplayer environments to reference objects. For example, an avatar might be parented to a
/// moving platform and this operation needs to be executed in other client PCs.
/// If there is no reference to this platform (it might be the result of performing a raycast and not because a script
/// has a reference to it, for instance), there needs to be a way to get the same GameObject in the other PCS.
/// Since <see cref="UxrComponent" /> has a way to identify and obtain components (<see cref="UxrComponent.UniqueId" />
/// and <see cref="UxrUniqueIdImplementer.TryGetComponentById" />), a simple way to send references through the network
/// is by using a <see cref="UxrSyncObject" /> component on the GameObject.
/// </summary>
public partial class UxrSyncObject : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private bool _syncTransform;
[SerializeField] private UxrTransformSpace _transformSpace = UxrTransformSpace.World;
[SerializeField] private bool _syncActiveAndEnabled;
[SerializeField] private bool _syncWhileDisabled;
#endregion
#region Public Types & Data
/// <summary>
/// Gets whether the transform should be synchronized.
/// </summary>
public bool SyncTransform => _syncTransform;
#endregion
#region Protected Overrides UxrComponent
/// <inheritdoc />
protected override UxrTransformSpace TransformStateSaveSpace => _transformSpace;
/// <inheritdoc />
protected override bool RequiresTransformSerialization(UxrStateSaveLevel level)
{
return SyncTransform;
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 45164fba55c670d439fa1c4e8182e6e1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,51 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrInstanceEventArgs.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UnityEngine;
namespace UltimateXR.Core.Instantiation
{
/// <summary>
/// Instantiation event args for <see cref="UxrInstanceManager" />.
/// </summary>
public class UxrInstanceEventArgs : EventArgs
{
#region Public Types & Data
/// <summary>
/// Gets the instance of the instantiation/destroy operation. Will be null on
/// <see cref="UxrInstanceManager.Instantiating" /> and
/// <see cref="UxrInstanceManager.Destroyed" /> events, since the instance will not exist.
/// </summary>
public GameObject Instance { get; }
/// <summary>
/// Gets the prefab of the instantiation/destroy operation. Null when using
/// <see cref="UxrInstanceManager.InstantiateEmptyGameObject" />.
/// </summary>
public GameObject Prefab { get; }
/// <summary>
/// Gets the prefab Id of the instantiation/destroy operation. The prefab Id is the Id assigned by Unity to the prefab
/// asset. Null when using <see cref="UxrInstanceManager.InstantiateEmptyGameObject" /> for instantiation.
/// </summary>
public string PrefabId { get; }
#endregion
#region Constructors & Finalizer
/// <inheritdoc />
public UxrInstanceEventArgs(GameObject instance, GameObject prefab, string prefabId)
{
Instance = instance;
Prefab = prefab;
PrefabId = prefabId;
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8959f68d36624e93b7749b11fc9192f6
timeCreated: 1708255563

View File

@@ -0,0 +1,185 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrInstanceManager.InstanceInfo.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Serialization;
using UltimateXR.Core.Unique;
using UltimateXR.Extensions.Unity;
using UnityEngine;
namespace UltimateXR.Core.Instantiation
{
public partial class UxrInstanceManager
{
#region Private Types & Data
/// <summary>
/// Stores instantiation information.
/// </summary>
private class InstanceInfo : IUxrSerializable
{
#region Public Types & Data
/// <summary>
/// Gets the Id of the prefab that was instantiated. Null if it's an empty GameObject.
/// </summary>
public string PrefabId => _prefabId;
/// <summary>
/// Gets the name of the empty instance, if the instance was created using
/// <see cref="UxrInstanceManager.InstantiateEmptyGameObject" />.
/// </summary>
public string Name => _name;
/// <summary>
/// Gets the parent if the object was parented to any.
/// </summary>
public IUxrUniqueId Parent => _parent;
/// <summary>
/// Gets the position. It will contain relative position to the parent if parented.
/// </summary>
public Vector3 Position => _position;
/// <summary>
/// Gets the rotation. It will contain relative rotation to the parent if parented.
/// </summary>
public Quaternion Rotation => _rotation;
/// <summary>
/// Gets the local scale.
/// </summary>
public Vector3 Scale => _scale;
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="instance">The instance</param>
/// <param name="prefabId">
/// The id of the prefab that was instantiated or null if the instance was created using
/// <see cref="UxrInstanceManager.InstantiateEmptyGameObject" />.
/// </param>
public InstanceInfo(IUxrUniqueId instance, string prefabId)
{
_prefabId = prefabId;
_name = string.IsNullOrEmpty(prefabId) ? instance.GameObject.name : null;
_parent = instance.Transform.parent != null ? instance.Transform.parent.GetComponent<IUxrUniqueId>() : null;
_position = _parent != null ? instance.Transform.localPosition : instance.Transform.position;
_rotation = _parent != null ? instance.Transform.localRotation : instance.Transform.rotation;
_scale = instance.Transform.localScale;
}
/// <summary>
/// Default constructor required for serialization.
/// </summary>
private InstanceInfo()
{
}
#endregion
#region Implicit IUxrSerializable
/// <inheritdoc />
public int SerializationVersion => 0;
/// <inheritdoc />
public void Serialize(IUxrSerializer serializer, int serializationVersion)
{
serializer.Serialize(ref _prefabId);
serializer.Serialize(ref _name);
serializer.SerializeUniqueComponent(ref _parent);
serializer.Serialize(ref _position);
serializer.Serialize(ref _rotation);
serializer.Serialize(ref _scale);
}
#endregion
#region Public Methods
/// <summary>
/// Updates the info using the current object data.
/// </summary>
/// <param name="instance">The GameObject to update the information of</param>
public void UpdateInfoUsingObject(GameObject instance)
{
Transform transform = instance.transform;
IUxrUniqueId parent = transform.parent != null ? transform.parent.GetComponent<IUxrUniqueId>() : null;
Transform parentTransform = parent?.Transform;
if (parentTransform != null)
{
// Use relative parent data
_parent = parent;
_position = transform.localPosition;
_rotation = transform.localRotation;
}
else
{
// Use world data
_parent = null;
_position = transform.position;
_rotation = transform.rotation;
}
_scale = transform.localScale;
}
/// <summary>
/// Updates the object using the current information.
/// </summary>
/// <param name="instance">The GameObject to update</param>
public void UpdateObjectUsingInfo(GameObject instance)
{
Transform transform = instance.transform;
Transform parentTransform = Parent?.Transform;
if (parentTransform != null)
{
// Use relative parent data
if (transform.parent != parentTransform)
{
transform.SetParent(parentTransform);
}
TransformExt.SetLocalPositionAndRotation(transform, Position, Rotation);
}
else
{
// Use world
if (transform.parent != null)
{
transform.SetParent(null);
}
transform.SetPositionAndRotation(Position, Rotation);
}
transform.localScale = Scale;
}
#endregion
#region Private Types & Data
private string _prefabId;
private string _name;
private IUxrUniqueId _parent;
private Vector3 _position;
private Quaternion _rotation;
private Vector3 _scale;
#endregion
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: afa00701315f483ab45afb6d9b6fa2b7
timeCreated: 1708512591

View File

@@ -0,0 +1,144 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrInstanceManager.StateSave.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using UltimateXR.Core.StateSave;
using UltimateXR.Core.Unique;
using UltimateXR.Extensions.System.Collections;
using UnityEngine;
namespace UltimateXR.Core.Instantiation
{
public partial class UxrInstanceManager
{
#region Protected Overrides UxrComponent
/// <inheritdoc />
protected override int SerializationOrder => UxrConstants.Serialization.SerializationOrderInstanceManager;
/// <inheritdoc />
protected override void SerializeState(bool isReading, int stateSerializationVersion, UxrStateSaveLevel level, UxrStateSaveOptions options)
{
base.SerializeState(isReading, stateSerializationVersion, level, options);
// Individual instantiations are already handled through events.
// We save all generated instances in higher save levels.
if (level > UxrStateSaveLevel.ChangesSincePreviousSave)
{
// When writing, update instance info first
if (!isReading)
{
foreach (KeyValuePair<Guid, InstanceInfo> pair in _currentInstancedPrefabs)
{
if (_currentInstances.TryGetValue(pair.Key, out GameObject instance) && instance != null)
{
pair.Value.UpdateInfoUsingObject(instance);
}
}
}
// We don't want to compare dictionaries, we save the instantiation info always by using null as name to avoid overhead.
SerializeStateValue(level, options, null, ref _currentInstancedPrefabs);
if (isReading)
{
// When reading we need to update the scene:
// -Destroy instances that do not exist anymore in the deserialized data
// -Create instances that are not yet in the scene, from the deserialized data
// First destroy existing instances in the scene that are not present in the deserialized list anymore:
List<Guid> toRemove = null;
foreach (KeyValuePair<Guid, GameObject> pair in _currentInstances)
{
if (pair.Value == null)
{
continue;
}
if (_currentInstancedPrefabs == null || !_currentInstancedPrefabs.ContainsKey(pair.Key))
{
// Before destroying the GameObject, unregister the components ahead of time too in case they are going to be re-created.
IUxrUniqueId[] components = pair.Value.GetComponentsInChildren<IUxrUniqueId>(true);
components.ForEach(c => c.Unregister());
Destroy(pair.Value);
if (toRemove == null)
{
toRemove = new List<Guid>();
}
toRemove.Add(pair.Key);
}
else if(_currentInstancedPrefabs != null && _currentInstancedPrefabs.TryGetValue(pair.Key, out InstanceInfo info))
{
// If the object is still present, update it to the current state
info.UpdateObjectUsingInfo(pair.Value);
}
}
if (toRemove != null)
{
foreach (Guid guid in toRemove)
{
_currentInstances.Remove(guid);
}
}
// Now instantiate prefabs that are present in the deserialized list but not in the scene:
List<(Guid CombineGuid, InstanceInfo Info)> toAdd = null;
if (_currentInstancedPrefabs != null)
{
foreach (KeyValuePair<Guid, InstanceInfo> pair in _currentInstancedPrefabs)
{
if (!_currentInstances.ContainsKey(pair.Key))
{
if (toAdd == null)
{
toAdd = new List<(Guid, InstanceInfo)>();
}
toAdd.Add((pair.Key, pair.Value));
}
}
}
if (toAdd != null)
{
foreach ((Guid CombineGuid, InstanceInfo Info) pair in toAdd)
{
Vector3 position = pair.Info.Parent != null ? pair.Info.Parent.Transform.TransformPoint(pair.Info.Position) : pair.Info.Position;
Quaternion rotation = pair.Info.Parent != null ? pair.Info.Parent.Transform.rotation * pair.Info.Rotation : pair.Info.Rotation;
Vector3 scale = pair.Info.Scale;
if (!string.IsNullOrEmpty(pair.Info.PrefabId))
{
// Prefab
GameObject newInstance = InstantiatePrefabInternal(pair.Info.PrefabId, pair.Info.Parent, position, rotation, 0, pair.CombineGuid);
newInstance.transform.localScale = scale;
CheckNetworkSpawnPostprocess(newInstance);
}
else
{
// Empty GameObject
GameObject newObject = InstantiateEmptyGameObjectInternal(pair.Info.Name, pair.Info.Parent, position, rotation, 0, pair.CombineGuid);
newObject.transform.localScale = scale;
}
}
}
}
}
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5252f824b19944e38a1031be6ef5475c
timeCreated: 1708445377

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,32 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrPrefabList.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using UnityEngine;
namespace UltimateXR.Core.Instantiation
{
/// <summary>
/// List of user-defined instantiable prefabs used by <see cref="UxrInstanceManager" />.
/// </summary>
[CreateAssetMenu(fileName = "PrefabList", menuName = "UltimateXR/Prefab List", order = 1)]
public class UxrPrefabList : ScriptableObject
{
#region Inspector Properties/Serialized Fields
[SerializeField] private List<GameObject> _prefabList;
#endregion
#region Public Types & Data
/// <summary>
/// Gets the prefab list.
/// </summary>
public IReadOnlyList<GameObject> PrefabList => _prefabList;
#endregion
}
}

View File

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

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ebebb29280ab495c90fc5dce895bae8e
timeCreated: 1643819154

View File

@@ -0,0 +1,243 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrAxis.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UltimateXR.Core.Settings;
using UnityEngine;
namespace UltimateXR.Core.Math
{
/// <summary>
/// Class that allows to have formatted axes (A combo box with X, Y, Z strings) instead of numerical fields.
/// It also allows conversion from and to integers and <see cref="Vector3" /> types.
/// See the UxrAxisPropertyDrawer editor class for the integration with Unity Editor.
/// </summary>
[Serializable]
public class UxrAxis : IEquatable<UxrAxis>
{
#region Inspector Properties/Serialized Fields
[SerializeField] private int _axis;
#endregion
#region Public Types & Data
public const int X = 0;
public const int Y = 1;
public const int Z = 2;
/// <summary>
/// Returns a perpendicular axis.
/// </summary>
public UxrAxis Perpendicular => (_axis + 1) % 3;
/// <summary>
/// Returns the other perpendicular axis.
/// </summary>
public UxrAxis OtherPerpendicular => (_axis + 2) % 3;
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="axis">Axis as an integer value</param>
public UxrAxis(int axis)
{
#if UNITY_EDITOR
if (axis < 0 || axis > 3)
{
if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.CoreModule} Assigning invalid value to axis: {axis}");
}
}
#endif
_axis = Mathf.Clamp(axis, 0, 2);
}
#endregion
#region Implicit IEquatable<UxrAxis>
/// <inheritdoc />
public bool Equals(UxrAxis other)
{
if (ReferenceEquals(null, other))
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return _axis == other._axis;
}
#endregion
#region Public Overrides object
/// <inheritdoc />
public override string ToString()
{
return _axis switch
{
0 => "Right",
1 => "Up",
_ => "Forward"
};
}
/// <inheritdoc />
public override bool Equals(object obj)
{
return Equals(obj as UxrAxis);
}
/// <inheritdoc />
public override int GetHashCode()
{
return _axis.GetHashCode();
}
#endregion
#region Public Methods
/// <summary>
/// Returns the remaining axis which is not axis1 nor axis2.
/// </summary>
/// <param name="axis1">Axis 1</param>
/// <param name="axis2">Axis 2</param>
/// <returns>The remaining axis which is not axis1 nor axis2</returns>
public static UxrAxis OtherThan(UxrAxis axis1, UxrAxis axis2)
{
if (axis1 == axis2)
{
if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.CoreModule} {nameof(UxrAxis)}: Got same axis for {nameof(OtherThan)} (axis1)");
}
return axis1.Perpendicular;
}
int smaller = axis1._axis < axis2._axis ? axis1._axis : axis2._axis;
int bigger = axis1._axis > axis2._axis ? axis1._axis : axis2._axis;
if (smaller == 0 && bigger == 1)
{
return 2;
}
if (smaller == 0 && bigger == 2)
{
return 1;
}
return 0;
}
/// <summary>
/// Gets the color representing
/// </summary>
/// <param name="alpha"></param>
/// <returns></returns>
public Color GetColor(float alpha)
{
Vector3 axis = this;
return new Color(Mathf.Abs(axis.x), Mathf.Abs(axis.y), Mathf.Abs(axis.z), alpha);
}
#endregion
#region Operators
/// <summary>
/// Unary minus operator. Negates the axis value.
/// </summary>
/// <param name="axis">Axis to negate</param>
/// <returns>Negated axis</returns>
public static Vector3 operator -(UxrAxis axis)
{
return -(Vector3)axis;
}
/// <summary>
/// Implicit conversion from an <see cref="UxrAxis" /> to a <see cref="Vector3" />.
/// </summary>
/// <param name="axis">Axis to convert</param>
/// <returns>Converted <see cref="Vector3" /> value</returns>
public static implicit operator Vector3(UxrAxis axis)
{
return axis._axis switch
{
0 => Vector3.right,
1 => Vector3.up,
_ => Vector3.forward
};
}
/// <summary>
/// Implicit conversion from an integer to an <see cref="UxrAxis" />.
/// </summary>
/// <param name="axis">Integer to convert</param>
/// <returns>Converted <see cref="UxrAxis" /> value</returns>
public static implicit operator UxrAxis(int axis)
{
return new UxrAxis(axis);
}
/// <summary>
/// Equality operator.
/// </summary>
/// <param name="a">Operand A</param>
/// <param name="b">Operand B</param>
/// <returns>Whether the two operands are equal</returns>
public static bool operator ==(UxrAxis a, UxrAxis b)
{
if (ReferenceEquals(a, null) && ReferenceEquals(b, null))
{
return true;
}
if (ReferenceEquals(a, null) || ReferenceEquals(b, null))
{
return false;
}
return a.Equals(b);
}
/// <summary>
/// Inequality operator.
/// </summary>
/// <param name="a">Operand A</param>
/// <param name="b">Operand B</param>
/// <returns>Whether the two operands are different</returns>
public static bool operator !=(UxrAxis a, UxrAxis b)
{
return !(a == b);
}
/// <summary>
/// Implicit conversion from an <see cref="UxrAxis" /> to an integer.
/// </summary>
/// <param name="axis">Axis to convert</param>
/// <returns>Int value (0 = right, 1 = up, 2 = forward)</returns>
public static implicit operator int(UxrAxis axis)
{
return axis._axis;
}
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 79ee905395498e5428d29fba7ef65416
timeCreated: 1538564096
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,193 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrMathUtils.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Extensions.Unity.Math;
using UnityEngine;
namespace UltimateXR.Core.Math
{
/// <summary>
/// Contains math computations involving elements that do not belong to a specific class.
/// Most math is available through extensions classes in namespaces such as
/// <see cref="UltimateXR.Extensions.System.Math">UltimateXR.Extensions.System.Math</see> or
/// <see cref="UltimateXR.Extensions.Unity.Math">UltimateXR.Extensions.Unity.Math</see>.
/// Math related to animation is also available through classes in namespaces such as
/// <see cref="UltimateXR.Animation.IK">UltimateXR.Animation.IK</see>,
/// <see cref="UltimateXR.Animation.Interpolation">UltimateXRAnimation.Interpolation</see> or
/// <see cref="UltimateXR.Animation.Splines">UltimateXR.Animation.Splines</see>.
/// This class will contain math functionality that cannot be assigned to any extensions class.
/// </summary>
public static class UxrMathUtils
{
#region Public Methods
/// <summary>
/// Tries to find the intersection(s) between a 2D line and a 2D circle
/// Code from: http://csharphelper.com/blog/2014/09/determine-where-a-line-intersects-a-circle-in-c/
/// </summary>
/// <param name="linePoint1">Point A in the line</param>
/// <param name="linePoint2">Point B in the line</param>
/// <param name="circlePos">Circle position</param>
/// <param name="radius">Circle radius</param>
/// <param name="intersection1">Intersection 1 result, if it exists</param>
/// <param name="intersection2">Intersection 2 result, if it exists</param>
/// <returns>Number of intersections found (0, 1 or 2)</returns>
public static int FindLineCircleIntersections2D(Vector2 linePoint1, Vector2 linePoint2, Vector2 circlePos, float radius, out Vector2 intersection1, out Vector2 intersection2)
{
float t;
float dx = linePoint2.x - linePoint1.x;
float dy = linePoint2.y - linePoint1.y;
float a = dx * dx + dy * dy;
float b = 2 * (dx * (linePoint1.x - circlePos.x) + dy * (linePoint1.y - circlePos.y));
float c = (linePoint1.x - circlePos.x) * (linePoint1.x - circlePos.x) + (linePoint1.y - circlePos.y) * (linePoint1.y - circlePos.y) - radius * radius;
float det = b * b - 4 * a * c;
if (a <= 0.0000001 || det < 0)
{
// No real solutions.
intersection1 = new Vector3(float.NaN, float.NaN);
intersection2 = new Vector3(float.NaN, float.NaN);
return 0;
}
if (det == 0)
{
// One solution.
t = -b / (2 * a);
intersection1 = new Vector3(linePoint1.x + t * dx, linePoint1.y + t * dy);
intersection2 = new Vector3(float.NaN, float.NaN);
return 1;
}
// Two solutions.
t = (-b + Mathf.Sqrt(det)) / (2 * a);
intersection1 = new Vector3(linePoint1.x + t * dx, linePoint1.y + t * dy);
t = (-b - Mathf.Sqrt(det)) / (2 * a);
intersection2 = new Vector3(linePoint1.x + t * dx, linePoint1.y + t * dy);
return 2;
}
/// <summary>
/// Applies to <paramref name="position" /> and <paramref name="rotation" /> the transformation to make a transform
/// defined by <paramref name="sourcePosition" /> and <paramref name="sourceRotation" /> move and rotate to
/// <paramref name="targetPosition" /> and <paramref name="targetRotation" />.
/// </summary>
/// <param name="position">Position to apply the transformation to</param>
/// <param name="rotation">Rotation to apply the transformation to</param>
/// <param name="sourcePosition">Source position that will try to match <paramref name="targetPosition" /></param>
/// <param name="sourceRotation">Source rotation that will try to match <paramref name="targetRotation" /></param>
/// <param name="targetPosition">Target position</param>
/// <param name="targetRotation">Target rotation</param>
/// <param name="rotate">Allows to control whether to rotate or not</param>
/// <param name="translate">Allows to control whether to translate or not</param>
/// <param name="t">Optional interpolation value [0.0, 1.0]</param>
public static void ApplyAlignment(ref Vector3 position,
ref Quaternion rotation,
Vector3 sourcePosition,
Quaternion sourceRotation,
Vector3 targetPosition,
Quaternion targetRotation,
bool rotate,
bool translate,
float t = 1.0f)
{
if (rotate)
{
rotation.ApplyAlignment(sourceRotation, targetRotation, t);
}
if (translate)
{
position += (targetPosition - sourcePosition) * t;
}
}
/// <summary>
/// Checks if a box is completely (all corners) inside a BoxCollider
/// </summary>
/// <param name="boxPosition">Position of the box to test if it is inside the BoxCollider</param>
/// <param name="boxRotation">Rotation of the box to test if it is inside the BoxCollider</param>
/// <param name="boxScale">Scale of the box to test if it is inside the BoxCollider</param>
/// <param name="boxCenter">Center of the box (in local coordinates) to test if it is inside the BoxCollider</param>
/// <param name="boxSize">Size of the box (in local coordinates) to test if it is inside the BoxCollider</param>
/// <param name="boxVolume">BoxCollider to test against</param>
/// <param name="margin">Allowed margin for each x, y, z component</param>
/// <returns>True if all corners are inside the BoxCollider plus margin</returns>
public static bool IsBoxInsideBox(Vector3 boxPosition, Quaternion boxRotation, Vector3 boxScale, Vector3 boxCenter, Vector3 boxSize, BoxCollider boxVolume, Vector3 margin = default)
{
Matrix4x4 boxMatrix = Matrix4x4.TRS(boxPosition, boxRotation, boxScale);
Vector3[] corners = new Vector3[8];
corners[0] = boxMatrix.MultiplyPoint(boxCenter + new Vector3(+boxSize.x, +boxSize.y, +boxSize.z) * 0.5f);
corners[1] = boxMatrix.MultiplyPoint(boxCenter + new Vector3(+boxSize.x, +boxSize.y, -boxSize.z) * 0.5f);
corners[2] = boxMatrix.MultiplyPoint(boxCenter + new Vector3(+boxSize.x, -boxSize.y, +boxSize.z) * 0.5f);
corners[3] = boxMatrix.MultiplyPoint(boxCenter + new Vector3(+boxSize.x, -boxSize.y, -boxSize.z) * 0.5f);
corners[4] = boxMatrix.MultiplyPoint(boxCenter + new Vector3(-boxSize.x, +boxSize.y, +boxSize.z) * 0.5f);
corners[5] = boxMatrix.MultiplyPoint(boxCenter + new Vector3(-boxSize.x, +boxSize.y, -boxSize.z) * 0.5f);
corners[6] = boxMatrix.MultiplyPoint(boxCenter + new Vector3(-boxSize.x, -boxSize.y, +boxSize.z) * 0.5f);
corners[7] = boxMatrix.MultiplyPoint(boxCenter + new Vector3(-boxSize.x, -boxSize.y, -boxSize.z) * 0.5f);
for (int i = 0; i < 8; ++i)
{
if (!corners[i].IsInsideBox(boxVolume, margin))
{
return false;
}
}
return true;
}
/// <summary>
/// Checks if box1 is completely (all corners) inside box2
/// </summary>
/// <param name="box1Position">Position of box1</param>
/// <param name="box1Rotation">Rotation of box1</param>
/// <param name="box1Scale">Scale of box1</param>
/// <param name="box1Center">Center of box1 in its own local coordinates</param>
/// <param name="box1Size">Size of box1 in its own local coordinates</param>
/// <param name="box2Position">Position of box2</param>
/// <param name="box2Rotation">Rotation of box2</param>
/// <param name="box2Scale">Scale of box2</param>
/// <param name="box2Center">Center of box2 in its own local coordinates</param>
/// <param name="box2Size">Size of box2 in its own local coordinates</param>
/// <param name="margin">Allowed margin for each x, y, z component</param>
/// <returns>True if all corners of box1 are inside box2 plus margin</returns>
public static bool IsBoxInsideBox(Vector3 box1Position,
Quaternion box1Rotation,
Vector3 box1Scale,
Vector3 box1Center,
Vector3 box1Size,
Vector3 box2Position,
Quaternion box2Rotation,
Vector3 box2Scale,
Vector3 box2Center,
Vector3 box2Size,
Vector3 margin = default)
{
Matrix4x4 boxMatrix = Matrix4x4.TRS(box1Position, box1Rotation, box1Scale);
Vector3[] corners = new Vector3[8];
corners[0] = boxMatrix.MultiplyPoint(box1Center + new Vector3(+box1Size.x, +box1Size.y, +box1Size.z) * 0.5f);
corners[1] = boxMatrix.MultiplyPoint(box1Center + new Vector3(+box1Size.x, +box1Size.y, -box1Size.z) * 0.5f);
corners[2] = boxMatrix.MultiplyPoint(box1Center + new Vector3(+box1Size.x, -box1Size.y, +box1Size.z) * 0.5f);
corners[3] = boxMatrix.MultiplyPoint(box1Center + new Vector3(+box1Size.x, -box1Size.y, -box1Size.z) * 0.5f);
corners[4] = boxMatrix.MultiplyPoint(box1Center + new Vector3(-box1Size.x, +box1Size.y, +box1Size.z) * 0.5f);
corners[5] = boxMatrix.MultiplyPoint(box1Center + new Vector3(-box1Size.x, +box1Size.y, -box1Size.z) * 0.5f);
corners[6] = boxMatrix.MultiplyPoint(box1Center + new Vector3(-box1Size.x, -box1Size.y, +box1Size.z) * 0.5f);
corners[7] = boxMatrix.MultiplyPoint(box1Center + new Vector3(-box1Size.x, -box1Size.y, -box1Size.z) * 0.5f);
for (int i = 0; i < 8; ++i)
{
if (!corners[i].IsInsideBox(box2Position, box2Rotation, box2Scale, box2Center, box2Size, margin))
{
return false;
}
}
return true;
}
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 94fda00f50a40c245b7a2825b2448da9
timeCreated: 1501489697
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,317 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrUniversalLocalAxes.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UltimateXR.Extensions.Unity.Math;
using UnityEngine;
namespace UltimateXR.Core.Math
{
/// <summary>
/// <para>
/// Different parts of the framework need to deal with axes. These algorithms like IK solvers or avatar
/// components need to know exactly where 'forward' is or which axis points to the right in avatar-space.
/// Since modelling packages and artists may rig objects using arbitrary coordinate systems we need a way to
/// perform operations in a way that takes this into account. The code also needs to remain readable since many
/// math operations may increase complexity. Readability is favoured here over performance.
/// </para>
/// <para>
/// This class allows to transform from arbitrary coordinate systems to a universal one where different rotations
/// can then be performed and vice versa.
/// One example would be a finger bone curl. We create the convention that forward is the axis from one bone to
/// the next, up points upwards and right would be the axis around which the bone should rotate to curl. This is OK
/// but now we face the problem that different modelling packages or artists rig fingers in completely different
/// ways using all varieties of axis systems. The purpose of this class is to help creating a system where
/// operations can be performed in this universal system to follow our conventions and then rotated "back" to any
/// kind of coordinate system afterwards.
/// </para>
/// <para>
/// tl;dr A class that helps us operate with rotations and angles of an object no matter which convention the
/// 3D assets use. We call 'Universal' the coordinate system we use as convention for our computations, we then
/// can use <see cref="UniversalToActualAxesRotation" /> to transform the object back to its actual axes.
/// This way our computations do not care which coordinate system the assets use, and is essential to simplify
/// operations like inverse kinematics or angle computations.
/// </para>
/// </summary>
[Serializable]
public class UxrUniversalLocalAxes
{
#region Inspector Properties/Serialized Fields
[SerializeField] private Transform _transform;
[SerializeField] private Vector3 _localRight = Vector3.right;
[SerializeField] private Vector3 _localUp = Vector3.up;
[SerializeField] private Vector3 _localForward = Vector3.forward;
[SerializeField] private Quaternion _universalToActualAxesRotation = Quaternion.identity;
[SerializeField] private Quaternion _initialRotation = Quaternion.identity;
[SerializeField] private Quaternion _initialLocalRotation = Quaternion.identity;
[SerializeField] private Quaternion _initialLocalReferenceRotation = Quaternion.identity;
[SerializeField] private Quaternion _initialUniversalLocalReferenceRotation = Quaternion.identity;
[SerializeField] private Vector3 _initialPosition = Vector3.zero;
[SerializeField] private Vector3 _initialLocalPosition = Vector3.zero;
#endregion
#region Public Types & Data
/// <summary>
/// Gets the universal 'right' direction in world space.
/// </summary>
public Vector3 WorldRight => _transform.TransformDirection(LocalRight);
/// <summary>
/// Gets the universal 'up' direction in world space.
/// </summary>
public Vector3 WorldUp => _transform.TransformDirection(LocalUp);
/// <summary>
/// Gets the universal 'forward' direction in world space.
/// </summary>
public Vector3 WorldForward => _transform.TransformDirection(LocalForward);
/// <summary>
/// Gets the local object rotation in universal convention
/// </summary>
public Quaternion UniversalLocalRotation => _transform.localRotation * Quaternion.Inverse(UniversalToActualAxesRotation);
/// <summary>
/// Gets the object rotation in universal convention
/// </summary>
public Quaternion UniversalRotation => _transform.rotation * Quaternion.Inverse(UniversalToActualAxesRotation);
/// <summary>
/// Gets the universal 'right' direction in transform's local space.
/// </summary>
public Vector3 LocalRight
{
get => _localRight;
private set => _localRight = value;
}
/// <summary>
/// Gets the universal 'up' direction in transform's local space.
/// </summary>
public Vector3 LocalUp
{
get => _localUp;
private set => _localUp = value;
}
/// <summary>
/// Gets the universal 'forward' direction in transform's local space.
/// </summary>
public Vector3 LocalForward
{
get => _localForward;
private set => _localForward = value;
}
/// <summary>
/// Gets the rotation that transforms from the universal axes to the convention that the transform follows.
/// <example>
/// <code>
/// // universalRotation may be a rotation around the y axis, where we know
/// // exactly that y points upwards in that space.
/// // This rotation will rotate an object around the "universal" y axis no
/// // matter where his actual axes point to.
/// transform.rotation = universalRotation * UniversalToActualAxesRotation;
/// </code>
/// </example>
/// </summary>
public Quaternion UniversalToActualAxesRotation
{
get => _universalToActualAxesRotation;
private set => _universalToActualAxesRotation = value;
}
/// <summary>
/// Gets the transform's rotation at the time of setting the object up.
/// </summary>
public Quaternion InitialRotation
{
get => _initialRotation;
private set => _initialRotation = value;
}
/// <summary>
/// Gets the transform's local rotation at the time of setting the object up.
/// </summary>
public Quaternion InitialLocalRotation
{
get => _initialLocalRotation;
private set => _initialLocalRotation = value;
}
/// <summary>
/// Gets the transform's rotation with respect to the reference transform at the time of setting the object up.
/// This will only contain a rotation when the constructor using a reference transform was used.
/// </summary>
public Quaternion InitialLocalReferenceRotation
{
get => _initialLocalReferenceRotation;
private set => _initialLocalReferenceRotation = value;
}
/// <summary>
/// Gets the transform's rotation (in universal coordinates) with respect to the reference transform at the time of
/// setting the object up.
/// This will only contain a rotation when the constructor using a reference transform was used.
/// </summary>
public Quaternion InitialUniversalLocalReferenceRotation
{
get => _initialUniversalLocalReferenceRotation;
private set => _initialUniversalLocalReferenceRotation = value;
}
/// <summary>
/// Gets the transform's position at the time of setting the object up.
/// </summary>
public Vector3 InitialPosition
{
get => _initialPosition;
private set => _initialPosition = value;
}
/// <summary>
/// Gets the transform's local position at the time of setting the object up.
/// </summary>
public Vector3 InitialLocalPosition
{
get => _initialLocalPosition;
private set => _initialLocalPosition = value;
}
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// Uses universalReference to check which axes of a transform are actually the ones
/// that are right, up and forward. For example, universalReference may be the avatar
/// root where we know that right, up and forward point to these actual directions and
/// we want to know which axes of an upper body part point to these directions too.
/// These may be completely different depending on the modelling package or artist.
/// Using this class we can easily check which one points upwards and create a small
/// chest torsion by rotating around this axis.
/// </summary>
/// <param name="transform">Transform to create the universal axes for</param>
/// <param name="universalReference">
/// The transform to use as a reference for the universal right, up and forward directions.
/// </param>
public UxrUniversalLocalAxes(Transform transform, Transform universalReference)
{
_transform = transform;
LocalRight = transform.InverseTransformDirection(universalReference != null ? universalReference.right : Vector3.right).GetClosestAxis();
LocalUp = transform.InverseTransformDirection(universalReference != null ? universalReference.up : Vector3.up).GetClosestAxis();
LocalForward = transform.InverseTransformDirection(universalReference != null ? universalReference.forward : Vector3.forward).GetClosestAxis();
UniversalToActualAxesRotation = GetUniversalToActualAxesRotation();
InitialRotation = transform.rotation;
InitialLocalRotation = transform.localRotation;
InitialLocalReferenceRotation = Quaternion.Inverse(universalReference.rotation) * transform.rotation;
InitialUniversalLocalReferenceRotation = InitialLocalReferenceRotation * Quaternion.Inverse(UniversalToActualAxesRotation);
InitialPosition = transform.position;
InitialLocalPosition = transform.localPosition;
}
/// <summary>
/// Default constructor is private to make it inaccessible.
/// </summary>
private UxrUniversalLocalAxes()
{
}
#endregion
#region Public Methods
/// <summary>
/// Creates a UniversalLocalAxes object describing the universal local axes for the given transform.
/// </summary>
/// <param name="transform">The transform the UniversalLocalAxes object is for</param>
/// <param name="universalLocalRight">
/// Which vector in the transform local coordinates points to the 'right' direction in the universal convention
/// </param>
/// <param name="universalLocalUp">
/// Which vector in the transform local coordinates points to the 'up' direction in the universal convention
/// </param>
/// <param name="universalLocalForward">
/// Which vector in the transform local coordinates points to the 'forward' direction in the universal convention
/// </param>
/// <returns>
/// UniversalLocalAxes object that allows us to compute object rotations in a universal
/// space and then apply it to a transform that can have any kind of axis convention
/// (x may point up, z down...)
/// </returns>
public static UxrUniversalLocalAxes FromAxes(Transform transform, Vector3 universalLocalRight, Vector3 universalLocalUp, Vector3 universalLocalForward)
{
UxrUniversalLocalAxes localAxes = new UxrUniversalLocalAxes();
localAxes._transform = transform;
localAxes.LocalRight = universalLocalRight;
localAxes.LocalUp = universalLocalUp;
localAxes.LocalForward = universalLocalForward;
localAxes.InitialRotation = transform.rotation;
localAxes.InitialLocalRotation = transform.localRotation;
localAxes.InitialLocalReferenceRotation = Quaternion.identity;
localAxes.InitialUniversalLocalReferenceRotation = Quaternion.identity;
localAxes.InitialPosition = transform.position;
localAxes.InitialLocalPosition = transform.localPosition;
localAxes.UniversalToActualAxesRotation = localAxes.GetUniversalToActualAxesRotation();
return localAxes;
}
/// <summary>
/// See <see cref="FromAxes" />.
/// </summary>
public static UxrUniversalLocalAxes FromRightUp(Transform transform, Vector3 universalLocalRight, Vector3 universalLocalUp)
{
return FromAxes(transform, universalLocalRight, universalLocalUp, Vector3.Cross(universalLocalRight, universalLocalUp));
}
/// <summary>
/// See <see cref="FromAxes" />.
/// </summary>
public static UxrUniversalLocalAxes FromRightForward(Transform transform, Vector3 universalLocalRight, Vector3 universalLocalForward)
{
return FromAxes(transform, universalLocalRight, Vector3.Cross(universalLocalForward, universalLocalRight), universalLocalForward);
}
/// <summary>
/// See <see cref="FromAxes" />.
/// </summary>
public static UxrUniversalLocalAxes FromUpForward(Transform transform, Vector3 universalLocalUp, Vector3 universalLocalForward)
{
return FromAxes(transform, Vector3.Cross(universalLocalUp, universalLocalForward), universalLocalUp, universalLocalForward);
}
#endregion
#region Private Methods
/// <summary>
/// Computes the rotation that transforms from the universal coordinate system to the convention that the transform
/// follows.
/// </summary>
private Quaternion GetUniversalToActualAxesRotation()
{
Matrix4x4 matrix = new Matrix4x4();
matrix.SetColumn(0, LocalRight);
matrix.SetColumn(1, LocalUp);
matrix.SetColumn(2, LocalForward);
matrix.SetColumn(3, new Vector4(0, 0, 0, 1));
return Quaternion.Inverse(matrix.rotation);
}
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 92dff33db2971124397cffe499bf092a
timeCreated: 1549525217
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d2a7e91ab0a2ab049b171744aece6cb2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,44 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IUxrSerializable.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Core.Serialization
{
/// <summary>
/// Interface to add serialization capabilities to a class.
/// </summary>
public interface IUxrSerializable
{
#region Public Types & Data
/// <summary>
/// Gets the current serialization version of the class that implements the interface. It has the same goal as
/// <see cref="UxrConstants.Serialization.CurrentBinaryVersion" /> but this version property is specific to each class
/// that implements the <see cref="IUxrSerializable" /> interface, which may be used outside the UltimateXR scope,
/// in user specific classes that want to benefit from serialization.<br />
/// Each class that implement the <see cref="IUxrSerializable" /> interface may have its own version. It is a number
/// that gets incremented by one each time the serialization format of the class that implements this interface
/// changes, enabling backwards compatibility.
/// </summary>
int SerializationVersion { get; }
#endregion
#region Public Methods
/// <summary>
/// Serializes or deserializes the object. The serializer interface uses the same methods to serialize and to
/// deserialize, instead of requiring separate serialization and deserialization methods.
/// This simplifies implementations and helps eliminating bugs due to inconsistencies between reading and writing.
/// </summary>
/// <param name="serializer">Serializer to use</param>
/// <param name="serializationVersion">
/// When reading it tells the <see cref="SerializationVersion" /> the data was
/// serialized with. When writing it uses the latest <see cref="SerializationVersion" /> version.
/// </param>
void Serialize(IUxrSerializer serializer, int serializationVersion);
#endregion
}
}

View File

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

View File

@@ -0,0 +1,293 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IUxrSerializer.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using UltimateXR.Core.Math;
using UltimateXR.Core.Unique;
using UnityEngine;
namespace UltimateXR.Core.Serialization
{
/// <summary>
/// Interface to serialize and deserialize data. It uses a single method that performs the correct operation (read or
/// write) transparently, avoiding inconsistencies that can happen when using separate serialize/deserialize methods
/// on complex data with versioning.
/// </summary>
public interface IUxrSerializer : IDisposable
{
#region Public Types & Data
/// <summary>
/// Gets the serialization version. When reading it tells the version the data was serialized with. When writing it
/// uses the latest version.
/// </summary>
int Version { get; }
/// <summary>
/// Gets whether the operation is writing data (serializing) or reading data (deserializing).
/// </summary>
bool IsReading { get; }
#endregion
#region Public Methods
/// <summary>
/// Serializes or deserializes a boolean value.
/// </summary>
/// <param name="value">The element to serialize or deserialize</param>
void Serialize(ref bool value);
/// <summary>
/// Serializes or deserializes an sbyte value.
/// </summary>
/// <param name="value">The element to serialize or deserialize</param>
void Serialize(ref sbyte value);
/// <summary>
/// Serializes or deserializes a byte value.
/// </summary>
/// <param name="value">The element to serialize or deserialize</param>
void Serialize(ref byte value);
/// <summary>
/// Serializes or deserializes a char value.
/// </summary>
/// <param name="value">The element to serialize or deserialize</param>
void Serialize(ref char value);
/// <summary>
/// Serializes or deserializes an int value.
/// </summary>
/// <param name="value">The element to serialize or deserialize</param>
void Serialize(ref int value);
/// <summary>
/// Serializes or deserializes a uint value.
/// </summary>
/// <param name="value">The element to serialize or deserialize</param>
void Serialize(ref uint value);
/// <summary>
/// Serializes or deserializes a long value.
/// </summary>
/// <param name="value">The element to serialize or deserialize</param>
void Serialize(ref long value);
/// <summary>
/// Serializes or deserializes a ulong value.
/// </summary>
/// <param name="value">The element to serialize or deserialize</param>
void Serialize(ref ulong value);
/// <summary>
/// Serializes or deserializes a float value.
/// </summary>
/// <param name="value">The element to serialize or deserialize</param>
void Serialize(ref float value);
/// <summary>
/// Serializes or deserializes double value.
/// </summary>
/// <param name="value">The element to serialize or deserialize</param>
void Serialize(ref double value);
/// <summary>
/// Serializes or deserializes decimal value.
/// </summary>
/// <param name="value">The element to serialize or deserialize</param>
void Serialize(ref decimal value);
/// <summary>
/// Serializes or deserializes string value.
/// </summary>
/// <param name="value">The element to serialize or deserialize</param>
/// <remarks>Implementations must support serializing null strings</remarks>
void Serialize(ref string value);
/// <summary>
/// Serializes or deserializes an Enum value.
/// </summary>
/// <param name="value">The element to serialize or deserialize</param>
/// <remarks>Implementations must support null</remarks>
void SerializeEnum<T>(ref T value) where T : Enum;
/// <summary>
/// Serializes or deserializes a type value.
/// </summary>
/// <param name="value">The element to serialize or deserialize</param>
void Serialize(ref Type value);
/// <summary>
/// Serializes or deserializes a <see cref="Guid" /> value.
/// </summary>
/// <param name="value">The element to serialize or deserialize</param>
void Serialize(ref Guid value);
/// <summary>
/// Serializes or deserializes a tuple.
/// </summary>
/// <param name="value">The element to serialize or deserialize</param>
void Serialize<T1, T2>(ref (T1, T2) value);
/// <summary>
/// Serializes or deserializes an array.
/// </summary>
/// <param name="values">The elements to serialize or deserialize</param>
/// <typeparam name="T">The type of the elements</typeparam>
/// <remarks>Implementations must support null</remarks>
void Serialize<T>(ref T[] values);
/// <summary>
/// Serializes or deserializes an array, where each element might be of a different type. The type of each element will
/// be stored next to the object.
/// </summary>
/// <param name="values">The elements to serialize or deserialize</param>
/// <remarks>Implementations must support null</remarks>
void Serialize(ref object[] values);
/// <summary>
/// Serializes or deserializes a list.
/// </summary>
/// <param name="values">The elements to serialize or deserialize</param>
/// <typeparam name="T">The type of the elements</typeparam>
/// <remarks>Implementations must support null</remarks>
void Serialize<T>(ref List<T> values);
/// <summary>
/// Serializes or deserializes a list of objects, where each element might be of a different type. The type of each
/// element will be stored next to the object.
/// </summary>
/// <param name="values">The elements to serialize or deserialize</param>
/// <remarks>Implementations must support null</remarks>
void Serialize(ref List<object> values);
/// <summary>
/// Serializes or deserializes a hash set.
/// </summary>
/// <param name="values">The elements to serialize or deserialize</param>
/// <typeparam name="T">The type of the elements</typeparam>
/// <remarks>Implementations must support null</remarks>
void Serialize<T>(ref HashSet<T> values);
/// <summary>
/// Serializes or deserializes a hash set of objects, where each element might be of a different type. The type of each
/// element will be stored next to the object.
/// </summary>
/// <param name="values">The elements to serialize or deserialize</param>
/// <remarks>Implementations must support null</remarks>
void Serialize(ref HashSet<object> values);
/// <summary>
/// Serializes or deserializes a dictionary.
/// </summary>
/// <param name="values">The values</param>
/// <typeparam name="TKey">The key type</typeparam>
/// <typeparam name="TValue">The value type</typeparam>
/// <remarks>Implementations must support null</remarks>
void Serialize<TKey, TValue>(ref Dictionary<TKey, TValue> values);
/// <summary>
/// Serializes or deserializes a <see cref="DateTime" /> value.
/// </summary>
/// <param name="value">The element to serialize or deserialize</param>
void Serialize(ref DateTime dateTime);
/// <summary>
/// Serializes or deserializes a <see cref="TimeSpan" /> value.
/// </summary>
/// <param name="value">The element to serialize or deserialize</param>
void Serialize(ref TimeSpan timeSpan);
/// <summary>
/// Serializes or deserializes a Vector2 value.
/// </summary>
/// <param name="value">The element to serialize or deserialize</param>
void Serialize(ref Vector2 value);
/// <summary>
/// Serializes or deserializes a Vector3 value.
/// </summary>
/// <param name="value">The element to serialize or deserialize</param>
void Serialize(ref Vector3 value);
/// <summary>
/// Serializes or deserializes a Vector4 value.
/// </summary>
/// <param name="value">The element to serialize or deserialize</param>
void Serialize(ref Vector4 value);
/// <summary>
/// Serializes or deserializes Color value.
/// </summary>
/// <param name="value">The element to serialize or deserialize</param>
void Serialize(ref Color value);
/// <summary>
/// Serializes or deserializes Color32 value.
/// </summary>
/// <param name="value">The element to serialize or deserialize</param>
void Serialize(ref Color32 value);
/// <summary>
/// Serializes or deserializes Quaternion value.
/// </summary>
/// <param name="value">The element to serialize or deserialize</param>
void Serialize(ref Quaternion value);
/// <summary>
/// Serializes or deserializes Matrix4x4 value.
/// </summary>
/// <param name="value">The element to serialize or deserialize</param>
void Serialize(ref Matrix4x4 value);
/// <summary>
/// Serializes or deserializes a component with the <see cref="IUxrUniqueId" /> interface, storing only the
/// <see cref="IUxrUniqueId.UniqueId" />.
/// </summary>
/// <param name="unique">The element to serialize or deserialize</param>
/// <remarks>Implementations must support null</remarks>
void SerializeUniqueComponent(ref IUxrUniqueId unique);
/// <summary>
/// Serializes or deserializes a component with the <see cref="IUxrUniqueId" /> interface, storing only the
/// <see cref="IUxrUniqueId.UniqueId" />.
/// </summary>
/// <param name="component">The element to serialize or deserialize</param>
/// <remarks>Implementations must support null</remarks>
void SerializeUniqueComponent<T>(ref T component) where T : Component, IUxrUniqueId;
/// <summary>
/// Serializes or deserializes an object that implements the <see cref="IUxrSerializable" /> interface.
/// </summary>
/// <param name="serializable">The element to serialize or deserialize</param>
/// <remarks>Implementations must support null</remarks>
void SerializeUxrSerializable(ref IUxrSerializable serializable);
/// <summary>
/// Serializes or deserializes an object that implements the <see cref="IUxrSerializable" /> interface.
/// </summary>
/// <param name="obj">The element to serialize or deserialize</param>
/// <remarks>Implementations must support null</remarks>
void SerializeUxrSerializable<T>(ref T obj) where T : IUxrSerializable;
/// <summary>
/// Serializes or deserializes an <see cref="UxrAxis" /> value.
/// </summary>
/// <param name="axis">The axis to serialize or deserialize</param>
void SerializeAxis(ref UxrAxis axis);
/// <summary>
/// Serializes a variable of a type that is known only at runtime. When writing it will serialize the type
/// together with the value so that it can be deserialized back when reading.
/// </summary>
/// <param name="obj">The element to serialize or deserialize</param>
/// <remarks>Implementations must support null</remarks>
void SerializeAnyVar<T>(ref T obj);
#endregion
}
}

View File

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

View File

@@ -0,0 +1,587 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrBinarySerializer.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.IO;
using UltimateXR.Core.Math;
using UltimateXR.Core.Unique;
using UltimateXR.Extensions.System.IO;
using UnityEngine;
namespace UltimateXR.Core.Serialization
{
/// <summary>
/// Class that helps serializing/deserializing data using a single method instead of two separate Serialize
/// and Deserialize methods. It also stores the serialization version when reading or writing data.
/// </summary>
public class UxrBinarySerializer : IUxrSerializer
{
#region Constructors & Finalizer
/// <summary>
/// Constructor for deserialization.
/// </summary>
/// <param name="reader">Binary reader with the data</param>
/// <param name="version">Version that the data was serialized with</param>
public UxrBinarySerializer(BinaryReader reader, int version)
{
Version = version;
IsReading = true;
Reader = reader;
}
/// <summary>
/// Constructor for serialization.
/// </summary>
/// <param name="writer">Binary writer to output the data</param>
public UxrBinarySerializer(BinaryWriter writer)
{
Version = UxrConstants.Serialization.CurrentBinaryVersion;
IsReading = false;
Writer = writer;
}
#endregion
#region Implicit IDisposable
/// <inheritdoc />
public void Dispose()
{
}
#endregion
#region Implicit IUxrSerializer
/// <summary>
/// Gets, when reading, the version that the data was serialized with. When writing, it gets the latest version that it
/// is being serialized with, which is equal to <see cref="UxrConstants.Serialization.CurrentBinaryVersion" />.
/// </summary>
public int Version { get; }
/// <summary>
/// Gets whether the serializer is reading (using <see cref="Reader" />) or writing (using <see cref="Writer" />).
/// </summary>
public bool IsReading { get; }
/// <inheritdoc />
public void Serialize(ref bool value)
{
if (IsReading)
{
value = Reader.ReadBoolean();
}
else
{
Writer.Write(value);
}
}
/// <inheritdoc />
public void Serialize(ref sbyte value)
{
if (IsReading)
{
value = Reader.ReadSByte();
}
else
{
Writer.Write(value);
}
}
/// <inheritdoc />
public void Serialize(ref byte value)
{
if (IsReading)
{
value = Reader.ReadByte();
}
else
{
Writer.Write(value);
}
}
/// <inheritdoc />
public void Serialize(ref char value)
{
if (IsReading)
{
value = Reader.ReadChar();
}
else
{
Writer.Write(value);
}
}
/// <inheritdoc />
public void Serialize(ref int value)
{
if (IsReading)
{
value = Reader.ReadCompressedInt32(Version);
}
else
{
Writer.WriteCompressedInt32(value);
}
}
/// <inheritdoc />
public void Serialize(ref uint value)
{
if (IsReading)
{
value = Reader.ReadCompressedUInt32(Version);
}
else
{
Writer.WriteCompressedUInt32(value);
}
}
/// <inheritdoc />
public void Serialize(ref long value)
{
if (IsReading)
{
value = Reader.ReadCompressedInt64(Version);
}
else
{
Writer.WriteCompressedInt64(value);
}
}
/// <inheritdoc />
public void Serialize(ref ulong value)
{
if (IsReading)
{
value = Reader.ReadCompressedUInt64(Version);
}
else
{
Writer.WriteCompressedUInt64(value);
}
}
/// <inheritdoc />
public void Serialize(ref float value)
{
if (IsReading)
{
value = Reader.ReadSingle();
}
else
{
Writer.Write(value);
}
}
/// <inheritdoc />
public void Serialize(ref double value)
{
if (IsReading)
{
value = Reader.ReadDouble();
}
else
{
Writer.Write(value);
}
}
/// <inheritdoc />
public void Serialize(ref decimal value)
{
if (IsReading)
{
value = Reader.ReadDecimal();
}
else
{
Writer.Write(value);
}
}
/// <inheritdoc />
public void Serialize(ref string value)
{
if (IsReading)
{
bool nullCheck = Reader.ReadBoolean();
value = nullCheck ? Reader.ReadString() : null;
}
else
{
Writer.Write(value != null);
if (value != null)
{
Writer.Write(value);
}
}
}
/// <inheritdoc />
public void SerializeEnum<T>(ref T value) where T : Enum
{
if (IsReading)
{
value = Reader.ReadEnum<T>(Version);
}
else
{
Writer.WriteEnum(value);
}
}
/// <inheritdoc />
public void Serialize(ref Type value)
{
if (IsReading)
{
value = Reader.ReadType(Version);
}
else
{
Writer.Write(value);
}
}
/// <inheritdoc />
public void Serialize(ref Guid value)
{
if (IsReading)
{
value = Reader.ReadGuid(Version);
}
else
{
Writer.Write(value);
}
}
/// <inheritdoc />
public void Serialize<T1, T2>(ref (T1, T2) value)
{
if (IsReading)
{
value = Reader.ReadTuple<T1, T2>(Version);
}
else
{
Writer.Write(value);
}
}
/// <inheritdoc />
public void Serialize<T>(ref T[] values)
{
if (IsReading)
{
values = Reader.ReadArray<T>(Version);
}
else
{
Writer.Write(values);
}
}
/// <inheritdoc />
public void Serialize(ref object[] values)
{
if (IsReading)
{
values = Reader.ReadObjectArray(Version);
}
else
{
Writer.Write(values);
}
}
/// <inheritdoc />
public void Serialize<T>(ref List<T> values)
{
if (IsReading)
{
values = Reader.ReadList<T>(Version);
}
else
{
Writer.Write(values);
}
}
/// <inheritdoc />
public void Serialize(ref List<object> values)
{
if (IsReading)
{
values = Reader.ReadObjectList(Version);
}
else
{
Writer.Write(values);
}
}
/// <inheritdoc />
public void Serialize<TKey, TValue>(ref Dictionary<TKey, TValue> values)
{
if (IsReading)
{
values = Reader.ReadDictionary<TKey, TValue>(Version);
}
else
{
Writer.Write(values);
}
}
/// <inheritdoc />
public void Serialize<T>(ref HashSet<T> values)
{
if (IsReading)
{
values = Reader.ReadHashSet<T>(Version);
}
else
{
Writer.Write(values);
}
}
/// <inheritdoc />
public void Serialize(ref HashSet<object> values)
{
if (IsReading)
{
values = Reader.ReadObjectHashSet(Version);
}
else
{
Writer.Write(values);
}
}
/// <inheritdoc />
public void Serialize(ref DateTime value)
{
if (IsReading)
{
value = Reader.ReadDateTime(Version);
}
else
{
Writer.Write(value);
}
}
/// <inheritdoc />
public void Serialize(ref TimeSpan value)
{
if (IsReading)
{
value = Reader.ReadTimeSpan(Version);
}
else
{
Writer.Write(value);
}
}
/// <inheritdoc />
public void Serialize(ref Vector2 value)
{
if (IsReading)
{
value = Reader.ReadVector2(Version);
}
else
{
Writer.Write(value);
}
}
/// <inheritdoc />
public void Serialize(ref Vector3 value)
{
if (IsReading)
{
value = Reader.ReadVector3(Version);
}
else
{
Writer.Write(value);
}
}
/// <inheritdoc />
public void Serialize(ref Vector4 value)
{
if (IsReading)
{
value = Reader.ReadVector4(Version);
}
else
{
Writer.Write(value);
}
}
/// <inheritdoc />
public void Serialize(ref Color value)
{
if (IsReading)
{
value = Reader.ReadColor(Version);
}
else
{
Writer.Write(value);
}
}
/// <inheritdoc />
public void Serialize(ref Color32 value)
{
if (IsReading)
{
value = Reader.ReadColor32(Version);
}
else
{
Writer.Write(value);
}
}
/// <inheritdoc />
public void Serialize(ref Quaternion value)
{
if (IsReading)
{
value = Reader.ReadQuaternion(Version);
}
else
{
Writer.Write(value);
}
}
/// <inheritdoc />
public void Serialize(ref Matrix4x4 value)
{
if (IsReading)
{
value = Reader.ReadMatrix(Version);
}
else
{
Writer.Write(value);
}
}
/// <inheritdoc />
public void SerializeUniqueComponent(ref IUxrUniqueId unique)
{
if (IsReading)
{
unique = Reader.ReadUniqueComponent(Version);
}
else
{
Writer.WriteUniqueComponent(unique);
}
}
/// <inheritdoc />
public void SerializeUniqueComponent<T>(ref T component) where T : Component, IUxrUniqueId
{
if (IsReading)
{
component = Reader.ReadUniqueComponent(Version) as T;
}
else
{
Writer.WriteUniqueComponent(component);
}
}
/// <inheritdoc />
public void SerializeUxrSerializable(ref IUxrSerializable serializable)
{
if (IsReading)
{
serializable = Reader.ReadUxrSerializable(Version);
}
else
{
Writer.WriteUxrSerializable(serializable);
}
}
/// <inheritdoc />
public void SerializeUxrSerializable<T>(ref T obj) where T : IUxrSerializable
{
if (IsReading)
{
obj = (T)Reader.ReadUxrSerializable(Version);
}
else
{
Writer.WriteUxrSerializable(obj);
}
}
/// <inheritdoc />
public void SerializeAxis(ref UxrAxis axis)
{
if (IsReading)
{
axis = Reader.ReadAxis(Version);
}
else
{
Writer.WriteAxis(axis);
}
}
/// <inheritdoc />
public void SerializeAnyVar<T>(ref T obj)
{
if (IsReading)
{
obj = (T)Reader.ReadAnyVar(Version);
}
else
{
Writer.WriteAnyVar(obj);
}
}
#endregion
#region Private Types & Data
/// <summary>
/// Gets the reader, if <see cref="IsReading" /> is true. Otherwise it is null.
/// </summary>
private BinaryReader Reader { get; }
/// <summary>
/// Gets the writer, if <see cref="IsReading" /> is false. Otherwise it is null.
/// </summary>
private BinaryWriter Writer { get; }
#endregion
}
}

View File

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

View File

@@ -0,0 +1,259 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrDummySerializer.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using UltimateXR.Core.Math;
using UltimateXR.Core.StateSave;
using UltimateXR.Core.Unique;
using UnityEngine;
namespace UltimateXR.Core.Serialization
{
/// <summary>
/// Serializer that doesn't read or write data. It can be used by <see cref="IUxrStateSave" /> implementations to cache
/// initial object data without any read or write operations.
/// </summary>
public class UxrDummySerializer : IUxrSerializer
{
#region Constructors & Finalizer
/// <summary>
/// Constructor for deserialization.
/// </summary>
/// <param name="isReading">Whether to initialize in read mode or write mode</param>
/// <param name="version">Version that the data was serialized with</param>
public UxrDummySerializer(bool isReading, int version)
{
Version = version;
IsReading = isReading;
}
#endregion
#region Implicit IDisposable
/// <inheritdoc />
public void Dispose()
{
}
#endregion
#region Implicit IUxrSerializer
/// <inheritdoc />
public int Version { get; }
/// <inheritdoc />
public bool IsReading { get; }
/// <inheritdoc />
public void Serialize(ref bool value)
{
}
/// <inheritdoc />
public void Serialize(ref sbyte value)
{
}
/// <inheritdoc />
public void Serialize(ref byte value)
{
}
/// <inheritdoc />
public void Serialize(ref char value)
{
}
/// <inheritdoc />
public void Serialize(ref int value)
{
}
/// <inheritdoc />
public void Serialize(ref uint value)
{
}
/// <inheritdoc />
public void Serialize(ref long value)
{
}
/// <inheritdoc />
public void Serialize(ref ulong value)
{
}
/// <inheritdoc />
public void Serialize(ref float value)
{
}
/// <inheritdoc />
public void Serialize(ref double value)
{
}
/// <inheritdoc />
public void Serialize(ref decimal value)
{
}
/// <inheritdoc />
public void Serialize(ref string value)
{
}
/// <inheritdoc />
public void SerializeEnum<T>(ref T value) where T : Enum
{
}
/// <inheritdoc />
public void Serialize(ref Type value)
{
}
/// <inheritdoc />
public void Serialize(ref Guid value)
{
}
/// <inheritdoc />
public void Serialize<T1, T2>(ref (T1, T2) value)
{
}
/// <inheritdoc />
public void Serialize<T>(ref T[] values)
{
}
/// <inheritdoc />
public void Serialize(ref object[] values)
{
}
/// <inheritdoc />
public void Serialize<T>(ref List<T> values)
{
}
/// <inheritdoc />
public void Serialize(ref List<object> values)
{
}
/// <inheritdoc />
public void Serialize<TKey, TValue>(ref Dictionary<TKey, TValue> values)
{
}
/// <inheritdoc />
public void Serialize<T>(ref HashSet<T> values)
{
}
/// <inheritdoc />
public void Serialize(ref HashSet<object> values)
{
}
/// <inheritdoc />
public void Serialize(ref DateTime value)
{
}
/// <inheritdoc />
public void Serialize(ref TimeSpan value)
{
}
/// <inheritdoc />
public void Serialize(ref Vector2 value)
{
}
/// <inheritdoc />
public void Serialize(ref Vector3 value)
{
}
/// <inheritdoc />
public void Serialize(ref Vector4 value)
{
}
/// <inheritdoc />
public void Serialize(ref Color value)
{
}
/// <inheritdoc />
public void Serialize(ref Color32 value)
{
}
/// <inheritdoc />
public void Serialize(ref Quaternion value)
{
}
/// <inheritdoc />
public void Serialize(ref Matrix4x4 value)
{
}
/// <inheritdoc />
public void SerializeUniqueComponent(ref IUxrUniqueId unique)
{
}
/// <inheritdoc />
public void SerializeUniqueComponent<T>(ref T component) where T : Component, IUxrUniqueId
{
}
/// <inheritdoc />
public void SerializeUxrSerializable(ref IUxrSerializable serializable)
{
}
/// <inheritdoc />
public void SerializeUxrSerializable<T>(ref T obj) where T : IUxrSerializable
{
}
/// <inheritdoc />
public void SerializeAxis(ref UxrAxis axis)
{
}
/// <inheritdoc />
public void SerializeAnyVar<T>(ref T obj)
{
}
#endregion
#region Public Types & Data
/// <summary>
/// Gets a dummy serializer initialized in read mode.
/// </summary>
public static UxrDummySerializer ReadModeSerializer = new UxrDummySerializer(true, UxrConstants.Serialization.CurrentBinaryVersion);
/// <summary>
/// Gets a dummy serializer initialized in write mode.
/// </summary>
public static UxrDummySerializer WriteModeSerializer = new UxrDummySerializer(false, UxrConstants.Serialization.CurrentBinaryVersion);
#endregion
}
}

View File

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

View File

@@ -0,0 +1,23 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrSerializationFormat.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Core.Serialization
{
/// <summary>
/// Enumerates the different serialization formats that are supported.
/// </summary>
public enum UxrSerializationFormat
{
/// <summary>
/// Binary uncompressed format.
/// </summary>
BinaryUncompressed,
/// <summary>
/// Binary compressed using Gzip.
/// </summary>
BinaryGzip
}
}

View File

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

View File

@@ -0,0 +1,68 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrVarType.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.StateSync;
namespace UltimateXR.Core.Serialization
{
/// <summary>
/// Enumerates the variable types supported by serialization, for example
/// <see cref="UxrPropertyChangedSyncEventArgs" />.
/// </summary>
public enum UxrVarType
{
Unknown = 0,
// C#
Bool = 1,
SignedByte = 2,
Byte = 3,
Char = 4,
Int = 5,
UnsignedInt = 6,
Long = 7,
UnsignedLong = 8,
Float = 9,
Double = 10,
Decimal = 11,
String = 12,
Enum = 13,
Type = 20,
Guid = 21,
Tuple = 22,
// C# collections
Array = 30,
ObjectArray = 31,
List = 32,
ObjectList = 33,
Dictionary = 34,
HashSet = 35,
ObjectHashSet = 36,
// Other C# types
DateTime = 50,
TimeSpan = 51,
// Unity
Vector2 = 100,
Vector3 = 101,
Vector4 = 102,
Color = 103,
Color32 = 104,
Quaternion = 105,
Matrix4x4 = 106,
// UXR
IUxrUnique = 200,
IUxrSerializable = 201,
UxrAxis = 202
}
}

View File

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

View File

@@ -0,0 +1,216 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrVarTypeExt.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using UltimateXR.Core.Math;
using UltimateXR.Core.Settings;
using UltimateXR.Core.Unique;
using UnityEngine;
namespace UltimateXR.Core.Serialization
{
/// <summary>
/// Extensions for <see cref="UxrVarType" />.
/// </summary>
public static class UxrVarTypeExt
{
#region Public Methods
/// <summary>
/// Tries to get the type of an object that support serialization/deserialization using UltimateXR.
/// </summary>
/// <param name="obj">Object to get the type of</param>
/// <returns>Type from <see cref="UxrVarType" /> or <see cref="UxrVarType.Unknown" /> if not supported</returns>
public static UxrVarType GetType<T>(T obj)
{
if (obj == null)
{
return UxrVarType.Unknown;
}
Type type = obj.GetType();
// C# types
if (obj is bool)
{
return UxrVarType.Bool;
}
if (obj is sbyte)
{
return UxrVarType.SignedByte;
}
if (obj is byte)
{
return UxrVarType.Byte;
}
if (obj is char)
{
return UxrVarType.Char;
}
if (obj is int)
{
return UxrVarType.Int;
}
if (obj is uint)
{
return UxrVarType.UnsignedInt;
}
if (obj is long)
{
return UxrVarType.Long;
}
if (obj is ulong)
{
return UxrVarType.UnsignedLong;
}
if (obj is float)
{
return UxrVarType.Float;
}
if (obj is double)
{
return UxrVarType.Double;
}
if (obj is decimal)
{
return UxrVarType.Decimal;
}
if (obj is string)
{
return UxrVarType.String;
}
if (obj is Enum)
{
return UxrVarType.Enum;
}
if (obj is Type)
{
return UxrVarType.Type;
}
if (obj is Guid)
{
return UxrVarType.Guid;
}
if (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(Tuple<,>) || type.GetGenericTypeDefinition() == typeof(ValueTuple<,>)))
{
return UxrVarType.Tuple;
}
// C# collections
if (type.IsArray)
{
return type.GetElementType() == typeof(object) ? UxrVarType.ObjectArray : UxrVarType.Array;
}
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
{
return type.GetElementType() == typeof(object) ? UxrVarType.ObjectList : UxrVarType.List;
}
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
{
return UxrVarType.Dictionary;
}
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(HashSet<>))
{
return type.GetElementType() == typeof(object) ? UxrVarType.ObjectHashSet : UxrVarType.HashSet;
}
// Other C# types
if (obj is DateTime)
{
return UxrVarType.DateTime;
}
if (obj is TimeSpan)
{
return UxrVarType.TimeSpan;
}
// Unity types
if (obj is Vector2)
{
return UxrVarType.Vector2;
}
if (obj is Vector3)
{
return UxrVarType.Vector3;
}
if (obj is Vector4)
{
return UxrVarType.Vector4;
}
if (obj is Color)
{
return UxrVarType.Color;
}
if (obj is Color32)
{
return UxrVarType.Color32;
}
if (obj is Quaternion)
{
return UxrVarType.Quaternion;
}
if (obj is Matrix4x4)
{
return UxrVarType.Matrix4x4;
}
// UXR types
if (obj is IUxrUniqueId)
{
return UxrVarType.IUxrUnique;
}
if (obj is IUxrSerializable)
{
return UxrVarType.IUxrSerializable;
}
if (obj is UxrAxis)
{
return UxrVarType.UxrAxis;
}
if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.CoreModule} Unknown type in {nameof(GetType)} ({type.FullName})");
}
return UxrVarType.Unknown;
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a5c2c07e2d3697948bbbb560b2f23a70
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,245 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGlobalSettings.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.IO;
using UltimateXR.Core.Serialization;
using UnityEditor;
using UnityEngine;
namespace UltimateXR.Core.Settings
{
/// <summary>
/// UltimateXR global settings. The global settings is stores in a file called UxrGlobalSettings inside a /Resources
/// folder so that it can be loaded at runtime.
/// It can be accessed using the Tools->UltimateXR Unity menu.
/// </summary>
public class UxrGlobalSettings : ScriptableObject
{
#region Inspector Properties/Serialized Fields
[SerializeField] private UxrLogLevel _logLevelAnimation = UxrLogLevel.Warnings;
[SerializeField] private UxrLogLevel _logLevelAvatar = UxrLogLevel.Warnings;
[SerializeField] private UxrLogLevel _logLevelCore = UxrLogLevel.Relevant;
[SerializeField] private UxrLogLevel _logLevelDevices = UxrLogLevel.Relevant;
[SerializeField] private UxrLogLevel _logLevelLocomotion = UxrLogLevel.Relevant;
[SerializeField] private UxrLogLevel _logLevelManipulation = UxrLogLevel.Relevant;
[SerializeField] private UxrLogLevel _logLevelNetworking = UxrLogLevel.Relevant;
[SerializeField] private UxrLogLevel _logLevelRendering = UxrLogLevel.Relevant;
[SerializeField] private UxrLogLevel _logLevelUI = UxrLogLevel.Warnings;
[SerializeField] private UxrLogLevel _logLevelWeapons = UxrLogLevel.Relevant;
[SerializeField] private UxrSerializationFormat _netFormatInitialState = UxrSerializationFormat.BinaryGzip;
[SerializeField] private UxrSerializationFormat _netFormatStateSync = UxrSerializationFormat.BinaryUncompressed;
[SerializeField] private bool _syncGrabbablePhysics = true;
[SerializeField] private float _grabbableSyncIntervalSeconds = UxrConstants.Networking.DefaultGrabbableSyncIntervalSeconds;
#endregion
#region Public Types & Data
/// <summary>
/// Gets the global settings.
/// </summary>
/// <remarks>
/// Global settings must be stored in a file called UxrGlobalSettings inside a /Resources folder.
/// If no global settings could be found, default settings are used
/// </remarks>
public static UxrGlobalSettings Instance
{
get
{
if (s_current == null)
{
s_current = Resources.Load<UxrGlobalSettings>(nameof(UxrGlobalSettings));
if (s_current == null)
{
s_current = CreateInstance<UxrGlobalSettings>();
}
}
return s_current;
}
}
/// <summary>
/// Gets or sets the log level for animation events.
/// </summary>
public UxrLogLevel LogLevelAnimation
{
get => _logLevelAnimation;
set => _logLevelAnimation = value;
}
/// <summary>
/// Gets or sets the log level for avatar events.
/// </summary>
public UxrLogLevel LogLevelAvatar
{
get => _logLevelAvatar;
set => _logLevelAvatar = value;
}
/// <summary>
/// Gets or sets the log level for core events.
/// </summary>
public UxrLogLevel LogLevelCore
{
get => _logLevelCore;
set => _logLevelCore = value;
}
/// <summary>
/// Gets or sets the log level for controller input device events.
/// </summary>
public UxrLogLevel LogLevelDevices
{
get => _logLevelDevices;
set => _logLevelDevices = value;
}
/// <summary>
/// Gets or sets the log level for locomotion events.
/// </summary>
public UxrLogLevel LogLevelLocomotion
{
get => _logLevelLocomotion;
set => _logLevelLocomotion = value;
}
/// <summary>
/// Gets or sets the log level for manipulation events.
/// </summary>
public UxrLogLevel LogLevelManipulation
{
get => _logLevelManipulation;
set => _logLevelManipulation = value;
}
/// <summary>
/// Gets or sets the log level for networking events.
/// </summary>
public UxrLogLevel LogLevelNetworking
{
get => _logLevelNetworking;
set => _logLevelNetworking = value;
}
/// <summary>
/// Gets or sets the log level for rendering events.
/// </summary>
public UxrLogLevel LogLevelRendering
{
get => _logLevelRendering;
set => _logLevelRendering = value;
}
/// <summary>
/// Gets or sets the log level for UI events.
/// </summary>
public UxrLogLevel LogLevelUI
{
get => _logLevelUI;
set => _logLevelUI = value;
}
/// <summary>
/// Gets or sets the log level for weapon events.
/// </summary>
public UxrLogLevel LogLevelWeapons
{
get => _logLevelWeapons;
set => _logLevelWeapons = value;
}
/// <summary>
/// Gets or sets the format of the network message that contains the initial state of the scene upon joining.
/// </summary>
public UxrSerializationFormat NetFormatInitialState
{
get => _netFormatInitialState;
set => _netFormatInitialState = value;
}
/// <summary>
/// Gets or sets the format of the network messages to synchronize state changes.
/// </summary>
public UxrSerializationFormat NetFormatStateSync
{
get => _netFormatStateSync;
set => _netFormatStateSync = value;
}
/// <summary>
/// Gets or sets whether to manually sync physics-driven grabbable objects that do not have native networking
/// components such as NetworkTransform/NetworkRigidbody.
/// </summary>
public bool SyncGrabbablePhysics
{
get => _syncGrabbablePhysics;
set => _syncGrabbablePhysics = value;
}
/// <summary>
/// Gets or sets, when using <see cref="SyncGrabbablePhysics" />, the interval in seconds synchronization messages are
/// sent.
/// </summary>
public float GrabbableSyncIntervalSeconds
{
get => _grabbableSyncIntervalSeconds;
set => _grabbableSyncIntervalSeconds = value;
}
#endregion
#region Public Methods
#if UNITY_EDITOR
/// <summary>
/// Accesses the global settings using the Unity menu.
/// </summary>
[MenuItem(UxrConstants.Editor.MenuPathRoot + "Global Settings", priority = UxrConstants.Editor.PriorityMenuPathRoot)]
public static void SelectInProject()
{
UxrGlobalSettings settings = Resources.Load<UxrGlobalSettings>(nameof(UxrGlobalSettings));
if (settings == null)
{
if (EditorUtility.DisplayDialog("Create global settings?", "Global settings file has not yet been created. Create one now?", "Yes", "Cancel"))
{
settings = CreateInstance<UxrGlobalSettings>();
string resourcesFolder = "Resources";
string directory = $"{Application.dataPath}/{resourcesFolder}/";
if (!File.Exists(directory))
{
Directory.CreateDirectory(directory);
}
AssetDatabase.CreateAsset(settings, $"Assets/{resourcesFolder}/{nameof(UxrGlobalSettings)}.asset");
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
else
{
return;
}
}
Selection.activeObject = settings;
}
#endif
#endregion
#region Private Types & Data
private static UxrGlobalSettings s_current;
#endregion
}
}

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a1fdcd23b97672e45bc3130ba3dd44a3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,102 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IUxrStateSave.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Animation.Interpolation;
using UltimateXR.Core.Serialization;
using UltimateXR.Core.Unique;
namespace UltimateXR.Core.StateSave
{
/// <summary>
/// Interface for components to load/save their partial or complete state. This can be used to save
/// complete sessions to disk to restore the session later. It can also be used to save partial states
/// in a timeline to implement replay functionality.<br />
/// To leverage the implementation of this interface, consider using <see cref="UxrStateSaveImplementer{T}" />.<br />
/// </summary>
public interface IUxrStateSave : IUxrUniqueId
{
#region Public Types & Data
/// <summary>
/// Gets the current serialization version of the component type that implements the interface. It has the same goal as
/// <see cref="UxrConstants.Serialization.CurrentBinaryVersion" /> but this version property is specific to each class
/// that implements the <see cref="IUxrStateSave" /> interface, which may be used outside the UltimateXR scope,
/// in user specific classes that want to benefit from state serialization.<br />
/// Each class that implement the <see cref="IUxrStateSave" /> interface may have its own version. It is a number that
/// gets incremented by one each time the serialization format of the class that implements this interface changes,
/// enabling backwards compatibility.
/// </summary>
int StateSerializationVersion { get; }
/// <summary>
/// Gets the serialization order of the component. Components are serialized from lower to higher order values.
/// </summary>
int SerializationOrder { get; }
/// <summary>
/// Gets whether the component state should be save even when it's disabled.
/// This can be useful in components that have state changes even when being disabled. An example is when a
/// disabled component is subscribed to an event and the event triggers changes in the component.
/// </summary>
bool SaveStateWhenDisabled { get; }
/// <summary>
/// Gets whether to save the enabled state of the component and the active state of the object.
/// </summary>
bool SerializeActiveAndEnabledState { get; }
/// <summary>
/// Gets the space the transform will be serialized in.
/// </summary>
UxrTransformSpace TransformStateSaveSpace { get; }
/// <summary>
/// Gets the state save monitor.
/// </summary>
UxrStateSaveMonitor StateSaveMonitor { get; }
#endregion
#region Public Methods
/// <summary>
/// Checks whether the transform should be serialized when serializing the state.
/// </summary>
/// <param name="level">The amount of data to serialize</param>
/// <returns>Whether the transform should be serialized</returns>
bool RequiresTransformSerialization(UxrStateSaveLevel level);
/// <summary>
/// Serializes or deserializes the component state.
/// </summary>
/// <param name="serializer">Serializer to use</param>
/// <param name="stateSerializationVersion">
/// When reading it tells the <see cref="StateSerializationVersion" /> the data was
/// serialized with. When writing it uses the latest <see cref="StateSerializationVersion" /> version.
/// </param>
/// <param name="level">
/// The amount of data to serialize.
/// </param>
/// <param name="options">Options</param>
/// <returns>Whether there were any values in the state that changed</returns>
bool SerializeState(IUxrSerializer serializer, int stateSerializationVersion, UxrStateSaveLevel level, UxrStateSaveOptions options = UxrStateSaveOptions.None);
/// <summary>
/// Interpolates state variables.
/// </summary>
/// <param name="vars">Variable names with their old and new values</param>
/// <param name="t">Interpolation value [0.0, 1.0]</param>
void InterpolateState(in UxrStateInterpolationVars vars, float t);
/// <summary>
/// Gets the interpolator for a given variable, allowing to use customize interpolation for different variables.
/// </summary>
/// <param name="varName">Name of the variable to get the interpolator for</param>
/// <returns>Interpolator or null to not interpolate</returns>
UxrVarInterpolator GetInterpolator(string varName);
#endregion
}
}

View File

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

View File

@@ -0,0 +1,38 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrStateInterpolationVars.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
namespace UltimateXR.Core.StateSave
{
/// <summary>
/// Stores variables that can be interpolated during a frame.
/// </summary>
public struct UxrStateInterpolationVars
{
#region Public Types & Data
/// <summary>
/// Gets the variables as a dictionary where the keys are the variable names and the values are tuples (oldValue,
/// newValue).
/// </summary>
public IDictionary<string, (object OldValue, object NewValue)> Values { get; internal set; }
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="values">Values</param>
public UxrStateInterpolationVars(IDictionary<string, (object OldValue, object NewValue)> values)
{
Values = values;
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,108 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrStateSaveEventArgs.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UltimateXR.Core.Serialization;
namespace UltimateXR.Core.StateSave
{
/// <summary>
/// Event args for <see cref="UxrStateSaveMonitor" />.
/// </summary>
public class UxrStateSaveEventArgs : EventArgs
{
#region Public Types & Data
/// <summary>
/// Gets the serializer. Useful to know whether it is reading or writing using <see cref="IUxrSerializer.IsReading" />.
/// </summary>
public IUxrSerializer Serializer { get; private set; }
/// <summary>
/// Gets the serialization level.
/// </summary>
public UxrStateSaveLevel Level { get; private set; }
/// <summary>
/// Gets the serialization options.
/// </summary>
public UxrStateSaveOptions Options { get; private set; }
/// <summary>
/// Gets the serialized var name. Only for <see cref="UxrStateSaveMonitor.VarSerializing" /> and
/// <see cref="UxrStateSaveMonitor.VarSerialized" />, or global events
/// <see cref="UxrStateSaveImplementer.VarSerializing" /> and <see cref="UxrStateSaveImplementer.VarSerialized" />.
/// </summary>
public string VarName { get; private set; }
/// <summary>
/// Gets the value. For <see cref="UxrStateSaveMonitor.VarSerializing" /> and
/// <see cref="UxrStateSaveMonitor.VarSerialized" /> it will contain the value before serialization. For
/// <see cref="UxrStateSaveImplementer.VarSerializing" /> and <see cref="UxrStateSaveImplementer.VarSerialized" /> it
/// will contain the value after serialization.
/// </summary>
public object Value { get; private set; }
/// <summary>
/// For <see cref="UxrStateSaveMonitor.VarSerializing" /> and <see cref="UxrStateSaveMonitor.VarSerialized" /> it will
/// contain the same value as <see cref="Value" />.
/// For <see cref="UxrStateSaveImplementer.VarSerializing" /> and <see cref="UxrStateSaveImplementer.VarSerialized" />
/// it will contain the value before serialization.
/// In this case, <see cref="OldValue" /> will contain the value before serialization and <see cref="Value" /> will
/// contain the value after serialization.
/// </summary>
public object OldValue { get; private set; }
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="serializer">The serializer</param>
/// <param name="level">The serialization level</param>
/// <param name="options">The serialization options</param>
/// <param name="name">The variable name or null when not serializing any var</param>
/// <param name="value">The variable value or null when not serializing any var</param>
/// <param name="oldValue">The value of the variable before assigning the new one. Only when reading.</param>
public UxrStateSaveEventArgs(IUxrSerializer serializer, UxrStateSaveLevel level, UxrStateSaveOptions options, string name, object value, object oldValue = null)
{
Set(serializer, level, options, name, value, oldValue);
}
/// <summary>
/// Default Constructor.
/// </summary>
internal UxrStateSaveEventArgs()
{
}
#endregion
#region Public Methods
/// <summary>
/// Sets the current values.
/// </summary>
/// <param name="serializer">The serializer</param>
/// <param name="level">The serialization level</param>
/// <param name="options">The serialization options</param>
/// <param name="name">The variable name or null when not serializing any var</param>
/// <param name="value">The variable value or null when not serializing any var</param>
/// <param name="oldValue">Only when deserializing, tells the old value before assigning the new one</param>
public void Set(IUxrSerializer serializer, UxrStateSaveLevel level, UxrStateSaveOptions options, string name, object value, object oldValue = null)
{
Serializer = serializer;
Level = level;
Options = options;
VarName = name;
Value = value;
OldValue = oldValue;
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8cf8c16cf48d45198ac7dce795dde9d9
timeCreated: 1708596129

View File

@@ -0,0 +1,344 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrStateSaveImplementer.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using UltimateXR.Core.Components.Singleton;
using UltimateXR.Core.Instantiation;
using UltimateXR.Core.Serialization;
namespace UltimateXR.Core.StateSave
{
/// <summary>
/// Base class for <see cref="UxrStateSaveImplementer{T}" />.
/// It provides access to global state serialization events and components.
/// </summary>
public abstract class UxrStateSaveImplementer
{
#region Public Types & Data
/// <summary>
/// The name assigned to the Transform when an object's Transform is being serialized.
/// </summary>
public const string SelfTransformVarName = "this.transform";
/// <summary>
/// Event called when the state is about to be serialized using
/// <see cref="UxrStateSaveImplementer{T}.SerializeState" />. The sender is the <see cref="IUxrStateSave" /> that is
/// about to be serialized.
/// </summary>
public static event EventHandler<UxrStateSaveEventArgs> StateSerializing;
/// <summary>
/// Event called when the state finished serializing using <see cref="UxrStateSaveImplementer{T}.SerializeState" />.
/// The sender is the <see cref="IUxrStateSave" /> that was serialized.
/// </summary>
public static event EventHandler<UxrStateSaveEventArgs> StateSerialized;
/// <summary>
/// Event called when a state variable is about to be serialized inside
/// <see cref="UxrStateSaveImplementer{T}.SerializeState" />. The sender is the <see cref="IUxrStateSave" /> that is
/// about to be serialized.
/// </summary>
public static event EventHandler<UxrStateSaveEventArgs> VarSerializing;
/// <summary>
/// Event called when a state variable finished serializing inside
/// <see cref="UxrStateSaveImplementer{T}.SerializeState" />. The sender is the <see cref="IUxrStateSave" /> that was
/// serialized.
/// </summary>
public static event EventHandler<UxrStateSaveEventArgs> VarSerialized;
/// <summary>
/// Gets all the components with an <see cref="IUxrStateSave" /> interface that save any data.
/// The order will ensure that the first component will be the <see cref="UxrInstanceManager" /> if it exists, then all
/// the components with a <see cref="IUxrSingleton" /> interface and then the rest.<br />
/// To get the components sorted by <see cref="IUxrStateSave.SerializationOrder" /> use
/// <c>AllSerializableComponents.OrderBy(s => s.SerializationOrder);</c>
/// </summary>
public static IEnumerable<IUxrStateSave> AllSerializableComponents
{
get
{
if (UxrInstanceManager.HasInstance)
{
yield return UxrInstanceManager.Instance;
}
foreach (IUxrStateSave stateSave in s_allSingletons)
{
yield return stateSave;
}
foreach (IUxrStateSave stateSave in s_allComponents)
{
yield return stateSave;
}
}
}
/// <summary>
/// Gets all the enabled components with an <see cref="IUxrStateSave" /> interface that save any data.
/// The order will ensure that the first component will be the <see cref="UxrInstanceManager" /> if it exists, then all
/// the components with a <see cref="IUxrSingleton" /> interface and then the rest.<br />
/// To get the components sorted by <see cref="IUxrStateSave.SerializationOrder" /> use
/// <c>EnabledSerializableComponents.OrderBy(s => s.SerializationOrder);</c>
/// </summary>
public static IEnumerable<IUxrStateSave> EnabledSerializableComponents
{
get
{
if (UxrInstanceManager.HasInstance && UxrInstanceManager.Instance.isActiveAndEnabled)
{
yield return UxrInstanceManager.Instance;
}
foreach (IUxrStateSave stateSave in s_enabledSingletons)
{
yield return stateSave;
}
foreach (IUxrStateSave stateSave in s_enabledComponents)
{
yield return stateSave;
}
}
}
/// <summary>
/// Gets all the components that should be saved when saving the current state of the scene, such as
/// when using <see cref="UxrManager.SaveStateChanges" />.
/// This includes all the enabled components and the components with <see cref="IUxrStateSave.SaveStateWhenDisabled" />
/// set.<br />
/// To get the components sorted by <see cref="IUxrStateSave.SerializationOrder" /> use
/// <c>SaveRequiredComponents.OrderBy(s => s.SerializationOrder);</c>
/// </summary>
public static IEnumerable<IUxrStateSave> SaveRequiredComponents
{
get
{
if (UxrInstanceManager.HasInstance)
{
yield return UxrInstanceManager.Instance;
}
foreach (IUxrStateSave stateSave in s_saveRequiredSingletons)
{
yield return stateSave;
}
foreach (IUxrStateSave stateSave in s_saveRequiredComponents)
{
yield return stateSave;
}
}
}
#endregion
#region Internal Methods
/// <summary>
/// Notifies the end of frame in the UltimateXR update process.
/// </summary>
internal static void NotifyEndOfFrame()
{
foreach (UxrStateSaveImplementer implementer in s_pendingStoreInitialStates)
{
implementer.StoreInitialState();
}
s_pendingStoreInitialStates.Clear();
}
#endregion
#region Event Trigger Methods
/// <summary>
/// Event trigger for <see cref="StateSerializing" />.
/// </summary>
/// <param name="stateSave">The <see cref="IUxrStateSave" /> that is about to be serialized</param>
/// <param name="e">Event parameters</param>
protected virtual void OnStateSerializing(IUxrStateSave stateSave, UxrStateSaveEventArgs e)
{
StateSerializing?.Invoke(stateSave, e);
}
/// <summary>
/// Event trigger for <see cref="StateSerialized" />.
/// </summary>
/// <param name="stateSave">The <see cref="IUxrStateSave" /> that was serialized</param>
/// <param name="e">Event parameters</param>
protected virtual void OnStateSerialized(IUxrStateSave stateSave, UxrStateSaveEventArgs e)
{
StateSerialized?.Invoke(stateSave, e);
}
/// <summary>
/// Event trigger for <see cref="VarSerializing" />.
/// </summary>
/// <param name="stateSave">The <see cref="IUxrStateSave" /> that is about to be serialized</param>
/// <param name="e">Event parameters</param>
protected virtual void OnVarSerializing(IUxrStateSave stateSave, UxrStateSaveEventArgs e)
{
VarSerializing?.Invoke(stateSave, e);
}
/// <summary>
/// Event trigger for <see cref="VarSerialized" />.
/// </summary>
/// <param name="stateSave">The <see cref="IUxrStateSave" /> that was serialized</param>
/// <param name="e">Event parameters</param>
protected virtual void OnVarSerialized(IUxrStateSave stateSave, UxrStateSaveEventArgs e)
{
VarSerialized?.Invoke(stateSave, e);
}
#endregion
#region Protected Methods
/// <summary>
/// Stores the initial state of a component.
/// </summary>
protected abstract void StoreInitialState();
/// <summary>
/// Registers the component.
/// </summary>
protected void RegisterComponent(IUxrStateSave stateSave)
{
if (stateSave is UxrInstanceManager)
{
return;
}
// Do a serialization test and check if there is any state saving. If not we can ignore it because this component doesn't save any data.
if (!stateSave.SerializeState(UxrDummySerializer.WriteModeSerializer, stateSave.StateSerializationVersion, UxrStateSaveLevel.Complete, UxrStateSaveOptions.DontSerialize | UxrStateSaveOptions.DontCacheChanges))
{
return;
}
if (stateSave is IUxrSingleton)
{
s_allSingletons.Add(stateSave);
s_saveRequiredSingletons.Add(stateSave);
}
else
{
s_allComponents.Add(stateSave);
s_saveRequiredComponents.Add(stateSave);
}
s_pendingStoreInitialStates.Add(this);
}
/// <summary>
/// Unregisters the component
/// </summary>
protected void UnregisterComponent(IUxrStateSave stateSave)
{
if (stateSave is UxrInstanceManager)
{
return;
}
if (stateSave is IUxrSingleton)
{
s_allSingletons.Remove(stateSave);
s_saveRequiredSingletons.Remove(stateSave);
}
else
{
s_allComponents.Remove(stateSave);
s_saveRequiredComponents.Remove(stateSave);
}
}
/// <summary>
/// Notifies the component has been enabled.
/// </summary>
protected void NotifyOnEnable(IUxrStateSave stateSave)
{
if (stateSave is UxrInstanceManager)
{
return;
}
// We only register components that have been filtered by RegisterComponent, we want to discard components that don't save data.
if (stateSave is IUxrSingleton)
{
if (s_allSingletons.Contains(stateSave))
{
s_enabledSingletons.Add(stateSave);
}
if (!stateSave.SaveStateWhenDisabled)
{
s_saveRequiredSingletons.Add(stateSave);
}
}
else
{
if (s_allComponents.Contains(stateSave))
{
s_enabledComponents.Add(stateSave);
}
if (!stateSave.SaveStateWhenDisabled)
{
s_saveRequiredComponents.Add(stateSave);
}
}
}
/// <summary>
/// Notifies the component has been disabled.
/// </summary>
protected void NotifyOnDisable(IUxrStateSave stateSave)
{
if (stateSave is UxrInstanceManager)
{
return;
}
if (stateSave is IUxrSingleton)
{
s_enabledSingletons.Remove(stateSave);
if (!stateSave.SaveStateWhenDisabled)
{
s_saveRequiredSingletons.Remove(stateSave);
}
}
else
{
s_enabledComponents.Remove(stateSave);
if (!stateSave.SaveStateWhenDisabled)
{
s_saveRequiredComponents.Remove(stateSave);
}
}
}
#endregion
#region Private Types & Data
private static readonly HashSet<IUxrStateSave> s_allComponents = new HashSet<IUxrStateSave>();
private static readonly HashSet<IUxrStateSave> s_enabledComponents = new HashSet<IUxrStateSave>();
private static readonly HashSet<IUxrStateSave> s_saveRequiredComponents = new HashSet<IUxrStateSave>();
private static readonly HashSet<IUxrStateSave> s_allSingletons = new HashSet<IUxrStateSave>();
private static readonly HashSet<IUxrStateSave> s_enabledSingletons = new HashSet<IUxrStateSave>();
private static readonly HashSet<IUxrStateSave> s_saveRequiredSingletons = new HashSet<IUxrStateSave>();
private static readonly HashSet<UxrStateSaveImplementer> s_pendingStoreInitialStates = new HashSet<UxrStateSaveImplementer>();
#endregion
}
}

View File

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

View File

@@ -0,0 +1,883 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrStateSaveImplementer_1.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using UltimateXR.Animation.Interpolation;
using UltimateXR.Avatar;
using UltimateXR.Core.Components;
using UltimateXR.Core.Serialization;
using UltimateXR.Core.Unique;
using UnityEngine;
using ObjectExt = UltimateXR.Extensions.System.ObjectExt;
namespace UltimateXR.Core.StateSave
{
/// <summary>
/// Helper class simplifying the implementation of the <see cref="IUxrStateSave" /> interface.
/// This class includes functionality to serialize the state of components.
/// It is utilized by <see cref="UxrComponent" /> to implement <see cref="IUxrStateSave" />.
/// In scenarios where custom classes cannot inherit from <see cref="UxrComponent" /> for state saving capabilities,
/// this class is designed to implement the interface.
/// </summary>
public class UxrStateSaveImplementer<T> : UxrStateSaveImplementer where T : Component, IUxrStateSave
{
#region Public Types & Data
/// <summary>
/// State serialization handler.
/// </summary>
public delegate void SerializeStateHandler(bool isReading, int stateSerializationVersion, UxrStateSaveLevel level, UxrStateSaveOptions options);
/// <summary>
/// State interpolation handler.
/// </summary>
public delegate void InterpolateStateHandler(in UxrStateInterpolationVars vars, float t);
/// <summary>
/// Gets the state save monitor.
/// </summary>
public UxrStateSaveMonitor Monitor { get; }
/// <summary>
/// Gets incremented each time a value is serialized or deserialized using <see cref="SerializeStateValue{TV}" />.
/// This can be used to check whether a value was actually serialized. When it doesn't get incremented, it means that
/// the value didn't change (when writing) or doesn't need to change (when reading).
/// </summary>
/// <remarks>
/// When a value doesn't need serialization because it didn't change, a boolean is serialized to tell that no change
/// was made. If all values in a component didn't change, the component can be ignored to avoid writing many false
/// booleans and save space. This still requires a first
/// pass using <see cref="UxrStateSaveOptions.DontCacheChanges" /> and <see cref="UxrStateSaveOptions.DontSerialize" />
/// .
/// </remarks>
public int SerializeCounter { 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 UxrStateSaveImplementer(T targetComponent)
{
_targetComponent = targetComponent;
Monitor = new UxrStateSaveMonitor(targetComponent);
}
#endregion
#region Public Methods
/// <summary>
/// Registers the component if necessary.
/// </summary>
public void RegisterIfNecessary()
{
if (!Application.isPlaying)
{
return;
}
if (!_registered)
{
RegisterComponent(_targetComponent);
_registered = true;
}
}
/// <summary>
/// Unregisters the component.
/// </summary>
public void Unregister()
{
base.UnregisterComponent(_targetComponent);
}
/// <summary>
/// Notifies the component has been enabled.
/// </summary>
public void NotifyOnEnable()
{
base.NotifyOnEnable(_targetComponent);
}
/// <summary>
/// Notifies the component has been disabled.
/// </summary>
public void NotifyOnDisable()
{
base.NotifyOnDisable(_targetComponent);
}
/// <summary>
/// Serializes the state of a component, handling serialization of different elements if necessary:
/// <list type="bullet">
/// <item>
/// The enabled state of the component and active state of the GameObject, if required by
/// <see cref="IUxrStateSave.SerializeActiveAndEnabledState" />.
/// </item>
/// <item>
/// The transform, using <see cref="IUxrStateSave.RequiresTransformSerialization" /> and
/// <see cref="IUxrStateSave.TransformStateSaveSpace" />.
/// </item>
/// </list>
/// After that, it hands over the serialization to <paramref name="customSerializeStateHandler" /> to handle the custom
/// component serialization.
/// </summary>
/// <param name="serializer">The serializer</param>
/// <param name="level">The amount of data to serialize</param>
/// <param name="options">Options</param>
/// <param name="customSerializeStateHandler">The handler that will serialize the remaining custom data</param>
public void SerializeState(IUxrSerializer serializer, UxrStateSaveLevel level, UxrStateSaveOptions options, SerializeStateHandler customSerializeStateHandler)
{
if (_targetComponent == null)
{
return;
}
OnStateSerializing(_targetComponent, GetStateSaveEventArgs(serializer, level, options));
// Enabled/Active states
if (_targetComponent.SerializeActiveAndEnabledState)
{
Behaviour behaviour = _targetComponent.Component;
bool enabled = behaviour.enabled;
bool active = _targetComponent.GameObject.activeSelf;
bool enabledBefore = enabled;
bool activeBefore = active;
SerializeStateValue(serializer, level, options, NameIsEnabled, ref enabled);
SerializeStateValue(serializer, level, options, NameIsActive, ref active);
if (serializer.IsReading)
{
if (enabled != enabledBefore && behaviour != null)
{
behaviour.enabled = enabled;
}
if (active != activeBefore)
{
_targetComponent.gameObject.SetActive(active);
}
}
}
// Transform
if (_targetComponent.RequiresTransformSerialization(level))
{
SerializeStateTransform(serializer, level, options, SelfTransformVarName, _targetComponent.TransformStateSaveSpace, _targetComponent.transform);
}
// User custom serialization
customSerializeStateHandler?.Invoke(serializer.IsReading, _targetComponent.StateSerializationVersion, level, options);
OnStateSerialized(_targetComponent, GetStateSaveEventArgs(serializer, level, options));
}
/// <summary>
/// Serializes a value only when necessary, depending on <paramref name="level" />, <paramref name="options" /> and if
/// the value changed.<br />
/// </summary>
/// <param name="serializer">Serializer</param>
/// <param name="level">The amount of data to serialize</param>
/// <param name="options">Options</param>
/// <param name="varName">
/// The parameter name. It will be used to track value changes over time. If it is null or empty,
/// it will be serialized without checking for value changes. The name must be unique to any other transform or value
/// serialized for the target component using <see cref="SerializeStateValue{TV}" /> or
/// <see cref="SerializeStateTransform" />.
/// </param>
/// <param name="value">A reference to the value being loaded/saved</param>
[SuppressMessage("ReSharper", "InvokeAsExtensionMethod")]
public void SerializeStateValue<TV>(IUxrSerializer serializer, UxrStateSaveLevel level, UxrStateSaveOptions options, string varName, ref TV value)
{
// Initialize dictionaries if necessary. We store a deep copy to be able to check for changes later by comparing values.
if (!string.IsNullOrEmpty(varName))
{
if (!_initialValues.ContainsKey(varName))
{
_initialValues.Add(varName, ObjectExt.DeepCopy(value));
}
else if (options.HasFlag(UxrStateSaveOptions.ResetChangesCache))
{
_initialValues[varName] = ObjectExt.DeepCopy(value);
}
if (!_lastValues.ContainsKey(varName))
{
_lastValues.Add(varName, ObjectExt.DeepCopy(value));
}
else if (options.HasFlag(UxrStateSaveOptions.ResetChangesCache))
{
_lastValues[varName] = ObjectExt.DeepCopy(value);
}
}
// Here we need to write read/write parts separately to implement comparison logic
bool serialize = false;
if (!serializer.IsReading)
{
// When writing, check if the data changed to export changes only.
// We use a floating point precision threshold for specific types to help avoiding redundant writes.
if (options.HasFlag(UxrStateSaveOptions.DontCheckCache))
{
serialize = true;
}
else
{
switch (level)
{
case UxrStateSaveLevel.None: return;
case UxrStateSaveLevel.ChangesSinceBeginning:
serialize = varName == null || !ObjectExt.ValuesEqual(value, _initialValues[varName], UxrConstants.Math.DefaultPrecisionThreshold);
break;
case UxrStateSaveLevel.ChangesSincePreviousSave:
serialize = varName == null || !ObjectExt.ValuesEqual(value, _lastValues[varName], UxrConstants.Math.DefaultPrecisionThreshold);
break;
case UxrStateSaveLevel.Complete:
serialize = true;
break;
}
}
}
if (!options.HasFlag(UxrStateSaveOptions.DontSerialize))
{
// When reading, will deserialize a boolean telling whether there is data to be deserialized. If a false value is deserialized as a result, it means that the previous value didn't change and no new data needs to be read.
// When writing, will serialize a boolean telling whether any value will be written. If a false value is serialized, it means that the previous value didn't change and no data needs to be written.
serializer.Serialize(ref serialize);
}
if (serialize)
{
object oldValue = !string.IsNullOrEmpty(varName) ? ObjectExt.DeepCopy(value) : null;
OnVarSerializing(_targetComponent, GetStateSaveEventArgs(serializer, level, options, varName, value, oldValue));
if (!options.HasFlag(UxrStateSaveOptions.DontSerialize))
{
serializer.SerializeAnyVar(ref value);
}
if (!options.HasFlag(UxrStateSaveOptions.DontCacheChanges) && !string.IsNullOrEmpty(varName))
{
_lastValues[varName] = ObjectExt.DeepCopy(value);
}
SerializeCounter++;
OnVarSerialized(_targetComponent, GetStateSaveEventArgs(serializer, level, options, varName, value, oldValue));
}
}
/// <summary>
/// Serializes transform data.
/// The Transform can be for the target component or any other component tracked by it, normally children in the
/// hierarchy. For example, an avatar serializes the position of the head and hands.
/// </summary>
/// <param name="serializer">Serializer</param>
/// <param name="level">The amount of data to serialize</param>
/// <param name="options">Options</param>
/// <param name="transformVarName">
/// A name to identify the transform. It will be used to track value changes over time. If it is null or empty,
/// it will be serialized without checking for value changes. The name must be unique to any other transform or
/// value serialized for the target component using <see cref="SerializeStateValue{TV}" /> or
/// <see cref="SerializeStateTransform" />.
/// </param>
/// <param name="space">
/// The space the transform data is specified in, when writing. Scale will always be stored in local
/// space.
/// </param>
/// <param name="transform">
/// The transform to serialize. It can be the target component's Transform or any other transform serialized by the
/// component.
/// </param>
public void SerializeStateTransform(IUxrSerializer serializer, UxrStateSaveLevel level, UxrStateSaveOptions options, string transformVarName, UxrTransformSpace space, Transform transform)
{
// Can't use ref with Transform property directly, so we need to implement read/write paths separately
if (serializer.IsReading)
{
// Read parent
IUxrUniqueId newUniqueParent = transform.parent != null ? transform.parent.GetComponent<IUxrUniqueId>() : null;
IUxrUniqueId currentUniqueParent = newUniqueParent;
SerializeStateValue(serializer, level, options, GetTransformVarName(transformVarName, NameTransformParent), ref newUniqueParent);
if (currentUniqueParent != newUniqueParent)
{
Debug.Log($"{transform.name} parent changed from {currentUniqueParent} to {newUniqueParent}");
if (newUniqueParent != null)
{
Component newParentComponent = newUniqueParent as Component;
Transform newParentTransform = newParentComponent != null ? newParentComponent.transform : null;
if (newParentTransform != null)
{
if (transform.parent != null)
{
// If there is a current parent, only switch parent if the new parent has also a UniqueID
Component currentParentComponent = currentUniqueParent as Component;
if (currentParentComponent != null && newParentComponent != null && transform.parent != newParentComponent.transform)
{
transform.SetParent(newParentTransform);
}
}
else
{
// If there is no current parent, switch parent to the new UniqueID
transform.SetParent(newParentTransform);
}
}
}
else
{
if (currentUniqueParent != null)
{
// If the new parent is null, switch only if the current parent has a UniqueID
transform.SetParent(null);
}
}
}
// Read space
object boxedSpace = space;
SerializeStateValue(serializer, level, options, GetTransformVarName(transformVarName, NameTransformSpace), ref boxedSpace);
space = (UxrTransformSpace)boxedSpace;
object boxedPos = GetPosition(transform, space);
object boxedRot = GetRotation(transform, space);
object boxedScale = transform.localScale;
// Read position
int counterBefore = SerializeCounter;
SerializeStateValue(serializer, level, options, GetTransformVarName(transformVarName, NameTransformPos), ref boxedPos);
if (counterBefore != SerializeCounter && !options.HasFlag(UxrStateSaveOptions.DontSerialize))
{
SetPosition(transform, space, (Vector3)boxedPos);
}
// Read rotation
counterBefore = SerializeCounter;
SerializeStateValue(serializer, level, options, GetTransformVarName(transformVarName, NameTransformRot), ref boxedRot);
if (counterBefore != SerializeCounter && !options.HasFlag(UxrStateSaveOptions.DontSerialize))
{
SetRotation(transform, space, (Quaternion)boxedRot);
}
// Read scale
counterBefore = SerializeCounter;
SerializeStateValue(serializer, level, options, GetTransformVarName(transformVarName, NameTransformScale), ref boxedScale);
if (counterBefore != SerializeCounter && !options.HasFlag(UxrStateSaveOptions.DontSerialize))
{
transform.localScale = (Vector3)boxedScale;
}
}
else
{
// Write parent
int counterBeforeParent = SerializeCounter;
bool parentSerialized = false;
IUxrUniqueId uniqueParent = transform.parent != null ? transform.parent.GetComponent<IUxrUniqueId>() : null;
SerializeStateValue(serializer, level, options, GetTransformVarName(transformVarName, NameTransformParent), ref uniqueParent);
if (counterBeforeParent != SerializeCounter)
{
parentSerialized = true;
}
// Write space
object boxedSpace = space;
SerializeStateValue(serializer, level, options, GetTransformVarName(transformVarName, NameTransformSpace), ref boxedSpace);
// Compute values
Vector3 position = GetPosition(transform, space);
Quaternion rotation = GetRotation(transform, space);
// Write values
object boxedPos = position;
object boxedRot = rotation;
object boxedScale = transform.localScale;
if (parentSerialized)
{
// If the parent was serialized, it means that it changed or was forced to be serialized.
// In this case we need to make sure that the transform components are serialized too.
options |= UxrStateSaveOptions.DontCheckCache;
}
SerializeStateValue(serializer, level, options, GetTransformVarName(transformVarName, NameTransformPos), ref boxedPos);
SerializeStateValue(serializer, level, options, GetTransformVarName(transformVarName, NameTransformRot), ref boxedRot);
SerializeStateValue(serializer, level, options, GetTransformVarName(transformVarName, NameTransformScale), ref boxedScale);
}
}
/// <summary>
/// Interpolates state variables.
/// </summary>
/// <param name="vars">Contains the variables that can be interpolated for the target component</param>
/// <param name="t">Interpolation value [0.0, 1.0]</param>
/// <param name="customInterpolateStateHandler">The user-defined interpolate state handler for the component</param>
/// <param name="getInterpolator">A function that gets the interpolator for a given serialized var</param>
public void InterpolateState(in UxrStateInterpolationVars vars, float t, InterpolateStateHandler customInterpolateStateHandler, Func<string, UxrVarInterpolator> getInterpolator)
{
if (_targetComponent == null)
{
return;
}
InterpolateStateTransform(vars, t, SelfTransformVarName, _targetComponent.transform, _targetComponent.TransformStateSaveSpace, getInterpolator);
customInterpolateStateHandler?.Invoke(vars, t);
}
/// <summary>
/// Interpolates a transform.
/// </summary>
/// <param name="vars">Contains the variables that can be interpolated for the target component</param>
/// <param name="t">Interpolation value [0.0, 1.0]</param>
/// <param name="transformVarName">
/// The name assigned to the transform when serializing it using
/// <see cref="SerializeStateTransform" />
/// </param>
/// <param name="targetTransform">The target transform</param>
/// <param name="space">
/// The space in which the transform data was serialized using <see cref="SerializeStateTransform" />
/// </param>
/// <param name="getInterpolator">A function that gets the interpolator for a given serialized var</param>
public void InterpolateStateTransform(in UxrStateInterpolationVars vars, float t, string transformVarName, Transform targetTransform, UxrTransformSpace space, Func<string, UxrVarInterpolator> getInterpolator)
{
if (getInterpolator == null)
{
return;
}
string posVarName = GetTransformVarName(transformVarName, NameTransformPos);
string rotVarName = GetTransformVarName(transformVarName, NameTransformRot);
string scaleVarName = GetTransformVarName(transformVarName, NameTransformScale);
if (vars.Values.ContainsKey(posVarName))
{
if (getInterpolator(posVarName) is UxrVector3Interpolator positionInterpolator)
{
SetPosition(targetTransform, space, positionInterpolator.Interpolate((Vector3)vars.Values[posVarName].OldValue, (Vector3)vars.Values[posVarName].NewValue, t));
}
}
if (vars.Values.ContainsKey(rotVarName))
{
if (getInterpolator(rotVarName) is UxrQuaternionInterpolator rotationInterpolator)
{
SetRotation(targetTransform, space, rotationInterpolator.Interpolate((Quaternion)vars.Values[rotVarName].OldValue, (Quaternion)vars.Values[rotVarName].NewValue, t));
}
}
if (vars.Values.ContainsKey(scaleVarName))
{
if (getInterpolator(scaleVarName) is UxrVector3Interpolator scaleInterpolator)
{
targetTransform.localScale = scaleInterpolator.Interpolate((Vector3)vars.Values[scaleVarName].OldValue, (Vector3)vars.Values[scaleVarName].NewValue, t);
}
}
}
/// <summary>
/// Gets the default interpolator for the given variable.
/// </summary>
/// <param name="varName">The variable name</param>
/// <returns>Interpolator or null to not interpolate</returns>
public UxrVarInterpolator GetDefaultInterpolator(string varName)
{
if (!_initialValues.ContainsKey(varName) || _initialValues[varName] == null)
{
return null;
}
Type type = _initialValues[varName].GetType();
if (type == typeof(Vector3))
{
return UxrVector3Interpolator.DefaultInterpolator;
}
if (type == typeof(Quaternion))
{
return UxrQuaternionInterpolator.DefaultInterpolator;
}
if (type == typeof(float))
{
return UxrFloatInterpolator.DefaultInterpolator;
}
if (type == typeof(int))
{
return UxrIntInterpolator.DefaultInterpolator;
}
if (type == typeof(Vector2))
{
return UxrVector2Interpolator.DefaultInterpolator;
}
if (type == typeof(Vector4))
{
return UxrVector4Interpolator.DefaultInterpolator;
}
if (type == typeof(Color))
{
return UxrColorInterpolator.DefaultInterpolator;
}
if (type == typeof(Color32))
{
return UxrColor32Interpolator.DefaultInterpolator;
}
return null;
}
/// <summary>
/// Checks whether a serialized var name is the name given to the position component of a given transform serialized
/// using <see cref="SerializeStateTransform" />.
/// </summary>
/// <param name="varName">The variable name to check</param>
/// <param name="transformVarName">The name given to the transform using <see cref="SerializeStateTransform" /></param>
/// <returns>Whether <paramref name="varName" /> is the name assigned to the position component of the given transform</returns>
public bool IsTransformPositionVarName(string varName, string transformVarName)
{
if (!_transformVarNameCache.TryGetValue(transformVarName, out Dictionary<string, string> innerDict))
{
return false;
}
return innerDict.TryGetValue(NameTransformPos, out string cachedValue) && cachedValue == varName;
}
/// <summary>
/// Checks whether a serialized var name is the name given to the rotation component of a given transform serialized
/// using <see cref="SerializeStateTransform" />.
/// </summary>
/// <param name="varName">The variable name to check</param>
/// <param name="transformVarName">The name given to the transform using <see cref="SerializeStateTransform" /></param>
/// <returns>Whether <paramref name="varName" /> is the name assigned to the rotation component of the given transform</returns>
public bool IsTransformRotationVarName(string varName, string transformVarName)
{
if (!_transformVarNameCache.TryGetValue(transformVarName, out Dictionary<string, string> innerDict))
{
return false;
}
return innerDict.TryGetValue(NameTransformRot, out string cachedValue) && cachedValue == varName;
}
/// <summary>
/// Checks whether a serialized var name is the name given to the scale component of a given transform serialized using
/// <see cref="SerializeStateTransform" />.
/// </summary>
/// <param name="varName">The variable name to check</param>
/// <param name="transformVarName">The name given to the transform using <see cref="SerializeStateTransform" /></param>
/// <returns>Whether <paramref name="varName" /> is the name assigned to the scale component of the given transform</returns>
public bool IsTransformScaleVarName(string varName, string transformVarName)
{
if (!_transformVarNameCache.TryGetValue(transformVarName, out Dictionary<string, string> innerDict))
{
return false;
}
return innerDict.TryGetValue(NameTransformScale, out string cachedValue) && cachedValue == varName;
}
#endregion
#region Event Trigger Methods
/// <inheritdoc />
protected override void OnStateSerializing(IUxrStateSave stateSave, UxrStateSaveEventArgs e)
{
base.OnStateSerializing(stateSave, e);
Monitor.RaiseStateSerializing(e);
}
/// <inheritdoc />
protected override void OnStateSerialized(IUxrStateSave stateSave, UxrStateSaveEventArgs e)
{
base.OnStateSerialized(stateSave, e);
Monitor.RaiseStateSerialized(e);
}
/// <inheritdoc />
protected override void OnVarSerializing(IUxrStateSave stateSave, UxrStateSaveEventArgs e)
{
base.OnVarSerializing(stateSave, e);
Monitor.RaiseVarSerializing(e);
}
/// <inheritdoc />
protected override void OnVarSerialized(IUxrStateSave stateSave, UxrStateSaveEventArgs e)
{
base.OnVarSerialized(stateSave, e);
Monitor.RaiseVarSerialized(e);
}
#endregion
#region Protected Overrides UxrStateSaveImplementer
/// <inheritdoc />
protected override void StoreInitialState()
{
if (_targetComponent == null)
{
return;
}
// Cache the initial state after the first frame. We use a dummy serializer in write mode to initialize the changes cache without saving any data.
_targetComponent.SerializeState(UxrDummySerializer.WriteModeSerializer,
_targetComponent.StateSerializationVersion,
UxrStateSaveLevel.ChangesSinceBeginning,
UxrStateSaveOptions.DontSerialize | UxrStateSaveOptions.ResetChangesCache | UxrStateSaveOptions.FirstFrame);
}
#endregion
#region Private Methods
/// <summary>
/// Fills the internal state event args object with the given parameters.
/// </summary>
/// <param name="serializer">The serializer</param>
/// <param name="level">Serialization level</param>
/// <param name="options">Serialization options</param>
/// <param name="varName">Var name or null when not serializing any var</param>
/// <param name="value">Var value or null when not serializing any var</param>
/// <param name="oldValue">Old var value or null when not reading any var</param>
/// <returns>The state event args</returns>
private UxrStateSaveEventArgs GetStateSaveEventArgs(IUxrSerializer serializer, UxrStateSaveLevel level, UxrStateSaveOptions options, string varName = null, object value = null, object oldValue = null)
{
_stateSaveArgs.Set(serializer, level, options, varName, value, oldValue);
return _stateSaveArgs;
}
/// <summary>
/// Gets a transform's position in a given space.
/// </summary>
/// <param name="transform">Transform to get the position of</param>
/// <param name="space">Coordinates to get the position in</param>
/// <returns>Position in the given coordinates</returns>
private Vector3 GetPosition(Transform transform, UxrTransformSpace space)
{
switch (space)
{
case UxrTransformSpace.World: return transform.position;
case UxrTransformSpace.Local: return transform.localPosition;
case UxrTransformSpace.Avatar:
UxrAvatar avatar = GetAvatar();
if (avatar)
{
return avatar.transform.InverseTransformPoint(transform.position);
}
break;
default: throw new ArgumentOutOfRangeException(nameof(space), space, "Transform space not implemented");
}
return transform.position;
}
/// <summary>
/// Gets a transform's rotation in a given space.
/// </summary>
/// <param name="transform">Transform to get the rotation of</param>
/// <param name="space">Coordinates to get the rotation in</param>
/// <returns>Rotation in the given coordinates</returns>
private Quaternion GetRotation(Transform transform, UxrTransformSpace space)
{
switch (space)
{
case UxrTransformSpace.World: return transform.rotation;
case UxrTransformSpace.Local: return transform.localRotation;
case UxrTransformSpace.Avatar:
UxrAvatar avatar = GetAvatar();
if (avatar)
{
return Quaternion.Inverse(avatar.transform.rotation) * transform.rotation;
}
break;
default: throw new ArgumentOutOfRangeException(nameof(space), space, "Transform space not implemented");
}
return transform.rotation;
}
/// <summary>
/// Sets a transform's position in a given space.
/// </summary>
/// <param name="transform">Transform to set the position of</param>
/// <param name="space">Coordinates to set the position in</param>
/// <param name="position">Position value</param>
private void SetPosition(Transform transform, UxrTransformSpace space, Vector3 position)
{
switch (space)
{
case UxrTransformSpace.World:
transform.position = position;
break;
case UxrTransformSpace.Local:
transform.localPosition = position;
break;
case UxrTransformSpace.Avatar:
UxrAvatar avatar = GetAvatar();
if (avatar)
{
transform.position = avatar.transform.TransformPoint(position);
}
break;
}
}
/// <summary>
/// Sets a transform's rotation in a given space.
/// </summary>
/// <param name="transform">Transform to set the rotation of</param>
/// <param name="space">Coordinates to set the rotation in</param>
/// <param name="rotation">Rotation value</param>
private void SetRotation(Transform transform, UxrTransformSpace space, Quaternion rotation)
{
switch (space)
{
case UxrTransformSpace.World:
transform.rotation = rotation;
break;
case UxrTransformSpace.Local:
transform.localRotation = rotation;
break;
case UxrTransformSpace.Avatar:
UxrAvatar avatar = GetAvatar();
if (avatar)
{
transform.rotation = avatar.transform.rotation * rotation;
}
break;
}
}
/// <summary>
/// Gets the avatar the target component belongs to.
/// </summary>
/// <returns>Avatar component or null if the component doesn't belong to an avatar</returns>
private UxrAvatar GetAvatar()
{
if (_avatar != null)
{
return _avatar;
}
_avatar = _targetComponent.GetComponentInParent<UxrAvatar>();
return _avatar;
}
/// <summary>
/// Gets the name used to identify a serialized transform variable if it's cached, or caches it if it's not yet stored.
/// This is used to avoid many string manipulation calls.
/// </summary>
/// <param name="transformVarName">The name assigned to the Transform</param>
/// <param name="subName">The name to identify which part of the Transform is serialized (pos, rot, scale, parent or space)</param>
/// <returns>The name</returns>
private string GetTransformVarName(string transformVarName, string subName)
{
if (_transformVarNameCache.TryGetValue(transformVarName, out Dictionary<string, string> innerDict))
{
if (innerDict.TryGetValue(subName, out string cachedValue))
{
return cachedValue;
}
}
else
{
innerDict = new Dictionary<string, string>();
_transformVarNameCache[transformVarName] = innerDict;
}
// If not in cache, compute the value and cache it
string combinedValue = string.Concat(transformVarName, subName);
innerDict[subName] = combinedValue;
return combinedValue;
}
#endregion
#region Private Types & Data
private const string NameIsEnabled = "__enabled";
private const string NameIsActive = "__active";
private const string NameTransformParent = ".tf.parent";
private const string NameTransformSpace = ".tf.space";
private const string NameTransformPos = ".tf.pos";
private const string NameTransformRot = ".tf.rot";
private const string NameTransformScale = ".tf.scl";
private readonly T _targetComponent;
private readonly Dictionary<string, object> _initialValues = new Dictionary<string, object>();
private readonly Dictionary<string, object> _lastValues = new Dictionary<string, object>();
private readonly UxrStateSaveEventArgs _stateSaveArgs = new UxrStateSaveEventArgs();
private readonly Dictionary<string, Dictionary<string, string>> _transformVarNameCache = new Dictionary<string, Dictionary<string, string>>();
private bool _registered;
private UxrAvatar _avatar;
#endregion
}
}

View File

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

View File

@@ -0,0 +1,42 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrStateSaveLevel.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Core.StateSave
{
/// <summary>
/// Enumerates the different levels of state saving.
/// </summary>
public enum UxrStateSaveLevel
{
/// <summary>
/// Don't save anything.
/// </summary>
None = 0,
/// <summary>
/// Save a snapshot with only the changes since the previous save using <see cref="ChangesSincePreviousSave" />,
/// <see cref="ChangesSinceBeginning" /> or <see cref="Complete" />.
/// This can be used in replay systems to save incremental changes only.
/// </summary>
ChangesSincePreviousSave,
/// <summary>
/// Save a snapshot with all changes since the end of the first frame when the object is first enabled. The end of the
/// first frame is used because it represents the initial state of the scene when it's loaded, including logic that
/// might be executed during Awake() or Start().
/// This can be used to save space/bandwidth by avoiding sending redundant information.
/// It can be utilized in a networking environment to send an up-to-date scene state to new users upon joining.
/// Additionally, it can be employed in replay systems to save a keyframe.
/// </summary>
ChangesSinceBeginning,
/// <summary>
/// Save complete data. It will serialize the state of all objects, regardless of whether any values changed since
/// the beginning. This can be used to ensure that the state of all objects is restored correctly, even if changes are
/// made in later versions where objects are moved to a different initial location or have a different initial state.
/// </summary>
Complete
}
}

View File

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

View File

@@ -0,0 +1,101 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrStateSaveMonitor.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
namespace UltimateXR.Core.StateSave
{
/// <summary>
/// Monitor that helps intercepting relevant state serialization events.
/// </summary>
public class UxrStateSaveMonitor
{
#region Public Types & Data
/// <summary>
/// Event called right before the state of an object with the <see cref="IUxrStateSave" /> interface is about to be
/// serialized using <see cref="IUxrStateSave.SerializeState" />.
/// </summary>
public static event EventHandler<UxrStateSaveEventArgs> StateSerializing;
/// <summary>
/// Event called right after the state of an object with the <see cref="IUxrStateSave" /> interface was serialized
/// using <see cref="IUxrStateSave.SerializeState" />.
/// </summary>
public static event EventHandler<UxrStateSaveEventArgs> StateSerialized;
/// <summary>
/// Event called right before a state variable is about to be serialized in a
/// <see cref="IUxrStateSave.SerializeState" /> call.
/// </summary>
public static event EventHandler<UxrStateSaveEventArgs> VarSerializing;
/// <summary>
/// Event called right after a state variable was serialized in a <see cref="IUxrStateSave.SerializeState" /> call.
/// </summary>
public static event EventHandler<UxrStateSaveEventArgs> VarSerialized;
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="target">The <see cref="IUxrStateSave" /> that is being monitored</param>
public UxrStateSaveMonitor(IUxrStateSave target)
{
_target = target;
}
#endregion
#region Event Trigger Methods
/// <summary>
/// Raises the <see cref="StateSerializing" /> event.
/// </summary>
/// <param name="e">Event parameters</param>
internal void RaiseStateSerializing(UxrStateSaveEventArgs e)
{
StateSerializing?.Invoke(_target, e);
}
/// <summary>
/// Raises the <see cref="StateSerialized" /> event.
/// </summary>
/// <param name="e">Event parameters</param>
internal void RaiseStateSerialized(UxrStateSaveEventArgs e)
{
StateSerialized?.Invoke(_target, e);
}
/// <summary>
/// Raises the <see cref="VarSerializing" /> event.
/// </summary>
/// <param name="e">Event parameters</param>
internal void RaiseVarSerializing(UxrStateSaveEventArgs e)
{
VarSerializing?.Invoke(_target, e);
}
/// <summary>
/// Raises the <see cref="VarSerialized" /> event.
/// </summary>
/// <param name="e">Event parameters</param>
internal void RaiseVarSerialized(UxrStateSaveEventArgs e)
{
VarSerialized?.Invoke(_target, e);
}
#endregion
#region Private Types & Data
private readonly IUxrStateSave _target;
#endregion
}
}

View File

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

View File

@@ -0,0 +1,52 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrStateSaveOptions.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
namespace UltimateXR.Core.StateSave
{
/// <summary>
/// Flags for state saving options.
/// </summary>
[Flags]
public enum UxrStateSaveOptions
{
/// <summary>
/// No options.
/// </summary>
None = 0,
/// <summary>
/// Simulate the process, but do not read or write any data. The serialization counter
/// <see cref="UxrStateSaveImplementer{T}.SerializeCounter" /> will still be updated to know how many vars would be
/// serialized. The changes cache will be updated unless <see cref="DontCacheChanges" /> is also used.
/// </summary>
DontSerialize = 1 << 0,
/// <summary>
/// Do not update the changes cache. The changes cache stores the last values that were serialized to make
/// sure to serialize changes only in incremental serializations (see <see cref="UxrStateSaveLevel" />).
/// </summary>
DontCacheChanges = 1 << 1,
/// <summary>
/// Do not check the changes cache when writing, which means that the values will be written whether they changed
/// or not.
/// </summary>
DontCheckCache = 1 << 2,
/// <summary>
/// Resets the changes cache, which will set the serialized values as the initial (
/// <see cref="UxrStateSaveLevel.ChangesSinceBeginning" />) and latest (
/// <see cref="UxrStateSaveLevel.ChangesSincePreviousSave" />) states.
/// </summary>
ResetChangesCache = 1 << 3,
/// <summary>
/// Notifies that it is gathering the first initial state.
/// </summary>
FirstFrame = 1 << 10,
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6d83ab819e544593b22ca55e601d88fe
timeCreated: 1705684047

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e488170cacf297842bbd2c6e691a50d0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,49 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IUxrStateSync.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UltimateXR.Core.Unique;
namespace UltimateXR.Core.StateSync
{
/// <summary>
/// <para>
/// Interface for components to synchronize their state changes. State changes can be intercepted,
/// serialized, deserialized and be reproduced back in a different environment. This can be used to
/// synchronize state changes in a network session or save state changes to disk.
/// </para>
/// <para>
/// Relevant internal state changes are notified through a <see cref="StateChanged" /> event. The state
/// change is described by a <see cref="UxrSyncEventArgs" /> object. Each <see cref="UxrSyncEventArgs" />
/// can be reproduced back using the <see cref="SyncState" /> method. This architecture can be used to
/// listen for changes and reproduce them on the other clients, since <see cref="UxrSyncEventArgs" />
/// objects can be serialized.
/// </para>
/// <para>
/// To leverage the implementation of this interface, consider using <see cref="UxrStateSyncImplementer{T}" />.
/// </para>
/// </summary>
public interface IUxrStateSync : IUxrUniqueId
{
#region Public Types & Data
/// <summary>
/// Event raised when a relevant state of a component changed and requires synchronization.
/// </summary>
event EventHandler<UxrSyncEventArgs> StateChanged;
#endregion
#region Public Methods
/// <summary>
/// Executes the state change described by <see cref="e" />.
/// </summary>
/// <param name="e">State change information</param>
void SyncState(UxrSyncEventArgs e);
#endregion
}
}

View File

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

View File

@@ -0,0 +1,109 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrMethodInvokedSyncEventArgs.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Linq;
using UltimateXR.Core.Serialization;
using UltimateXR.Core.Settings;
using UnityEngine;
namespace UltimateXR.Core.StateSync
{
/// <summary>
/// Event args for the state sync of a method that was called. It supports sending the same parameters.
/// </summary>
public class UxrMethodInvokedSyncEventArgs : UxrSyncEventArgs
{
#region Public Types & Data
/// <summary>
/// Gets the method name.
/// </summary>
public string MethodName
{
get => _methodName;
private set => _methodName = value;
}
/// <summary>
/// Gets the call parameters.
/// </summary>
public object[] Parameters
{
get => _parameters;
private set => _parameters = value;
}
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="methodName">The name of the method that was called</param>
/// <param name="parameters">The parameters used in the call</param>
public UxrMethodInvokedSyncEventArgs(string methodName, params object[] parameters)
{
MethodName = methodName;
Parameters = parameters ?? new object[] { };
}
#endregion
#region Public Overrides object
/// <inheritdoc />
public override string ToString()
{
if (MethodName == null && Parameters == null)
{
return "Unknown method call";
}
if (Parameters == null || Parameters.Length == 0)
{
return $"Method call {MethodName}()";
}
return $"Method call {MethodName ?? "unknown"}({string.Join(", ", Parameters.Select(p => p == null ? "null" : p.ToString()))})";
}
#endregion
#region Protected Overrides UxrSyncEventArgs
/// <inheritdoc />
protected override void SerializeEventInternal(IUxrSerializer serializer)
{
bool isMethodKnown = false;
try
{
serializer.Serialize(ref _methodName);
isMethodKnown = true;
serializer.Serialize(ref _parameters);
}
catch (Exception)
{
if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Errors && serializer.IsReading && isMethodKnown)
{
Debug.LogError($"{UxrConstants.CoreModule} Error deserializing invoked method {_methodName}(). Exception below.");
}
throw;
}
}
#endregion
#region Private Types & Data
private string _methodName;
private object[] _parameters;
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: eb960f54581c96d4991f67ac281dd26f
timeCreated: 1650792910

View File

@@ -0,0 +1,81 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrPropertyChangedSyncEventArgs.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Serialization;
namespace UltimateXR.Core.StateSync
{
/// <summary>
/// Event args for the state sync of a property whose value was changed.
/// </summary>
public class UxrPropertyChangedSyncEventArgs : UxrSyncEventArgs
{
#region Public Types & Data
/// <summary>
/// Gets the property name.
/// </summary>
public string PropertyName
{
get => _propertyName;
private set => _propertyName = value;
}
/// <summary>
/// Gets the new property value.
/// </summary>
public object Value
{
get => _value;
private set => _value = value;
}
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="propertyName">The name of the property</param>
/// <param name="value">The new value of the property</param>
public UxrPropertyChangedSyncEventArgs(string propertyName, object value)
{
PropertyName = propertyName;
Value = value;
}
#endregion
#region Public Overrides object
/// <inheritdoc />
public override string ToString()
{
string newValue = Value == null ? "null" : Value.ToString();
return $"Property change {PropertyName ?? "Unknown"} = {newValue ?? "null/unknown"}";
}
#endregion
#region Protected Overrides UxrSyncEventArgs
/// <inheritdoc />
protected override void SerializeEventInternal(IUxrSerializer serializer)
{
serializer.Serialize(ref _propertyName);
serializer.SerializeAnyVar(ref _value);
}
#endregion
#region Private Types & Data
private string _propertyName;
private object _value;
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 126849ebc072f2243851202a432b423e
timeCreated: 1650792910

View File

@@ -0,0 +1,59 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrStateSyncImplementer.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Core.StateSync
{
/// <summary>
/// Base class for <see cref="UxrStateSyncImplementer{T}" />.
/// </summary>
public class UxrStateSyncImplementer
{
#region Public Types & Data
/// <summary>
/// <para>
/// Gets the current call depth of BeginSync/EndSync calls, which are responsible for helping synchronize calls
/// over the network.
/// To avoid redundant synchronization, nested calls (where <see cref="SyncCallDepth" /> is greater than 1),
/// need to be ignored.
/// </para>
/// <para>
/// State synchronization, for networking or other functionality like saving gameplay replays, can be done
/// by subscribing to <see cref="UxrManager.ComponentStateChanged" />. By default, only top level calls will
/// trigger the event. This can be changed using <see cref="UxrManager.UseTopLevelStateChangesOnly" />.
/// </para>
/// <para>
/// In the following code, only PlayerShoot() needs to be synchronized. This will not only save bandwidth, but also
/// make sure that only a single particle system gets instantiated and the shot audio doesn't get played twice.
/// </para>
/// <code>
/// void PlayerShoot(int parameter1, bool parameter2)
/// {
/// BeginSync();
/// ShowParticles(parameter1);
/// PlayAudioShot(parameter2);
/// EndSyncMethod(new object[] {parameter1, parameter2});
/// }
///
/// void ShowParticles(int parameter);
/// {
/// BeginSync();
/// Instantiate(ParticleSystem);
/// EndSyncMethod(new object[] {parameter});
/// }
///
/// void PlayAudioShot(bool parameter);
/// {
/// BeginSync();
/// _audio.Play();
/// EndSyncMethod(new object[] {parameter});
/// }
/// </code>
/// </summary>
public static int SyncCallDepth { get; protected set; }
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7c8beee399964389a19123b9deef1a36
timeCreated: 1705596679

View File

@@ -0,0 +1,308 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrStateSyncImplementer_1.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using UltimateXR.Core.Components;
using UltimateXR.Core.Settings;
using UltimateXR.Extensions.Unity;
using UnityEngine;
namespace UltimateXR.Core.StateSync
{
/// <summary>
/// Helper class simplifying the implementation of the <see cref="IUxrStateSync" /> interface.
/// This class includes functionality for automatic synchronization of property changes and method calls
/// through reflection, using a convenient BeginSync/EndSync pattern.
/// It is utilized by <see cref="UxrComponent" /> to implement <see cref="IUxrStateSync" />.
/// In scenarios where custom classes cannot inherit from <see cref="UxrComponent" /> for automatic sync capabilities,
/// this class is designed to implement the interface.
/// </summary>
public class UxrStateSyncImplementer<T> : UxrStateSyncImplementer where T : Component, IUxrStateSync
{
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="targetComponent">Target component for all the methods called on this object</param>
public UxrStateSyncImplementer(T targetComponent)
{
_targetComponent = targetComponent;
}
/// <summary>
/// Default constructor is private to use public constructor with target component.
/// </summary>
private UxrStateSyncImplementer()
{
}
#endregion
#region Public Methods
/// <summary>
/// Executes the state change described by <see cref="e" /> on the component.<br />
/// First it will check if they are built-in state sync events such as property change (
/// <see cref="UxrPropertyChangedSyncEventArgs" />, using <see cref="BeginSync" />/<see cref="EndSyncProperty" />) and
/// method call (<see cref="UxrMethodInvokedSyncEventArgs" />, using <see cref="BeginSync" />/
/// <see cref="EndSyncMethod" />).
/// If it's a different, custom event, it will be handled using the provided
/// <paramref name="fallbackSyncStateHandler" />.
/// </summary>
/// <param name="e">State change</param>
/// <param name="fallbackSyncStateHandler">Fallback event handler</param>
public void SyncState(UxrSyncEventArgs e, Action<UxrSyncEventArgs> fallbackSyncStateHandler)
{
// First check if it's a synchronization that can be solved at the base level
if (e is UxrPropertyChangedSyncEventArgs propertyChangedEventArgs)
{
try
{
// Set new property value using reflection
_targetComponent.GetType().GetProperty(propertyChangedEventArgs.PropertyName, PropertyFlags).SetValue(_targetComponent, propertyChangedEventArgs.Value);
}
catch (Exception exception)
{
if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.CoreModule} Error trying to sync property {propertyChangedEventArgs.PropertyName} to value {propertyChangedEventArgs.Value} . Component: {_targetComponent.GetPathUnderScene()}. Exception: {exception}");
}
}
}
else if (e is UxrMethodInvokedSyncEventArgs methodInvokedEventArgs)
{
try
{
if (methodInvokedEventArgs.Parameters == null || !methodInvokedEventArgs.Parameters.Any())
{
// Invoke without arguments
_targetComponent.GetType().GetMethod(methodInvokedEventArgs.MethodName, MethodFlags).Invoke(_targetComponent, null);
}
else
{
// Invoke method using same parameters using reflection. Make sure we select the correct overload.
bool anyIsNull = methodInvokedEventArgs.Parameters.Any(p => p == null);
if (_targetComponent.GetType().GetMethods(MethodFlags).Count(m => m.Name.Equals(methodInvokedEventArgs.MethodName)) == 1)
{
// There are no overloads
_targetComponent.GetType().GetMethod(methodInvokedEventArgs.MethodName, MethodFlags).Invoke(_targetComponent, methodInvokedEventArgs.Parameters);
}
else if (!anyIsNull)
{
// We can look for a method specifying the parameter types.
_targetComponent.GetType().GetMethod(methodInvokedEventArgs.MethodName, MethodFlags, null, methodInvokedEventArgs.Parameters.Select(p => p.GetType()).ToArray(), null).Invoke(_targetComponent, methodInvokedEventArgs.Parameters);
}
else
{
// We have a call where a parameter is null, so we can't infer the parameter type. Try to find a method with the same parameter count.
MethodInfo method = _targetComponent.GetType().GetMethods(MethodFlags).FirstOrDefault(x => x.Name.Equals(methodInvokedEventArgs.MethodName) && x.GetParameters().Length == methodInvokedEventArgs.Parameters.Length);
if (method != null)
{
method.Invoke(_targetComponent, methodInvokedEventArgs.Parameters);
}
else
{
throw new Exception("Could not find a method with the given name and parameter count");
}
}
}
}
catch (AmbiguousMatchException ambiguousMatchException)
{
if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.CoreModule} Trying to sync a method that has ambiguous call. {e}. Component: {_targetComponent.GetPathUnderScene()}. Exception: {ambiguousMatchException}");
}
}
catch (Exception exception)
{
if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.CoreModule} Error trying to sync method. It could be that an exception inside the method was thrown, that {nameof(EndSyncMethod)} was used with the wrong parameters or it has an overload that could not be resolved. {e}. Component: {_targetComponent.GetPathUnderScene()}. Exception: {exception}");
}
}
}
else
{
// It's a synchronization using a custom UxrSyncEventArgs object. Pass it to the fallback handler.
try
{
fallbackSyncStateHandler?.Invoke(e);
}
catch (Exception exception)
{
if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.CoreModule} Error trying to sync state. {e}. Component: {_targetComponent.GetPathUnderScene()}. Exception: {exception}");
}
}
}
}
/// <summary>
/// <para>
/// Starts a synchronization block that will end with an EndSync method like <see cref="EndSyncProperty" />,
/// <see cref="EndSyncMethod" /> or <see cref="EndSyncState" />, which causes the
/// <see cref="IUxrStateSync.StateChanged" /> event to be triggered.
/// </para>
/// <para>
/// See <see cref="UxrComponent.BeginSync" />.
/// </para>
/// </summary>
/// <param name="options">Options. It's saved/used in all environments by default.</param>
public void BeginSync(UxrStateSyncOptions options = UxrStateSyncOptions.Default)
{
SyncCallDepth++;
_optionStack.Push(options);
if (SyncCallDepth > StateSyncCallDepthErrorThreshold)
{
if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.CoreModule} BeginSync/EndSync mismatch when calling BeginSync. Did you forget an EndSync call? Component: {_targetComponent.GetPathUnderScene()}");
}
}
}
/// <summary>
/// <para>
/// Cancels a <see cref="BeginSync" /> to escape when a condition is found that makes it not require to sync.
/// </para>
/// <para>
/// See <see cref="UxrComponent.CancelSync" />.
/// </para>
/// </summary>
public void CancelSync()
{
if (SyncCallDepth > 0)
{
SyncCallDepth--;
_optionStack.Pop();
}
else
{
if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.CoreModule} BeginSync/CancelSync mismatch when calling CancelSync. Did you forget a BeginSync call? State call depth is < 1. Component: {_targetComponent.GetPathUnderScene()}");
}
}
}
/// <summary>
/// <para>
/// Ends synchronization for a property change. It notifies that a property was changed in a component that
/// requires network/state synchronization, ensuring that the change is performed in all other clients too.
/// The synchronization should begin using <see cref="BeginSync" />.
/// </para>
/// <para>
/// See <see cref="UxrComponent.EndSyncProperty" />.
/// </para>
/// </summary>
/// <param name="raiseChangedEvent">Delegate that will call the <see cref="IUxrStateSync.StateChanged" /> event</param>
/// <param name="value">New property value</param>
/// <param name="propertyName">Property name</param>
public void EndSyncProperty(Action<UxrSyncEventArgs> raiseChangedEvent, in object value, [CallerMemberName] string propertyName = null)
{
EndSyncState(raiseChangedEvent, new UxrPropertyChangedSyncEventArgs(propertyName, value));
}
/// <summary>
/// <para>
/// Ends synchronization for a method call. It notifies that a method was invoked in a component that requires
/// network/state synchronization, ensuring that the call is performed in all other clients too.
/// The synchronization should begin using <see cref="BeginSync" />.
/// </para>
/// <para>
/// See <see cref="UxrComponent.EndSyncMethod" />.
/// </para>
/// </summary>
public void EndSyncMethod(Action<UxrSyncEventArgs> raiseChangedEvent, object[] parameters = null, [CallerMemberName] string methodName = null)
{
EndSyncState(raiseChangedEvent, new UxrMethodInvokedSyncEventArgs(methodName, parameters));
}
/// <summary>
/// <para>
/// Ends a synchronization block for a custom event. The synchronization block should begin using
/// <see cref="BeginSync" />. The event ensures that the code is executed in all other receivers too.
/// </para>
/// <para>
/// See <see cref="UxrComponent.EndSyncState" />.
/// </para>
/// </summary>
/// <param name="raiseChangedEvent">
/// The delegate to call to raise the <see cref="IUxrStateSync.StateChanged" /> event. The
/// delegate will receive a <see cref="UxrSyncEventArgs" /> as parameter describing the state change.
/// </param>
/// <param name="e">The state change</param>
public void EndSyncState(Action<UxrSyncEventArgs> raiseChangedEvent, UxrSyncEventArgs e)
{
if (SyncCallDepth > 0)
{
e.Options = _optionStack.Pop();
raiseChangedEvent?.Invoke(e);
SyncCallDepth--;
}
else
{
if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.CoreModule} BeginSync/EndSync mismatch when calling EndSync. Did you forget a BeginSync call? State call depth is < 1. Component: {_targetComponent.GetPathUnderScene()}");
}
}
}
/// <summary>
/// Registers the component if it hasn't been registered already.
/// </summary>
public void RegisterIfNecessary()
{
if (!Application.isPlaying)
{
return;
}
if (!_registered)
{
UxrManager.Instance.RegisterStateSyncComponent<T>(_targetComponent);
_registered = true;
}
}
/// <summary>
/// Unregisters the component.
/// </summary>
public void Unregister()
{
UxrManager.Instance.UnregisterStateSyncComponent<T>(_targetComponent);
}
#endregion
#region Private Types & Data
private const int StateSyncCallDepthErrorThreshold = 100;
private const BindingFlags EventFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
private const BindingFlags MethodFlags = EventFlags | BindingFlags.InvokeMethod | BindingFlags.FlattenHierarchy;
private const BindingFlags PropertyFlags = EventFlags | BindingFlags.SetProperty;
private readonly T _targetComponent;
private readonly Stack<UxrStateSyncOptions> _optionStack = new Stack<UxrStateSyncOptions>();
private bool _registered;
#endregion
}
}

View File

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

View File

@@ -0,0 +1,58 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrStateSyncOptions.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
namespace UltimateXR.Core.StateSync
{
/// <summary>
/// Enumerates the different options when using a <see cref="UxrSyncEventArgs" /> with BeginSync().
/// </summary>
[Flags]
public enum UxrStateSyncOptions
{
/// <summary>
/// No save/use.
/// </summary>
None = 0,
/// <summary>
/// Specifies the event should be synchronized over the network.
/// This feature helps save bandwidth on events that do not need network synchronization.
/// For instance, a Transform update might not be needed in a networking scenario because it has already been
/// synchronized using a NetworkTransform component more efficiently. For replays, where the networking component is
/// not working, the transform update will still be needed.
/// </summary>
Network = 1 << 0,
/// <summary>
/// Specifies the event should be saved in replays.
/// For example, UxrGrabbableObject uses a coroutine that syncs grabbable objects with rigidbodies so that the
/// position/rotation and speed keep in sync in all devices every x seconds. This is used only in objects
/// that do not have specific networking components such as NetworkRigidbody or NetworkTransform.
/// In replays, the position and rotation are already sampled so there is no need to save these events.
/// </summary>
Replay = 1 << 1,
/// <summary>
/// Forces to output a new sampling frame before and after the sync event when recording a replay. This can be used
/// to avoid interpolation errors when a certain event affects how values are interpolated.
/// For example re-parenting an object between two frames will create a jump if the position is recorded
/// in local space. Forcing to output a new frame will avoid this.
/// </summary>
GenerateNewFrame = 1 << 8,
/// <summary>
/// Ignores nesting checks, which will generate <see cref="IUxrStateSync.StateChanged"/> events even when
/// the BeginSync/EndSync block is nested.
/// </summary>
IgnoreNestingCheck = 1 << 9,
/// <summary>
/// Save/use in all environments.
/// </summary>
Default = Network | Replay
}
}

View File

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

View File

@@ -0,0 +1,91 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrStateSyncResult.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Core.StateSync
{
/// <summary>
/// Contains the result of executing a state change synchronization.
/// </summary>
public class UxrStateSyncResult
{
#region Public Types & Data
/// <summary>
/// Gets whether there was an error executing the state change.
/// </summary>
public bool IsError => ErrorMessage != null;
/// <summary>
/// Gets the target of the state change.
/// </summary>
public IUxrStateSync Target { get; }
/// <summary>
/// Gets the state change data.
/// </summary>
public UxrSyncEventArgs EventArgs { get; }
/// <summary>
/// Gets the error message if there was an error executing the state change. Otherwise it returns null.
/// </summary>
public string ErrorMessage { get; }
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="target">Target of the state change</param>
/// <param name="eventArgs">Event data containing the state change</param>
/// <param name="errorMessage">Error message if there was an error executing the state change, otherwise null</param>
public UxrStateSyncResult(IUxrStateSync target, UxrSyncEventArgs eventArgs, string errorMessage)
{
Target = target;
EventArgs = eventArgs;
ErrorMessage = errorMessage;
}
#endregion
#region Public Overrides object
/// <inheritdoc />
public override string ToString()
{
string error = !string.IsNullOrEmpty(ErrorMessage) ? $" Error is: {ErrorMessage}" : string.Empty;
if (IsValid)
{
string result = !string.IsNullOrEmpty(ErrorMessage) ? "Could not execute state change" : "Successful state change";
return $"{result} event for {Target.Component.name}, {EventArgs}.{error}";
}
if (Target != null)
{
return $"Unknown event for {Target.Component.name}.{error}";
}
if (EventArgs != null)
{
return $"Event for unresolved target: {EventArgs}.{error}";
}
return error;
}
#endregion
#region Private Types & Data
/// <summary>
/// Gets whether the result contains valid data.
/// </summary>
private bool IsValid => Target != null && EventArgs != null;
#endregion
}
}

View File

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

Some files were not shown because too many files have changed in this diff Show More