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,137 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ActionExt.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Threading;
using System.Threading.Tasks;
using UltimateXR.Core.Threading.TaskControllers;
using UnityEngine;
namespace UltimateXR.Extensions.System.Threading
{
/// <summary>
/// <see cref="Action" /> extensions.
/// </summary>
public static class ActionExt
{
#region Public Methods
/// <summary>
/// Executes repeatedly this <see cref="Action" />, in the main thread, at <paramref name="rate" /> until cancellation
/// is requested with <paramref name="ct" />.
/// </summary>
/// <param name="self"><see cref="Action" /> to loop at <paramref name="rate" /> Hz</param>
/// <param name="rate">Loop frequency in Hz</param>
/// <param name="ct">Cancellation token</param>
/// <seealso cref="LoopThreaded" />
/// <seealso cref="ToLoop" />
public static async void Loop(this Action self, float rate = 10f, CancellationToken ct = default)
{
if (ct.IsCancellationRequested)
{
return;
}
int deltaTimeMs = Mathf.RoundToInt(1000f / rate);
while (!ct.IsCancellationRequested)
{
// Start delay timer parallel to action execution
Task delayTask = TaskExt.Delay(deltaTimeMs, ct);
self();
await delayTask;
}
}
/// <summary>
/// Executes repeatedly this <see cref="Action" />, in a separated thread, at <paramref name="rate" /> Hz until
/// cancellation is requested using <paramref name="ct" />.
/// </summary>
/// <param name="self"><see cref="Action" /> to loop at <paramref name="rate" /> Hz</param>
/// <param name="rate">Loop frequency in Hz</param>
/// <param name="ct">Cancellation token</param>
public static async void LoopThreaded(this Action self, float rate = 10f, CancellationToken ct = default)
{
if (ct.IsCancellationRequested)
{
return;
}
int deltaTimeMs = Mathf.RoundToInt(1000f / rate);
while (!ct.IsCancellationRequested)
{
// We don't want to abort current thread (Task.Run) with ct
// Instead, we wait for action to end, breaking the loop after that.
Task delayTask = TaskExt.Delay(deltaTimeMs, ct);
Task runTask = Task.Run(self, CancellationToken.None);
await Task.WhenAll(delayTask, runTask);
}
}
/// <summary>
/// Creates a <see cref="UxrLoopController" /> which wraps a cancellable loop executing this <see cref="Action" /> in
/// the main thread.
/// </summary>
/// <param name="self"><see cref="Action" /> to loop at <paramref name="rate" /> Hz</param>
/// <param name="rate">Loop frequency in Hz</param>
/// <param name="autoStartDelay">
/// Delay in milliseconds before loop executes its first iteration.
/// <list type="bullet">
/// <item>
/// Equal or greater than zero: tells <see cref="UxrLoopController" /> to automatically start looping
/// <paramref name="autoStartDelay" /> milliseconds after creation.
/// </item>
/// <item>
/// Negative (default) <see cref="UxrLoopController.Start()" /> needs to be called on returned
/// <see cref="UxrLoopController" /> to start looping.
/// </item>
/// </list>
/// </param>
/// <returns>
/// A <see cref="UxrLoopController" /> to handle (<see cref="UxrLoopController.Start()" />,
/// <see cref="UxrLoopController.Stop" />) the loop execution.
/// </returns>
/// <seealso cref="UxrLoopController" />
/// <seealso cref="Loop" />
/// <seealso cref="ToThreadedLoop" />
public static UxrLoopController ToLoop(this Action self, float rate = 10f, int autoStartDelay = -1)
{
return new UxrLoopController(ct => Loop(self, rate, ct), autoStartDelay);
}
/// <summary>
/// Creates a <see cref="UxrLoopController" /> which wraps a cancellable loop executing this <see cref="Action" /> in a
/// separate thread.
/// </summary>
/// <param name="self"><see cref="Action" /> to loop, in a separate thread, at <paramref name="rate" /> Hz</param>
/// <param name="rate">Loop frequency in Hz</param>
/// <param name="autoStartDelay">
/// Delay in milliseconds before loop executes its first iteration.
/// <list type="bullet">
/// <item>
/// Equal or greater than zero: tells <see cref="UxrLoopController" /> to automatically start looping
/// <paramref name="autoStartDelay" /> milliseconds after creation.
/// </item>
/// <item>
/// Negative (default) <see cref="UxrLoopController.Start()" /> needs to be called on returned
/// <see cref="UxrLoopController" /> to start looping.
/// </item>
/// </list>
/// </param>
/// <returns>
/// A <see cref="UxrLoopController" /> to handle (<see cref="UxrLoopController.Start()" />,
/// <see cref="UxrLoopController.Stop" />) the loop execution.
/// </returns>
/// <seealso cref="UxrLoopController" />
/// <seealso cref="Loop" />
public static UxrLoopController ToThreadedLoop(this Action self, float rate = 10f, int autoStartDelay = -1)
{
return new UxrLoopController(ct => LoopThreaded(self, rate, ct), autoStartDelay);
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,314 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="TaskExt.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Threading;
using System.Threading.Tasks;
using UltimateXR.Animation.Interpolation;
using UltimateXR.Core;
using UltimateXR.Core.Settings;
using UnityEngine;
namespace UltimateXR.Extensions.System.Threading
{
/// <summary>
/// <see cref="Task" /> extensions.
/// </summary>
public static class TaskExt
{
#region Public Methods
/// <summary>
/// Allows to run a task in "fire and forget" mode, when it is not required to await nor is it relevant whether it
/// succeeds or not. There still needs to be a way to handle exceptions to avoid unhandled exceptions and process
/// termination.
/// </summary>
/// <param name="self">Task to run in fire and forget mode</param>
/// <exception cref="ArgumentNullException">The task is null</exception>
public static async void FireAndForget(this Task self)
{
if (self == null)
{
throw new ArgumentNullException(nameof(self));
}
try
{
// Simply awaiting the task in an async void method already guarantees exception propagation
await self;
}
catch (Exception e) // ...but default LogException behaviour only shows innerException.
{
if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.CoreModule} {nameof(TaskExt)}::{nameof(FireAndForget)} Exception missed (stack trace below):{e.Message}\n\n{e}");
Debug.LogException(e); // Log and ignore exceptions, until playlists are empty.
}
}
}
/// <summary>
/// Creates an awaitable task that finishes the next frame.
/// </summary>
/// <param name="ct">Optional cancellation token, to cancel the task</param>
/// <returns>Awaitable task</returns>
public static Task WaitForNextFrame(CancellationToken ct = default)
{
return SkipFrames(1, ct);
}
/// <summary>
/// Creates an awaitable task that finishes after a given amount of frames.
/// </summary>
/// <param name="frameCount">Number of frames to wait</param>
/// <param name="ct">Optional cancellation token, to cancel the task</param>
/// <returns>Awaitable task</returns>
public static async Task SkipFrames(int frameCount, CancellationToken ct = default)
{
if (ct.IsCancellationRequested || frameCount <= 0)
{
return;
}
for (uint i = 0; i < frameCount && !ct.IsCancellationRequested; ++i)
{
await Task.Yield();
}
}
/// <summary>
/// Creates an awaitable task that finishes after a given amount of seconds.
/// </summary>
/// <param name="seconds">Number of seconds to wait</param>
/// <param name="ct">Optional cancellation token, to cancel the task</param>
/// <returns>Awaitable task</returns>
public static Task Delay(float seconds, CancellationToken ct = default)
{
return Delay(Mathf.RoundToInt(1000f * seconds), ct);
}
/// <summary>
/// Creates an awaitable task that finishes after a given amount of milliseconds.
/// </summary>
/// <param name="milliseconds">Number of milliseconds to wait</param>
/// <param name="ct">Optional cancellation token, to cancel the task</param>
/// <returns>Awaitable task</returns>
public static async Task Delay(int milliseconds, CancellationToken ct = default)
{
if (ct.IsCancellationRequested || milliseconds <= 0)
{
return;
}
try
{
await Task.Delay(milliseconds, ct);
}
catch (OperationCanceledException)
{
// ignore: Task.Delay throws this exception when ct.IsCancellationRequested = true
// In this case, we only want to stop polling and finish this async Task.
}
}
/// <summary>
/// Creates an awaitable task that blocks while a condition is true or the task is canceled.
/// </summary>
/// <param name="condition">The condition that will perpetuate the block</param>
/// <param name="ct">Optional cancellation token, to cancel the task</param>
/// <returns>Awaitable <see cref="Task" /></returns>
public static async Task WaitWhile(Func<bool> condition, CancellationToken ct = default)
{
while (!ct.IsCancellationRequested && condition())
{
await Task.Yield();
}
}
/// <summary>
/// Creates an awaitable task that blocks until a condition is true or the task is canceled.
/// </summary>
/// <param name="condition">The condition that will perpetuate the block</param>
/// <param name="ct">Optional cancellation token, to cancel the task</param>
/// <returns>Awaitable <see cref="Task" /></returns>
public static async Task WaitUntil(Func<bool> condition, CancellationToken ct = default)
{
while (!ct.IsCancellationRequested && !condition())
{
await Task.Yield();
}
}
/// <summary>
/// Creates an awaitable task that blocks while a condition is true, a timeout occurs or the task is canceled.
/// </summary>
/// <param name="condition">The condition that will perpetuate the block</param>
/// <param name="timeout">Timeout, in milliseconds</param>
/// <param name="ct">Optional cancellation token, to cancel the task</param>
/// <returns>Awaitable <see cref="Task" /></returns>
/// <exception cref="TimeoutException">Thrown after <see cref="timeout" /> milliseconds</exception>
public static async Task WaitWhile(Func<bool> condition, int timeout, CancellationToken ct = default)
{
if (ct.IsCancellationRequested)
{
return;
}
using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
Task waitTask = WaitWhile(condition, cts.Token);
Task timeoutTask = Delay(timeout, cts.Token);
Task finishedTask = await Task.WhenAny(waitTask, timeoutTask);
if (!finishedTask.IsCanceled)
{
cts.Cancel(); // Cancel unfinished task
await finishedTask; // Propagate exceptions
if (finishedTask == timeoutTask)
{
throw new TimeoutException();
}
}
}
/// <summary>
/// Creates an awaitable task that blocks until a condition is true, a timeout occurs or the task is canceled.
/// </summary>
/// <param name="condition">The condition that will perpetuate the block</param>
/// <param name="timeout">Timeout, in milliseconds</param>
/// <param name="ct">Optional cancellation token, to cancel the task</param>
/// <returns>Awaitable <see cref="Task" /></returns>
/// <exception cref="TimeoutException">Thrown after <see cref="timeout" /> milliseconds</exception>
public static async Task WaitUntil(Func<bool> condition, int timeout, CancellationToken ct = default)
{
if (ct.IsCancellationRequested)
{
return;
}
using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
Task waitTask = WaitUntil(condition, cts.Token);
Task timeoutTask = Delay(timeout, cts.Token);
Task finishedTask = await Task.WhenAny(waitTask, timeoutTask);
if (!finishedTask.IsCanceled)
{
cts.Cancel(); // Cancel unfinished task
await finishedTask; // Propagate exceptions
if (finishedTask == timeoutTask)
{
throw new TimeoutException();
}
}
}
/// <summary>
/// Creates an awaitable task that blocks while a condition is true, waiting a certain amount of seconds at maximum. An
/// optional action can be called if the task was cancelled or it timed out.
/// </summary>
/// <param name="condition">The condition that will perpetuate the block</param>
/// <param name="duration">The maximum amount of seconds to wait while the condition is true</param>
/// <param name="cancelCallback">Optional action to execute if the task was canceled or it timed out</param>
/// <param name="ct">Optional cancellation token, to cancel the task</param>
/// <returns>Awaitable <see cref="Task" /></returns>
public static async Task WaitWhile(Func<bool> condition, float duration, Action cancelCallback = null, CancellationToken ct = default)
{
int timeout = Mathf.RoundToInt(duration * 1200f);
bool mustCancel;
try
{
await WaitWhile(condition, timeout, ct);
mustCancel = ct.IsCancellationRequested;
}
catch (TimeoutException)
{
mustCancel = true;
}
if (mustCancel)
{
cancelCallback?.Invoke();
}
}
/// <summary>
/// Creates an awaitable task that blocks until a condition is true, waiting a certain amount of seconds at maximum. An
/// optional action can be called if the task was cancelled or it timed out.
/// </summary>
/// <param name="condition">The condition that will perpetuate the block</param>
/// <param name="duration">The maximum amount of seconds to wait while the condition is true</param>
/// <param name="cancelCallback">Optional action to execute if the task was canceled or it timed out</param>
/// <param name="ct">Optional cancellation token, to cancel the task</param>
/// <returns>Awaitable <see cref="Task" /></returns>
public static async Task WaitUntil(Func<bool> condition, float duration, Action cancelCallback = null, CancellationToken ct = default)
{
int timeout = Mathf.RoundToInt(duration * 1200f);
bool mustCancel;
try
{
await WaitUntil(condition, timeout, ct);
mustCancel = ct.IsCancellationRequested;
}
catch (TimeoutException)
{
mustCancel = true;
}
if (mustCancel)
{
cancelCallback?.Invoke();
}
}
/// <summary>
/// Provides a one-liner method to await until a task is cancelled.
/// </summary>
/// <param name="ct">Cancellation token</param>
/// <returns>Awaitable <see cref="Task" /></returns>
public static async Task WaitUntilCancelled(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
await Task.Yield();
}
}
/// <summary>
/// Loops iterating once per frame during a specified amount of time, executing a user-defined action.
/// </summary>
/// <param name="ct">The cancellation token</param>
/// <param name="durationSeconds">Loop duration in seconds</param>
/// <param name="loopAction">
/// The action performed each frame, which will receive the interpolation [0.0, 1.0] parameter as
/// argument.
/// </param>
/// <param name="easing">The easing used to compute the interpolation parameter over time</param>
/// <param name="forceLastT1">
/// Will enforce a last iteration with 1.0 interpolation parameter. This will avoid
/// having a last step with close than, but not 1.0, interpolation.
/// </param>
public static async Task Loop(CancellationToken ct,
float durationSeconds,
Action<float> loopAction,
UxrEasing easing = UxrEasing.Linear,
bool forceLastT1 = false)
{
float startTime = Time.time;
while (Time.time - startTime < durationSeconds)
{
float t = UxrInterpolator.Interpolate(0.0f, 1.0f, Time.time - startTime, new UxrInterpolationSettings(durationSeconds, 0.0f, easing));
loopAction(t);
await Task.Yield();
}
if (forceLastT1)
{
loopAction(1.0f);
}
}
#endregion
}
}

View File

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