Add ultimate xr
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3f1e439607f93f8429810b27bd58f32c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb960f54581c96d4991f67ac281dd26f
|
||||
timeCreated: 1650792910
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 126849ebc072f2243851202a432b423e
|
||||
timeCreated: 1650792910
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7c8beee399964389a19123b9deef1a36
|
||||
timeCreated: 1705596679
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 759872880e47518449e2348177609a7b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 786fe7232748b434dad29e2c924229c0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5413f228685ae7f429bfa593f430c076
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,199 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrSyncEventArgs.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization;
|
||||
using UltimateXR.Core.Serialization;
|
||||
using UltimateXR.Core.Settings;
|
||||
using UltimateXR.Extensions.System;
|
||||
using UltimateXR.Extensions.System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Core.StateSync
|
||||
{
|
||||
/// <summary>
|
||||
/// Base event args to synchronize the state of entities for network synchronization.
|
||||
/// </summary>
|
||||
/// <seealso cref="IUxrStateSync" />
|
||||
public abstract class UxrSyncEventArgs : EventArgs
|
||||
{
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the options. See <see cref="UxrStateSyncOptions" /> flags.
|
||||
/// </summary>
|
||||
public UxrStateSyncOptions Options { get; set; } = UxrStateSyncOptions.Default;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors & Finalizer
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor.
|
||||
/// </summary>
|
||||
protected UxrSyncEventArgs()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes event data serialized using <see cref="SerializeEventBinary" /> and returns the intended target and
|
||||
/// event parameters.
|
||||
/// </summary>
|
||||
/// <param name="serializedEvent">The byte array with the serialized data</param>
|
||||
/// <param name="stateSync">Event target that should execute the event</param>
|
||||
/// <param name="eventArgs">Event parameters</param>
|
||||
/// <param name="errorMessage">Will return null if there were no errors or an error message when the method returns false</param>
|
||||
/// <returns>Whether the event was deserialized correctly</returns>
|
||||
public static bool DeserializeEventBinary(byte[] serializedEvent, out IUxrStateSync stateSync, out UxrSyncEventArgs eventArgs, out string errorMessage)
|
||||
{
|
||||
stateSync = null;
|
||||
eventArgs = null;
|
||||
|
||||
if (serializedEvent == null)
|
||||
{
|
||||
errorMessage = "Serialized event byte array is null";
|
||||
return false;
|
||||
}
|
||||
|
||||
string logPrefix = $"{nameof(UxrSyncEventArgs)}.{nameof(DeserializeEventBinary)}";
|
||||
|
||||
using (MemoryStream stream = new MemoryStream(serializedEvent))
|
||||
{
|
||||
using (BinaryReader reader = new BinaryReader(stream))
|
||||
{
|
||||
// Instantiate event object and deserialize it.
|
||||
|
||||
int serializationVersion = 0;
|
||||
Exception componentReadException = null;
|
||||
string eventTypeName = null;
|
||||
string eventTypeAssemblyName = null;
|
||||
Type syncEventType = null;
|
||||
|
||||
try
|
||||
{
|
||||
serializationVersion = reader.ReadUInt16();
|
||||
syncEventType = reader.ReadType(serializationVersion, out eventTypeName, out eventTypeAssemblyName);
|
||||
stateSync = reader.ReadAnyVar(serializationVersion) as IUxrStateSync;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (syncEventType == null)
|
||||
{
|
||||
errorMessage = $"{logPrefix}: Error creating/deserializing event. Cannot read event type. Exception message: {e}";
|
||||
return false;
|
||||
}
|
||||
|
||||
componentReadException = e;
|
||||
}
|
||||
|
||||
object eventObject = FormatterServices.GetUninitializedObject(syncEventType); // Creates instance without calling constructor
|
||||
bool eventDeserializeStarted = false;
|
||||
bool eventDeserializeFinished = false;
|
||||
|
||||
try
|
||||
{
|
||||
eventArgs = eventObject as UxrSyncEventArgs;
|
||||
|
||||
if (eventArgs == null)
|
||||
{
|
||||
throw new Exception($"Unknown event class ({TypeExt.GetTypeString(eventTypeName, eventTypeAssemblyName)})");
|
||||
}
|
||||
|
||||
if (componentReadException == null && stateSync == null)
|
||||
{
|
||||
throw new Exception($"Target component ({TypeExt.GetTypeString(eventTypeName, eventTypeAssemblyName)}) doesn't implement interface {nameof(IUxrStateSync)}");
|
||||
}
|
||||
|
||||
eventDeserializeStarted = true;
|
||||
|
||||
eventArgs.SerializeEventInternal(new UxrBinarySerializer(reader, serializationVersion));
|
||||
|
||||
eventDeserializeFinished = true;
|
||||
|
||||
if (componentReadException != null)
|
||||
{
|
||||
throw new Exception($"{logPrefix}: Target component wasn't found for event. Event is {eventArgs}. Exception message: {componentReadException}.");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (!eventDeserializeStarted || eventDeserializeFinished)
|
||||
{
|
||||
errorMessage = e.Message;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (componentReadException != null)
|
||||
{
|
||||
errorMessage = $"{logPrefix}: Error creating/deserializing event for unknown target component. Event (may show missing data) is {eventArgs}. Exception message: {e}";
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMessage = $"{logPrefix}: Error creating/deserializing event for {stateSync}. Event (may show missing data) is {eventArgs}. Exception message: {e}";
|
||||
}
|
||||
}
|
||||
|
||||
if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Errors)
|
||||
{
|
||||
Debug.LogError($"{UxrConstants.CoreModule} {errorMessage}");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the event to a byte array so that it can be sent through the network or saved to disk.
|
||||
/// </summary>
|
||||
/// <param name="sourceComponent">
|
||||
/// Component that generated the event.
|
||||
/// </param>
|
||||
/// <returns>Byte array representing the event</returns>
|
||||
public byte[] SerializeEventBinary(IUxrStateSync sourceComponent)
|
||||
{
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
{
|
||||
using (BinaryWriter writer = new BinaryWriter(stream))
|
||||
{
|
||||
// Serialize the binary serialization version
|
||||
writer.Write((ushort)UxrConstants.Serialization.CurrentBinaryVersion);
|
||||
|
||||
// Serialize event type to be able to instantiate event object using reflection
|
||||
writer.Write(GetType());
|
||||
|
||||
// Serialize the component that generated the event
|
||||
writer.WriteAnyVar(sourceComponent);
|
||||
|
||||
// Serialize the event data
|
||||
SerializeEventInternal(new UxrBinarySerializer(writer));
|
||||
}
|
||||
stream.Flush();
|
||||
return stream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Protected Methods
|
||||
|
||||
/// <summary>
|
||||
/// Serialization/Deserialization of event parameters.
|
||||
/// </summary>
|
||||
/// <param name="serializer">Serializer that can both serialize and deserialize, so that a single method is used</param>
|
||||
protected abstract void SerializeEventInternal(IUxrSerializer serializer);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 48ad91b045414ebd8dcf0c5c1b357f32
|
||||
timeCreated: 1650792910
|
||||
Reference in New Issue
Block a user