Add ultimate xr

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

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7319f4707f694720bbc7b2520eff6493
timeCreated: 1644230689

View File

@@ -0,0 +1,207 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrCancellableController.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Threading;
using UltimateXR.Extensions.System.Threading;
using UnityEngine;
namespace UltimateXR.Core.Threading.TaskControllers
{
/// <summary>
/// Parent abstract class that simplifies running a job which can be canceled through a
/// <see cref="CancellationToken" />.<br />
/// It wraps a <see cref="CancellationTokenSource" /> into a <see cref="Start()" /> and <see cref="Stop" /> pattern,
/// ensuring that the
/// <see cref="Stop" /> is called if the application quits abruptly or the Unity editor exits playmode.
/// </summary>
/// <seealso cref="UxrTaskController" />
/// <seealso cref="UxrLoopController" />
public abstract class UxrCancellableController
{
#region Public Types & Data
/// <summary>
/// Triggered when the inner job is completed, without having been canceled.
/// </summary>
public event EventHandler Completed;
/// <summary>
/// Gets whether the inner job is currently running.
/// </summary>
public bool IsRunning => _cts != null;
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
protected UxrCancellableController()
{
Application.quitting += Application_quitting;
}
#endregion
#region Public Methods
/// <summary>
/// Starts running the inner job until completion or <see cref="Stop" /> is called.
/// </summary>
public void Start()
{
Start(0, 0);
}
/// <summary>
/// Starts using an initial <paramref name="delayMilliseconds" />.
/// </summary>
/// <param name="delayMilliseconds">
/// Delay in milliseconds before <see cref="Start()" /> is automatically called.
/// </param>
public void StartAfterMilliseconds(int delayMilliseconds)
{
Start(delayMilliseconds, 0);
}
/// <summary>
/// Calls <see cref="Start()" /> and will automatically call <see cref="Stop" /> after
/// <paramref name="durationMilliseconds" /> milliseconds.
/// </summary>
/// <param name="durationMilliseconds">
/// Allowed running time until <see cref="Stop" /> is automatically called, in milliseconds
/// </param>
public void StartAndRunForMilliseconds(int durationMilliseconds)
{
Start(0, durationMilliseconds);
}
/// <summary>
/// Combines functionality of <see cref="StartAfterMilliseconds" /> and <see cref="StartAndRunForMilliseconds" />,
/// allowing to <see cref="Start()" /> after a delay and run for a certain amount of time.
/// </summary>
/// <param name="delayMilliseconds">
/// Delay in milliseconds before <see cref="Start()" /> is automatically called.
/// </param>
/// <param name="durationMilliseconds">
/// Allowed running time starting after the initial delay until <see cref="Stop" /> is automatically called, in
/// milliseconds.
/// </param>
public async void Start(int delayMilliseconds, int durationMilliseconds)
{
if (delayMilliseconds > 0)
{
Stop();
_cts = new CancellationTokenSource(delayMilliseconds);
await TaskExt.Delay(delayMilliseconds, _cts.Token);
if (_cts.IsCancellationRequested)
{
return;
}
}
Stop();
_cts = durationMilliseconds > 0 ? new CancellationTokenSource(durationMilliseconds) : new CancellationTokenSource();
StartInternal(_cts.Token, OnCompleted);
}
/// <summary>
/// Cancels the inner job.
/// </summary>
public void Stop()
{
if (!IsRunning)
{
return;
}
_cts.Cancel();
_cts.Dispose();
_cts = null;
}
/// <summary>
/// Creates a linked <see cref="CancellationTokenSource" /> using the internal controller token source.
/// This allows to create token sources that will be cancelled if the controller gets cancelled.
/// </summary>
/// <returns>Linked cancellation token source</returns>
/// <exception cref="InvalidOperationException">
/// The Task was not started, which means that there is no internal cancellation token source available yet. The Task
/// must have been started to create a linked cancellation token source.
/// </exception>
public CancellationTokenSource CreateLinkedTokenSource()
{
if (_cts == null)
{
throw new InvalidOperationException($"{nameof(GetType)}: Cannot create linked token source when Task is not running.");
}
return CancellationTokenSource.CreateLinkedTokenSource(_cts.Token);
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called when the application is about to quit. Ensures that the task is stopped.
/// </summary>
private void Application_quitting()
{
Stop();
}
#endregion
#region Event Trigger Methods
/// <summary>
/// Event trigger for <see cref="Completed" />.
/// </summary>
private void OnCompleted()
{
if (_cts is { IsCancellationRequested: false })
{
Completed?.Invoke(this, EventArgs.Empty);
Stop();
}
}
#endregion
#region Protected Methods
/// <summary>
/// Implements the internal logic between with <see cref="Start()" /> and <see cref="Stop" />.
/// </summary>
/// <param name="ct">
/// Flags, with
/// <see cref="CancellationToken.IsCancellationRequested" />, when <see cref="Stop" /> has been requested.
/// </param>
/// <param name="onCompleted">
/// Optional callback when the logic has completed, so that the base class can free resources. The callback is only
/// invoked if the logic fully completed. If the logic was stopped manually, the callback is not invoked.
/// </param>
/// <remarks>
/// In case the implementation can finish on its own, please invoke <paramref name="onCompleted" /> instead of
/// <see cref="Stop" />.
/// </remarks>
/// <seealso cref="UxrCancellableController" />
protected abstract void StartInternal(CancellationToken ct, Action onCompleted);
#endregion
#region Private Types & Data
private CancellationTokenSource _cts;
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cbb124c0e0744e1e895b6ae566258881
timeCreated: 1627554582

View File

@@ -0,0 +1,78 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrLoopController.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Threading;
namespace UltimateXR.Core.Threading.TaskControllers
{
/// <summary>
/// A wrapper class to turn a cancelable action into a controllable <see cref="UxrCancellableController.Start()" />/
/// <see cref="UxrCancellableController.Stop" /> pattern and run it uninterruptedly in a loop.
/// </summary>
public sealed class UxrLoopController : UxrCancellableController
{
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="loopAction">
/// A cancelable and loopable action that will be executing repeatedly until
/// <see cref="UxrCancellableController.Stop" /> is called.
/// </param>
/// <param name="autoStartDelayMilliseconds">
/// <list type="bullet">
/// <item>
/// If set, <paramref name="loopAction" /> starts looping after <paramref name="autoStartDelayMilliseconds" />
/// milliseconds.
/// </item>
/// <item>If not set, <paramref name="loopAction" /> starts looping immediately.</item>
/// </list>
/// </param>
public UxrLoopController(Action<CancellationToken> loopAction, int autoStartDelayMilliseconds = -1)
{
_loopAction = loopAction;
StartAfterMilliseconds(autoStartDelayMilliseconds);
}
#endregion
#region Event Handling Methods
/// <summary>
/// Explicit conversion operator from <see cref="Action{CancellationToken}" /> to <see cref="UxrLoopController" />.
/// </summary>
/// <param name="loopAction">
/// A cancelable and loopable action that will be executing repeatedly until
/// <see cref="UxrCancellableController.Stop" /> is called.
/// </param>
/// <returns>
/// A new instance of <see cref="UxrLoopController" /> wrapping <paramref name="loopAction" />.
/// </returns>
public static explicit operator UxrLoopController(Action<CancellationToken> loopAction)
{
return new UxrLoopController(loopAction);
}
#endregion
#region Protected Overrides UxrCancellableController
/// <inheritdoc />
protected override void StartInternal(CancellationToken ct, Action onCompleted)
{
_loopAction(ct); // Executes _loopAction until cancellation
}
#endregion
#region Private Types & Data
private readonly Action<CancellationToken> _loopAction;
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9d7e7222b7d24c9591199c3a739519d5
timeCreated: 1627554582

View File

@@ -0,0 +1,113 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrTaskController.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Threading;
using System.Threading.Tasks;
namespace UltimateXR.Core.Threading.TaskControllers
{
/// <summary>
/// A class that simplifies running tasks in Unity, taking care of stopping them automatically if an application quits
/// or Unity exits playmode.
/// </summary>
/// <example>
/// <para>
/// UxrTaskController simplifies running the task and takes care of stopping it automatically if Unity or the
/// application stops/quits.
/// The constructor lets you start the task automatically, without requiring any further instructions, and also
/// start/stop it manually if needed.
/// </para>
/// <code>
/// // An asynchronous task
/// public async Task MyTask(int parameterA, CancellationToken ct)
/// {
/// await SomethingAsync(ct);
/// }
/// <br />
/// // Create the task but don't start it yet (autoStart = false).
/// UxrTaskController taskController = new UxrTaskController(ct => MyTask(10, ct), false);<br />
/// <br />
/// // Start the task manually. There are optional parameters for delayed start or forced duration.
/// taskController.Start();<br />
/// <br />
/// // Stop the task manually at any point.
/// taskController.Stop();
/// </code>
/// </example>
public sealed class UxrTaskController : UxrCancellableController
{
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="taskFunc">
/// A cancelable task which that be executed asynchronously until completion or
/// <see cref="UxrCancellableController.Stop" /> is called.
/// </param>
/// <param name="autoStart">
/// <list type="bullet">
/// <item>
/// <term><see langword="false" />: </term>
/// <description>
/// <see cref="UxrCancellableController.Start()" /> needs to be called in order to start
/// <paramref name="taskFunc" /> execution.
/// </description>
/// </item>
/// <item>
/// <term><see langword="true" />: </term>
/// <description><paramref name="taskFunc" /> starts executing immediately.</description>
/// </item>
/// </list>
/// </param>
public UxrTaskController(Func<CancellationToken, Task> taskFunc, bool autoStart = false)
{
_taskFunc = taskFunc;
if (autoStart)
{
Start();
}
}
#endregion
#region Event Handling Methods
/// <summary>
/// Explicit conversion operator from <see cref="Func{CancellationToken,Task}" /> to <see cref="UxrTaskController" />.
/// </summary>
/// <param name="taskFunc">
/// A cancelable task that will be executed asynchronously until completion or
/// <see cref="UxrCancellableController.Stop" /> is called.
/// </param>
/// <returns>
/// A new instance of <see cref="UxrTaskController" /> wrapping <paramref name="taskFunc" />.
/// </returns>
public static explicit operator UxrTaskController(Func<CancellationToken, Task> taskFunc)
{
return new UxrTaskController(taskFunc);
}
#endregion
#region Protected Overrides UxrCancellableController
/// <inheritdoc />
protected override async void StartInternal(CancellationToken ct, Action onCompleted)
{
await _taskFunc(ct);
onCompleted();
}
#endregion
#region Private Types & Data
private readonly Func<CancellationToken, Task> _taskFunc;
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d151491a5e3e4e9fb8f7faff1e776f2f
timeCreated: 1621256057

View File

@@ -0,0 +1,102 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrMonoDispatcher.WorkDoubleBuffer.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
namespace UltimateXR.Core.Threading
{
public sealed partial class UxrMonoDispatcher
{
#region Private Types & Data
/// <summary>
/// A double buffered working queue.
/// </summary>
private sealed class WorkDoubleBuffer
{
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
public WorkDoubleBuffer()
{
_input = new Queue<Action>();
_output = new Queue<Action>();
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="capacity">Initial input and output queue capacity</param>
public WorkDoubleBuffer(int capacity)
{
_input = new Queue<Action>(capacity);
_output = new Queue<Action>(capacity);
}
#endregion
#region Public Methods
/// <summary>
/// Executes all enqueued actions.
/// </summary>
/// <exception cref="Exception">
/// A delegate callback throws an exception.
/// </exception>
public void Flush()
{
Switch();
foreach (var action in _output)
{
action?.Invoke();
}
}
/// <summary>
/// Enqueues a new action that should be executed.
/// </summary>
/// <param name="workItem">Action to execute</param>
public void Enqueue(Action workItem)
{
lock (_lock)
{
_input.Enqueue(workItem);
}
}
#endregion
#region Private Methods
/// <summary>
/// Switches the buffers.
/// </summary>
private void Switch()
{
lock (_lock)
{
(_output, _input) = (_input, _output);
}
}
#endregion
#region Private Types & Data
private readonly object _lock = new object();
private Queue<Action> _input;
private Queue<Action> _output;
#endregion
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 828c7fd865a74fbf93cc1eb280b9fdf7
timeCreated: 1624656362

View File

@@ -0,0 +1,234 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrMonoDispatcher.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Threading;
using System.Threading.Tasks;
using UltimateXR.Core.Components;
using UltimateXR.Core.Settings;
using UltimateXR.Extensions.System;
using UltimateXR.Extensions.System.Threading;
using UnityEngine;
namespace UltimateXR.Core.Threading
{
/// <summary>
/// A dispatcher that helps ensuring code runs on the main thread. Most Unity user functionality requires to be called
/// from the main thread.
/// </summary>
public sealed partial class UxrMonoDispatcher : UxrComponent
{
#region Public Types & Data
/// <summary>
/// Gets whether the caller is running on the main thread.
/// </summary>
public static bool IsCurrentThreadMain => !Application.isPlaying || s_mainThread == Thread.CurrentThread;
#endregion
#region Public Methods
/// <summary>
/// Runs code on the main thread.
/// </summary>
/// <param name="action">
/// Action to execute.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="action" /> is <see langword="null" />
/// </exception>
/// <exception cref="Exception">
/// A delegate callback throws an exception.
/// </exception>
public static void RunOnMainThread(Action action)
{
action.ThrowIfNull(nameof(action));
if (!Application.isPlaying || Thread.CurrentThread == s_mainThread)
{
action();
}
else
{
Instance.Enqueue(action);
Instance.StartDispatching();
}
}
/// <summary>
/// Runs code on the main thread.
/// </summary>
/// <param name="actions">
/// An variable set of actions that will run on the main thread sequentially.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="actions" /> is <see langword="null" />
/// </exception>
/// <exception cref="Exception">
/// A delegate callback throws an exception.
/// </exception>
public static void RunOnMainThread(params Action[] actions)
{
if (!Application.isPlaying || Thread.CurrentThread == s_mainThread)
{
foreach (Action action in actions)
{
action();
}
}
else
{
foreach (Action action in actions)
{
Instance.Enqueue(action);
}
Instance.StartDispatching();
}
}
/// <summary>
/// Runs code on the main thread, asynchronously.
/// </summary>
/// <param name="ct">
/// Cancellation token that allows to cancel the task.
/// </param>
/// <param name="action">
/// The action to execute.
/// </param>
/// <exception cref="Exception">
/// A delegate callback throws an exception.
/// </exception>
/// <returns>
/// An awaitable <see cref="Task" /> that finishes when the operation finished.
/// </returns>
public static Task RunOnMainThreadAsync(CancellationToken ct, Action action)
{
if (ct.IsCancellationRequested)
{
return Task.CompletedTask;
}
if (!Application.isPlaying || Thread.CurrentThread == s_mainThread)
{
action();
return Task.CompletedTask;
}
Instance.Enqueue(action);
return Instance.DispatchAsync(ct);
}
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
if (s_instance is null)
{
s_instance = this;
DontDestroyOnLoad(gameObject);
if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.CoreModule} {nameof(UxrMonoDispatcher)} singleton successfully initialized on Awake", this);
}
}
else if (!ReferenceEquals(s_instance, this))
{
if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Warnings)
{
Debug.LogWarning($"{UxrConstants.CoreModule} {nameof(UxrMonoDispatcher)} singleton already initialized. Destroying secondary instance on Awake", this);
}
Destroy(this);
}
}
/// <summary>
/// Flushes the queue, executing all remaining actions, and disables the component.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
_workQueue.Flush();
enabled = false;
}
#endregion
#region Private Methods
/// <summary>
/// Tries to find the singleton.
/// </summary>
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void Initialize()
{
s_mainThread = Thread.CurrentThread;
s_instance = FindObjectOfType<UxrMonoDispatcher>();
if (!(s_instance is null))
{
Debug.Log($"[{nameof(UxrMonoDispatcher)} singleton successfully found in scene.");
}
}
/// <summary>
/// Dispatches the current actions.
/// </summary>
/// <param name="ct">Cancellation token</param>
/// <returns>Awaitable <see cref="Task" /> that finishes when the actions were dispatched</returns>
private Task DispatchAsync(CancellationToken ct)
{
StartDispatching();
return TaskExt.WaitWhile(() => IsDispatching, ct);
}
/// <summary>
/// Enqueues a new action.
/// </summary>
/// <param name="workItem">Action to enqueue</param>
private void Enqueue(Action workItem)
{
_workQueue.Enqueue(workItem);
}
/// <summary>
/// Enables the component so that it starts dispatching the enqueued actions.
/// </summary>
private void StartDispatching()
{
enabled = true;
}
#endregion
#region Private Types & Data
/// <summary>
/// Gets the singleton.
/// </summary>
private static UxrMonoDispatcher Instance => s_instance ? s_instance : s_instance = new GameObject(nameof(UxrMonoDispatcher)).AddComponent<UxrMonoDispatcher>();
/// <summary>
/// Gets whether it is currently dispatching.
/// </summary>
private bool IsDispatching => enabled;
private static UxrMonoDispatcher s_instance;
private static Thread s_mainThread;
private readonly WorkDoubleBuffer _workQueue = new WorkDoubleBuffer(8);
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2eb59326e7404032b2825f4e4a993981
timeCreated: 1611659570