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,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