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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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
}
}

View File

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