Move third party assets to ThirdParty folder
This commit is contained in:
3
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Collections.meta
vendored
Normal file
3
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Collections.meta
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 438cf96278134136af485d7eff7f16aa
|
||||
timeCreated: 1620807425
|
||||
130
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Collections/CollectionExt.cs
vendored
Normal file
130
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Collections/CollectionExt.cs
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="CollectionExt.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UltimateXR.Extensions.System.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="IReadOnlyCollection{T}" /> and <see cref="ICollection{T}" /> extensions.
|
||||
/// </summary>
|
||||
public static class CollectionExt
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Throws an exception if a given index is out of a <see cref="IReadOnlyCollection{T}" /> bounds.
|
||||
/// </summary>
|
||||
/// <param name="self">Collection</param>
|
||||
/// <param name="index">Index to check if it is out of bounds</param>
|
||||
/// <param name="paramName">Optional argument name</param>
|
||||
/// <typeparam name="T">Element type</typeparam>
|
||||
/// <exception cref="IndexOutOfRangeException">When index is out of range and no parameter name was specified</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">When index is out of range and a parameter name was specified</exception>
|
||||
public static void ThrowIfInvalidIndex<T>(this IReadOnlyCollection<T> self, int index, string paramName = null)
|
||||
{
|
||||
if (index >= 0 && index < self.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(paramName))
|
||||
{
|
||||
throw new IndexOutOfRangeException($"Index[{index}] out of range for collection of {typeof(T).Name}");
|
||||
}
|
||||
throw new ArgumentOutOfRangeException(paramName, index, $"Index[{index}] out of range for collection of {typeof(T).Name}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an exception if any of the given indexes is out of a <see cref="IReadOnlyCollection{T}" /> bounds.
|
||||
/// </summary>
|
||||
/// <param name="self">Collection</param>
|
||||
/// <param name="index1">Index 1 to check if it is out of bounds</param>
|
||||
/// <param name="index2">Index 2 to check if it is out of bounds</param>
|
||||
/// <typeparam name="T">Element type</typeparam>
|
||||
/// <seealso cref="ThrowIfInvalidIndex{T}" />
|
||||
public static void ThrowIfInvalidIndexes<T>(this IReadOnlyCollection<T> self, int index1, int index2)
|
||||
{
|
||||
self.ThrowIfInvalidIndex(index1);
|
||||
self.ThrowIfInvalidIndex(index2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an exception if any of the given indexes is out of a <see cref="IReadOnlyCollection{T}" /> bounds.
|
||||
/// </summary>
|
||||
/// <param name="self">Collection</param>
|
||||
/// <param name="index1">Index 1 to check if it is out of bounds</param>
|
||||
/// <param name="index2">Index 2 to check if it is out of bounds</param>
|
||||
/// <param name="index3">Index 3 to check if it is out of bounds</param>
|
||||
/// <typeparam name="T">Element type</typeparam>
|
||||
/// <seealso cref="ThrowIfInvalidIndex{T}" />
|
||||
public static void ThrowIfInvalidIndexes<T>(this IReadOnlyCollection<T> self, int index1, int index2, int index3)
|
||||
{
|
||||
self.ThrowIfInvalidIndex(index1);
|
||||
self.ThrowIfInvalidIndex(index2);
|
||||
self.ThrowIfInvalidIndex(index3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an exception if any of the given indexes is out of a <see cref="IReadOnlyCollection{T}" /> bounds.
|
||||
/// </summary>
|
||||
/// <param name="self">Collection</param>
|
||||
/// <param name="indexes">Indexes to check</param>
|
||||
/// <typeparam name="T">Element type</typeparam>
|
||||
public static void ThrowIfInvalidIndexes<T>(this IReadOnlyCollection<T> self, params int[] indexes)
|
||||
{
|
||||
foreach (int index in indexes)
|
||||
{
|
||||
self.ThrowIfInvalidIndex(index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits a string using <see cref="string.Split(char[])" /> and adds the result to the collection.
|
||||
/// </summary>
|
||||
/// <param name="self">Collection to add the split result to</param>
|
||||
/// <param name="toSplit">String to split</param>
|
||||
/// <param name="separator">
|
||||
/// Separator to use for splitting. This will be used to call <see cref="string.Split(char[])" />
|
||||
/// on <paramref name="toSplit" />
|
||||
/// </param>
|
||||
/// <returns>The result collection</returns>
|
||||
public static ICollection<string> SplitAddRange(this ICollection<string> self, string toSplit, char separator)
|
||||
{
|
||||
self.ThrowIfNull(nameof(self));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(toSplit))
|
||||
{
|
||||
return self;
|
||||
}
|
||||
|
||||
foreach (string s in toSplit.Split(separator))
|
||||
{
|
||||
self.Add(s.Trim());
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits a string using <see cref="string.Split(char[])" /> and sets the result in the collection.
|
||||
/// </summary>
|
||||
/// <param name="self">Collection to set the split result in</param>
|
||||
/// <param name="toSplit">String to split</param>
|
||||
/// <param name="separator">
|
||||
/// Separator to use for splitting. This will be used to call <see cref="string.Split(char[])" />
|
||||
/// on <paramref name="toSplit" />
|
||||
/// </param>
|
||||
/// <returns>The result collection</returns>
|
||||
public static ICollection<string> SplitSetRange(this ICollection<string> self, string toSplit, char separator)
|
||||
{
|
||||
self.ThrowIfNull(nameof(self));
|
||||
self.Clear();
|
||||
return self.SplitAddRange(toSplit, separator);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Collections/CollectionExt.cs.meta
vendored
Normal file
11
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Collections/CollectionExt.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2fbb5811e6ee84042ad95c9df2447cfd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
66
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Collections/DictionaryExt.cs
vendored
Normal file
66
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Collections/DictionaryExt.cs
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="DictionaryExt.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UltimateXR.Extensions.System.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="IDictionary{TKey,TValue}" /> and <see cref="Dictionary{TKey,TValue}" /> extensions.
|
||||
/// </summary>
|
||||
public static class DictionaryExt
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Adds all elements in another dictionary to the dictionary.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">Key type</typeparam>
|
||||
/// <typeparam name="TValue">Value type</typeparam>
|
||||
/// <param name="self">Destination dictionary</param>
|
||||
/// <param name="other">Source dictionary</param>
|
||||
/// <param name="overrideExistingKeys">Determines if duplicated keys must be overriden with other's values</param>
|
||||
public static void AddRange<TKey, TValue>(this IDictionary<TKey, TValue> self, IDictionary<TKey, TValue> other, bool overrideExistingKeys = false)
|
||||
{
|
||||
foreach (var kv in other)
|
||||
{
|
||||
if (!self.ContainsKey(kv.Key))
|
||||
{
|
||||
self.Add(kv.Key, kv.Value);
|
||||
}
|
||||
else if (overrideExistingKeys)
|
||||
{
|
||||
self[kv.Key] = kv.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a given value defined by a key in a dictionary. If the key is not found, it is added and the value is given
|
||||
/// the default value.
|
||||
/// </summary>
|
||||
/// <param name="self">Dictionary</param>
|
||||
/// <param name="key">Key to look for</param>
|
||||
/// <typeparam name="TKey">Key type</typeparam>
|
||||
/// <typeparam name="TValue">Value type</typeparam>
|
||||
/// <returns>
|
||||
/// Value in the dictionary. If the key doesn't exist it will be added and the return value will be the default
|
||||
/// value
|
||||
/// </returns>
|
||||
public static TValue GetOrAddValue<TKey, TValue>(this IDictionary<TKey, TValue> self, TKey key)
|
||||
where TValue : new()
|
||||
{
|
||||
if (!self.TryGetValue(key, out TValue value))
|
||||
{
|
||||
value = new TValue();
|
||||
self.Add(key, value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d0cdca585a749e69c9d098ba98c006d
|
||||
timeCreated: 1620807425
|
||||
318
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Collections/EnumerableExt.cs
vendored
Normal file
318
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Collections/EnumerableExt.cs
vendored
Normal file
@@ -0,0 +1,318 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="EnumerableExt.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using UltimateXR.Core;
|
||||
using UltimateXR.Core.Settings;
|
||||
using UnityEngine;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
namespace UltimateXR.Extensions.System.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="IEnumerable{T}" /> extensions.
|
||||
/// </summary>
|
||||
public static class EnumerableExt
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Compares two IEnumerable for equality, considering the order of elements.
|
||||
/// For dictionaries, compares key-value pairs regardless of their order.
|
||||
/// </summary>
|
||||
/// <param name="enumerableA">The first collection to compare</param>
|
||||
/// <param name="enumerableB">The second collection to compare</param>
|
||||
/// <returns>True if the collections are equal; otherwise, false</returns>
|
||||
public static bool ContentEqual(IEnumerable enumerableA, IEnumerable enumerableB)
|
||||
{
|
||||
return ContentEqual(enumerableA, enumerableB, (a, b) => a.ValuesEqual(b));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two IEnumerable for equality, considering the order of elements.
|
||||
/// For dictionaries, compares key-value pairs regardless of their order.
|
||||
/// Values are compared using a floating point precision threshold used by
|
||||
/// <see cref="ObjectExt.ValuesEqual(object,object,float)" />.
|
||||
/// </summary>
|
||||
/// <param name="enumerableA">The first collection to compare</param>
|
||||
/// <param name="enumerableB">The second collection to compare</param>
|
||||
/// <param name="precisionThreshold">
|
||||
/// The precision threshold for float comparisons in types supported by
|
||||
/// <see cref="ObjectExt.ValuesEqual(object,object,float)" />.
|
||||
/// </param>
|
||||
/// <returns>True if the collections are equal; otherwise, false</returns>
|
||||
public static bool ContentEqual(IEnumerable enumerableA, IEnumerable enumerableB, float precisionThreshold)
|
||||
{
|
||||
return ContentEqual(enumerableA, enumerableB, (a, b) => a.ValuesEqual(b, precisionThreshold));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random element from the collection.
|
||||
/// </summary>
|
||||
/// <param name="list">Collection to get the random element from</param>
|
||||
/// <typeparam name="TIn">Element type</typeparam>
|
||||
/// <returns>Random element from the collection</returns>
|
||||
/// <remarks>
|
||||
/// Uses Unity's random number generator (<see cref="UnityEngine.Random.Range(int,int)" />).
|
||||
/// </remarks>
|
||||
public static TIn RandomElement<TIn>(this IEnumerable<TIn> list)
|
||||
{
|
||||
return list.Any() ? list.ElementAt(Random.Range(0, list.Count())) : default(TIn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies an <see cref="Action" /> on all elements in a collection.
|
||||
/// </summary>
|
||||
/// <param name="list">Elements to apply the action on</param>
|
||||
/// <param name="action">Action to apply</param>
|
||||
/// <typeparam name="TIn">Element type</typeparam>
|
||||
/// <exception cref="ArgumentException">Any of the parameters was null</exception>
|
||||
public static void ForEach<TIn>(this IEnumerable<TIn> list, Action<TIn> action)
|
||||
{
|
||||
if (list == null)
|
||||
{
|
||||
throw new ArgumentException("Argument cannot be null.", nameof(list));
|
||||
}
|
||||
|
||||
if (action == null)
|
||||
{
|
||||
throw new ArgumentException("Argument cannot be null.", nameof(action));
|
||||
}
|
||||
|
||||
foreach (TIn value in list)
|
||||
{
|
||||
action(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously applies a function on all elements in a collection.
|
||||
/// </summary>
|
||||
/// <param name="list">Elements to apply the function on</param>
|
||||
/// <param name="function">Function to apply</param>
|
||||
/// <typeparam name="TIn">Element type</typeparam>
|
||||
/// <returns>An awaitable task wrapping the Task.WhenAll applying the function on all elements in a collection</returns>
|
||||
public static Task ForEachAsync<TIn>(this IEnumerable<TIn> list, Func<TIn, Task> function)
|
||||
{
|
||||
return Task.WhenAll(list.Select(function));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously applies a function to all elements in a collection.
|
||||
/// </summary>
|
||||
/// <param name="list">Elements to apply the function on</param>
|
||||
/// <param name="function">Function to apply</param>
|
||||
/// <typeparam name="TIn">Element type</typeparam>
|
||||
/// <typeparam name="TOut">Function return type</typeparam>
|
||||
/// <returns>An awaitable task wrapping the Task.WhenAll applying the function on all elements in a collection</returns>
|
||||
public static Task<TOut[]> ForEachAsync<TIn, TOut>(this IEnumerable<TIn> list, Func<TIn, Task<TOut>> function)
|
||||
{
|
||||
return Task.WhenAll(list.Select(function));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously applies an action on all elements in a collection.
|
||||
/// </summary>
|
||||
/// <param name="list">Elements to apply the action on</param>
|
||||
/// <param name="action">Action to apply</param>
|
||||
/// <typeparam name="TIn">Element type</typeparam>
|
||||
/// <returns>Task wrapping the Task.WhenAll applying the action on all elements in a collection</returns>
|
||||
public static Task ForEachThreaded<TIn>(this IEnumerable<TIn> list, Action<TIn> action)
|
||||
{
|
||||
void OnFaulted(Task runTask, int itemIndex)
|
||||
{
|
||||
if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Warnings)
|
||||
{
|
||||
Debug.LogWarning($"{UxrConstants.CoreModule} ForEachThreaded::Item[{itemIndex}] faulted (see reason below):");
|
||||
}
|
||||
|
||||
if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Errors)
|
||||
{
|
||||
Debug.LogException(runTask.Exception);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.WhenAll(list.Select((item, index) => Task.Run(() => action(item)).ContinueWith(runTask => OnFaulted(runTask, index), TaskContinuationOptions.OnlyOnFaulted)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously applies a function on all elements in a collection.
|
||||
/// </summary>
|
||||
/// <param name="list">Elements to apply the function on</param>
|
||||
/// <param name="function">Function to apply</param>
|
||||
/// <typeparam name="TIn">Element type</typeparam>
|
||||
/// <typeparam name="TOut">Function return type</typeparam>
|
||||
/// <returns>Task wrapping the Task.WhenAll applying the function on all elements in a collection</returns>
|
||||
public static Task<TOut[]> ForEachThreaded<TIn, TOut>(this IEnumerable<TIn> list, Func<TIn, TOut> function)
|
||||
{
|
||||
TOut OnFaulted(Task<TOut> t, int itemIndex)
|
||||
{
|
||||
if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Warnings)
|
||||
{
|
||||
Debug.LogWarning($"{UxrConstants.CoreModule} ForEachThreaded::Item[{itemIndex}] faulted (see reason below):");
|
||||
}
|
||||
|
||||
if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Errors)
|
||||
{
|
||||
Debug.LogException(t.Exception);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
return Task.WhenAll(list.Select((item, index) => Task.Run(() => function(item)).ContinueWith(t => OnFaulted(t, index), TaskContinuationOptions.OnlyOnFaulted)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the maximal element of the given sequence, based on the given projection.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This overload uses the default comparer for the projected type. This operator uses deferred execution. The results
|
||||
/// are evaluated and cached on first use to returned sequence.
|
||||
/// </remarks>
|
||||
/// <typeparam name="TSource">Type of the source sequence</typeparam>
|
||||
/// <typeparam name="TKey">Type of the projected element</typeparam>
|
||||
/// <param name="source">Source sequence</param>
|
||||
/// <param name="selector">Selector to use to pick the results to compare</param>
|
||||
/// <returns>The maximal element, according to the projection.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="source" /> or <paramref name="selector" /> is null</exception>
|
||||
public static TSource MaxBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector)
|
||||
{
|
||||
return source.MaxBy(selector, null!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the maximal element of the given sequence, based on the given projection and the specified comparer for
|
||||
/// projected values.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This operator uses deferred execution. The results are evaluated and cached on first use to returned sequence.
|
||||
/// </remarks>
|
||||
/// <typeparam name="TSource">Type of the source sequence</typeparam>
|
||||
/// <typeparam name="TKey">Type of the projected element</typeparam>
|
||||
/// <param name="source">Source sequence</param>
|
||||
/// <param name="selector">Selector to use to pick the results to compare</param>
|
||||
/// <param name="comparer">Comparer to use to compare projected values</param>
|
||||
/// <returns>The maximal element, according to the projection.</returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <paramref name="source" />, <paramref name="selector" /> or <paramref name="comparer" /> is null
|
||||
/// </exception>
|
||||
/// <exception cref="T:System.Exception">A delegate callback throws an exception.</exception>
|
||||
public static TSource MaxBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector, IComparer<TKey> comparer)
|
||||
{
|
||||
source.ThrowIfNull(nameof(source));
|
||||
selector.ThrowIfNull(nameof(selector));
|
||||
|
||||
TSource result = default;
|
||||
TKey keyMax = default;
|
||||
comparer ??= Comparer<TKey>.Default;
|
||||
bool isFirst = true;
|
||||
|
||||
foreach (TSource s in source)
|
||||
{
|
||||
TKey key = selector(s);
|
||||
|
||||
if (isFirst)
|
||||
{
|
||||
result = s;
|
||||
keyMax = key;
|
||||
isFirst = false;
|
||||
}
|
||||
else if (comparer.Compare(key, keyMax) > 0)
|
||||
{
|
||||
result = s;
|
||||
keyMax = key;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits a list of strings using CamelCase.
|
||||
/// </summary>
|
||||
/// <param name="strings">List of strings</param>
|
||||
/// <returns>List of strings with added spacing</returns>
|
||||
public static IEnumerable<string> SplitCamelCase(this IEnumerable<string> strings)
|
||||
{
|
||||
foreach (string element in strings)
|
||||
{
|
||||
yield return element.SplitCamelCase();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Compares two IEnumerable for equality, considering the order of elements.
|
||||
/// For dictionaries, compares key-value pairs regardless of their order.
|
||||
/// </summary>
|
||||
/// <param name="enumerableA">The first collection to compare</param>
|
||||
/// <param name="enumerableB">The second collection to compare</param>
|
||||
/// <param name="comparer">Comparison function</param>
|
||||
/// <returns>True if the collections are equal; otherwise, false</returns>
|
||||
private static bool ContentEqual(IEnumerable enumerableA, IEnumerable enumerableB, Func<object, object, bool> comparer)
|
||||
{
|
||||
// If the collections are dictionaries, compare key-value pairs
|
||||
if (enumerableA is IDictionary dictionaryA && enumerableB is IDictionary dictionaryB)
|
||||
{
|
||||
// Ensure both dictionaries have the same number of elements
|
||||
if (dictionaryA.Count != dictionaryB.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compare key-value pairs regardless of order
|
||||
foreach (DictionaryEntry entryA in dictionaryA)
|
||||
{
|
||||
if (!dictionaryB.Contains(entryA.Key) || !comparer(entryA.Value, dictionaryB[entryA.Key]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the collections are lists, do a quick test to check if they have different number of elements
|
||||
if (enumerableA is IList listA && enumerableB is IList listB)
|
||||
{
|
||||
if (listA.Count != listB.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If the collections are HashSets, compare elements using SetEquals
|
||||
if (enumerableA is HashSet<object> hashSetA && enumerableB is HashSet<object> hashSetB)
|
||||
{
|
||||
return hashSetA.SetEquals(hashSetB);
|
||||
}
|
||||
|
||||
// For non-dictionary, non-HashSet collections, compare elements
|
||||
IEnumerator enumeratorA = enumerableA.GetEnumerator();
|
||||
IEnumerator enumeratorB = enumerableB.GetEnumerator();
|
||||
|
||||
while (enumeratorA.MoveNext())
|
||||
{
|
||||
if (!enumeratorB.MoveNext() || !comparer(enumeratorA.Current, enumeratorB.Current))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure both collections have the same number of elements
|
||||
return !enumeratorB.MoveNext();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aded6c93d8bb43a799907d1f47eca9a4
|
||||
timeCreated: 1586946987
|
||||
161
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Collections/ListExt.cs
vendored
Normal file
161
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Collections/ListExt.cs
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="ListExt.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Extensions.System.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="List{T}" /> extensions.
|
||||
/// </summary>
|
||||
public static class ListExt
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of a given item in a list.
|
||||
/// </summary>
|
||||
/// <param name="self">List where to look for the item</param>
|
||||
/// <param name="item">Item to look for</param>
|
||||
/// <typeparam name="T">Element type</typeparam>
|
||||
/// <returns>Element index or -1 if not found</returns>
|
||||
/// <remarks>Equals() is used for comparison</remarks>
|
||||
public static int IndexOf<T>(this IReadOnlyList<T> self, T item)
|
||||
{
|
||||
for (int i = 0; i < self.Count; ++i)
|
||||
{
|
||||
if (Equals(self[i], item))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the list in reverse order.
|
||||
/// </summary>
|
||||
/// <param name="self">List in reverse order</param>
|
||||
/// <typeparam name="T">Element type</typeparam>
|
||||
/// <returns>List in reverse order</returns>
|
||||
public static IEnumerable<T> Reversed<T>(this IList<T> self)
|
||||
{
|
||||
for (int i = self.Count - 1; i >= 0; --i)
|
||||
{
|
||||
yield return self[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random element from the list.
|
||||
/// </summary>
|
||||
/// <param name="self">List to get the random element from</param>
|
||||
/// <typeparam name="T">Element type</typeparam>
|
||||
/// <returns>Random element from the list</returns>
|
||||
/// <remarks>
|
||||
/// Uses Unity's random number generator (<see cref="Random.Range(int,int)" />).
|
||||
/// </remarks>
|
||||
public static T RandomElement<T>(this IReadOnlyList<T> self)
|
||||
{
|
||||
return self.Count > 0 ? self[Random.Range(0, self.Count)] : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a list of random indices without repetition in a given range.
|
||||
/// </summary>
|
||||
/// <param name="minInclusive">The minimum index value (inclusive)</param>
|
||||
/// <param name="maxExclusive">The maximum value, which will not be included</param>
|
||||
/// <param name="count"></param>
|
||||
/// <returns></returns>
|
||||
public static List<int> GenerateRandomIndicesWithoutRepetition(int minInclusive, int maxExclusive, int count)
|
||||
{
|
||||
List<int> newList = new List<int>();
|
||||
|
||||
for (int i = minInclusive; i < maxExclusive; ++i)
|
||||
{
|
||||
newList.Add(i);
|
||||
}
|
||||
|
||||
return newList.RandomElementsWithoutRepetition(count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list with n random elements from a list without repetition.
|
||||
/// </summary>
|
||||
/// <param name="self">List to get the random elements from</param>
|
||||
/// <param name="count">Number of random elements to get</param>
|
||||
/// <typeparam name="T">Element type</typeparam>
|
||||
/// <returns>
|
||||
/// List with random elements. If <paramref name="count" /> is larger than the list, the result list will be as
|
||||
/// long as the input list.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// Uses Unity's random number generator (<see cref="Random.Range(int,int)" />).
|
||||
/// </remarks>
|
||||
public static List<T> RandomElementsWithoutRepetition<T>(this IReadOnlyList<T> self, int count)
|
||||
{
|
||||
List<T> candidates = new List<T>(self);
|
||||
List<T> randomElements = new List<T>();
|
||||
|
||||
for (int i = 0; i < count && candidates.Count > 0; ++i)
|
||||
{
|
||||
int randomIndex = Random.Range(0, candidates.Count);
|
||||
randomElements.Add(candidates[randomIndex]);
|
||||
candidates.RemoveAt(randomIndex);
|
||||
}
|
||||
|
||||
return randomElements;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list with n random elements from a list without repetition. An additional list can be provided to exclude
|
||||
/// elements from appearing in the results.
|
||||
/// </summary>
|
||||
/// <param name="self">List to get the random elements from</param>
|
||||
/// <param name="listToExclude">List of elements to exclude from the results</param>
|
||||
/// <param name="count">Number of random elements to get</param>
|
||||
/// <typeparam name="T">Element type</typeparam>
|
||||
/// <returns>
|
||||
/// List with random elements. If <paramref name="count" /> is larger than the list, the result list will be as
|
||||
/// long as the input list minus the excluded elements.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// Uses Unity's random number generator (<see cref="Random.Range(int,int)" />).
|
||||
/// </remarks>
|
||||
public static List<T> RandomElementsWithoutRepetitionExcept<T>(this IReadOnlyList<T> self, IReadOnlyList<T> listToExclude, int count)
|
||||
{
|
||||
List<T> candidates = new List<T>(self.Where(p => !listToExclude.Any(p2 => Equals(p2, p))));
|
||||
List<T> randomElements = new List<T>();
|
||||
|
||||
for (int i = 0; i < count && candidates.Count > 0; ++i)
|
||||
{
|
||||
int randomIndex = Random.Range(0, candidates.Count);
|
||||
randomElements.Add(candidates[randomIndex]);
|
||||
candidates.RemoveAt(randomIndex);
|
||||
}
|
||||
|
||||
return randomElements;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list with the input list elements shuffled.
|
||||
/// </summary>
|
||||
/// <param name="self">List to get the random elements from</param>
|
||||
/// <typeparam name="T">Element type</typeparam>
|
||||
/// <returns>List with shuffled elements.</returns>
|
||||
/// <remarks>
|
||||
/// Uses Unity's random number generator (<see cref="Random.Range(int,int)" />).
|
||||
/// </remarks>
|
||||
public static List<T> Shuffled<T>(this IReadOnlyList<T> self)
|
||||
{
|
||||
return self.RandomElementsWithoutRepetition(self.Count);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Collections/ListExt.cs.meta
vendored
Normal file
11
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Collections/ListExt.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0fe3095d1ef7a9c428f15cc9a190b4b2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
37
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/EnumExt.cs
vendored
Normal file
37
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/EnumExt.cs
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="EnumExt.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UltimateXR.Extensions.System
|
||||
{
|
||||
/// <summary>
|
||||
/// Enum extensions.
|
||||
/// </summary>
|
||||
public static class EnumExt
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates all flags that are set in the enum value.
|
||||
/// </summary>
|
||||
/// <param name="self"></param>
|
||||
/// <param name="includeZero">Whether to include the 0 in the list</param>
|
||||
/// <returns>List of flags set in the enum value</returns>
|
||||
public static IEnumerable<T> GetFlags<T>(this T self, bool includeZero = false) where T : Enum
|
||||
{
|
||||
foreach (T value in Enum.GetValues(self.GetType()))
|
||||
{
|
||||
if (self.HasFlag(value) && !(!includeZero && Convert.ToInt32(value) == 0))
|
||||
{
|
||||
yield return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/EnumExt.cs.meta
vendored
Normal file
11
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/EnumExt.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c46b7f127b45c614c9e7be73348c6e86
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
63
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/GuidExt.cs
vendored
Normal file
63
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/GuidExt.cs
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="GuidExt.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace UltimateXR.Extensions.System
|
||||
{
|
||||
public static class GuidExt
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Throws an exception if the object has the default Guid.
|
||||
/// </summary>
|
||||
/// <param name="self">Guid to check</param>
|
||||
/// <param name="paramName">Parameter name, used as argument for the exception message or null to not use it</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown if the object is the default Guid (default or <see cref="Guid.Empty" />)</exception>
|
||||
public static void ThrowIfDefault(this Guid self, string paramName)
|
||||
{
|
||||
if (self == Guid.Empty)
|
||||
{
|
||||
if (paramName == null)
|
||||
{
|
||||
throw new Exception("Guid cannot be empty");
|
||||
}
|
||||
|
||||
throw new Exception($"Guid is empty for parameter {paramName}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combines two Guid instances to create a new unique Guid.
|
||||
/// </summary>
|
||||
/// <param name="guid1">The first Guid to combine</param>
|
||||
/// <param name="guid2">The second Guid to combine</param>
|
||||
/// <returns>A new Guid that is a combination of the input Guids</returns>
|
||||
public static Guid Combine(Guid guid1, Guid guid2)
|
||||
{
|
||||
byte[] bytes1 = guid1.ToByteArray();
|
||||
byte[] bytes2 = guid2.ToByteArray();
|
||||
|
||||
// Concatenate the byte arrays
|
||||
byte[] combinedBytes = new byte[bytes1.Length + bytes2.Length];
|
||||
Buffer.BlockCopy(bytes1, 0, combinedBytes, 0, bytes1.Length);
|
||||
Buffer.BlockCopy(bytes2, 0, combinedBytes, bytes1.Length, bytes2.Length);
|
||||
|
||||
// Use SHA-256 hash function to generate a unique hash
|
||||
using SHA256 sha256 = SHA256.Create();
|
||||
byte[] hashBytes = sha256.ComputeHash(combinedBytes);
|
||||
|
||||
// Take the first 16 bytes of the hash to create a new Guid
|
||||
byte[] guidBytes = new byte[16];
|
||||
Buffer.BlockCopy(hashBytes, 0, guidBytes, 0, guidBytes.Length);
|
||||
|
||||
return new Guid(guidBytes);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/GuidExt.cs.meta
vendored
Normal file
11
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/GuidExt.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f2a483ee55424a44b6ee467f1a919f9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
3
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/IO.meta
vendored
Normal file
3
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/IO.meta
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1ccdc2da9fc4817855b0b71c1136de7
|
||||
timeCreated: 1620807376
|
||||
1162
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/IO/BinaryReaderExt.cs
vendored
Normal file
1162
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/IO/BinaryReaderExt.cs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
11
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/IO/BinaryReaderExt.cs.meta
vendored
Normal file
11
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/IO/BinaryReaderExt.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9b4b01a8c9bab94e9b674feafc6b02e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1042
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/IO/BinaryWriterExt.cs
vendored
Normal file
1042
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/IO/BinaryWriterExt.cs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
11
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/IO/BinaryWriterExt.cs.meta
vendored
Normal file
11
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/IO/BinaryWriterExt.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a0855abdfb7ddf945ab79e51a5024864
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
309
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/IO/FileExt.cs
vendored
Normal file
309
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/IO/FileExt.cs
vendored
Normal file
@@ -0,0 +1,309 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="FileExt.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using UltimateXR.Exceptions;
|
||||
using UltimateXR.Extensions.Unity.IO;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace UltimateXR.Extensions.System.IO
|
||||
{
|
||||
/// <summary>
|
||||
/// File extensions.
|
||||
/// </summary>
|
||||
public static class FileExt
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Reads bytes from a file asynchronously.
|
||||
/// Multiple file locations are supported:
|
||||
/// <list type="bullet">
|
||||
/// <item>Files in <see cref="Application.streamingAssetsPath" /></item>
|
||||
/// <item>Files in an http:// location</item>
|
||||
/// <item>Files in a file:// location</item>
|
||||
/// </list>
|
||||
/// All other Uris will be considered file paths and the file:// location will be added.
|
||||
/// </summary>
|
||||
/// <param name="uri">File full path to be opened for reading</param>
|
||||
/// <param name="ct">
|
||||
/// Optional cancellation token, to be able to cancel the asynchronous operation
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Bytes read
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// <see cref="UnityWebRequest.Get(string)">UnityWebRequest.Get()</see> is used internally to perform the actual
|
||||
/// reading
|
||||
/// </remarks>
|
||||
/// <exception cref="OperationCanceledException">
|
||||
/// Task canceled using <paramref name="ct" />
|
||||
/// </exception>
|
||||
/// <exception cref="FileNotFoundException">
|
||||
/// The file specified in <paramref name="uri" /> was not found.
|
||||
/// </exception>
|
||||
/// <exception cref="NotSupportedException">
|
||||
/// <paramref name="uri" /> is in an invalid format.
|
||||
/// </exception>
|
||||
/// <exception cref="IOException">
|
||||
/// An I/O error occurred while opening the file.
|
||||
/// </exception>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// The stream is currently in use by a previous read operation.
|
||||
/// </exception>
|
||||
public static async Task<byte[]> Read(string uri, CancellationToken ct = default)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
uri.ThrowIfNullOrWhitespace(nameof(uri));
|
||||
|
||||
if (UnityWebRequestExt.IsUwrUri(uri))
|
||||
{
|
||||
try
|
||||
{
|
||||
return await UnityWebRequestExt.ReadBytesAsync(uri, ct);
|
||||
}
|
||||
catch (UwrException e)
|
||||
{
|
||||
throw new FileNotFoundException(e.Message, uri, e);
|
||||
}
|
||||
}
|
||||
|
||||
List<byte> bytes = new List<byte>();
|
||||
byte[] buffer = new byte[0x1000];
|
||||
using (var fs = new FileStream(uri, FileMode.Open, FileAccess.Read, FileShare.Read, buffer.Length, true))
|
||||
{
|
||||
while (await fs.ReadAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false) != 0)
|
||||
{
|
||||
bytes.AddRange(buffer);
|
||||
}
|
||||
}
|
||||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads text from a file asynchronously.
|
||||
/// Multiple file locations are supported:
|
||||
/// <list type="bullet">
|
||||
/// <item>Files in <see cref="Application.streamingAssetsPath" /></item>
|
||||
/// <item>Files in an http:// location</item>
|
||||
/// <item>Files in a file:// location</item>
|
||||
/// </list>
|
||||
/// All other Uris will be considered file paths and the file:// location will be added.
|
||||
/// </summary>
|
||||
/// <param name="uri">File location</param>
|
||||
/// <param name="encoding">Optional file encoding</param>
|
||||
/// <param name="ct">Optional cancellation token, to cancel the asynchronous operation</param>
|
||||
/// <returns>A pair describing a boolean success value and the text read</returns>
|
||||
/// <remarks>
|
||||
/// <see cref="UnityWebRequest.Get(string)">UnityWebRequest.Get()</see> is used internally to perform the actual
|
||||
/// reading
|
||||
/// </remarks>
|
||||
public static async Task<(bool success, string text)> TryReadText(string uri, Encoding encoding = default, CancellationToken ct = default)
|
||||
{
|
||||
(bool success, string text) result;
|
||||
try
|
||||
{
|
||||
result.text = await ReadText(uri, encoding, ct).ConfigureAwait(false);
|
||||
result.success = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
result.text = null;
|
||||
result.success = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads text from a file asynchronously.
|
||||
/// Multiple file locations are supported:
|
||||
/// <list type="bullet">
|
||||
/// <item>Files in <see cref="Application.streamingAssetsPath" /></item>
|
||||
/// <item>Files in an http:// location</item>
|
||||
/// <item>Files in a file:// location</item>
|
||||
/// </list>
|
||||
/// All other Uris will be considered file paths and the file:// location will be added.
|
||||
/// </summary>
|
||||
/// <param name="uri">File full path to be opened for reading</param>
|
||||
/// <param name="encoding">Optional file encoding</param>
|
||||
/// <param name="ct">Optional cancellation token, to cancel the asynchronous operation</param>
|
||||
/// <returns>Text content of the file or <see langword="null" /> if not found.</returns>
|
||||
/// <remarks>
|
||||
/// <see cref="UnityWebRequest.Get(string)">UnityWebRequest.Get()</see> is used internally to perform the actual
|
||||
/// reading
|
||||
/// </remarks>
|
||||
/// <exception cref="OperationCanceledException">
|
||||
/// Task canceled using <paramref name="ct" />
|
||||
/// </exception>
|
||||
/// <exception cref="NotSupportedException">
|
||||
/// <paramref name="uri" /> is in an invalid format.
|
||||
/// </exception>
|
||||
/// <exception cref="IOException">
|
||||
/// An I/O error occurred while opening the file.
|
||||
/// </exception>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// The stream is currently in use by a previous read operation.
|
||||
/// </exception>
|
||||
public static async Task<string> ReadText(string uri, Encoding encoding = default, CancellationToken ct = default)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
uri.ThrowIfNullOrWhitespace(nameof(uri));
|
||||
encoding ??= DefaultEncoding;
|
||||
|
||||
if (UnityWebRequestExt.IsUwrUri(uri))
|
||||
{
|
||||
try
|
||||
{
|
||||
return await UnityWebRequestExt.ReadTextAsync(uri, ct);
|
||||
}
|
||||
catch (UwrException e)
|
||||
{
|
||||
throw new FileNotFoundException(e.Message, uri, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!File.Exists(uri))
|
||||
{
|
||||
throw new FileNotFoundException("File does not exist", uri);
|
||||
}
|
||||
|
||||
using StreamReader sr = new StreamReader(uri, encoding, true);
|
||||
return await sr.ReadToEndAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously writes an <see cref="Array" /> of <see cref="byte" /> to a file at <paramref name="path" />.
|
||||
/// </summary>
|
||||
/// <param name="bytes">File content as <see cref="Array" /> of <see cref="byte" /></param>
|
||||
/// <param name="path">File full path to be opened for writing</param>
|
||||
/// <param name="ct">Optional cancellation token, to cancel the asynchronous operation</param>
|
||||
/// <returns>An awaitable writing <see cref="Task" /></returns>
|
||||
/// <exception cref="IOException">
|
||||
/// An I/O error occurred while creating the file.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <paramref name="path" /> is a zero-length string, contains only white space, or
|
||||
/// contains one or more invalid characters. You can query for invalid characters by using the
|
||||
/// <see cref="Path.GetInvalidPathChars" /> method.
|
||||
/// </exception>
|
||||
/// <exception cref="PathTooLongException">
|
||||
/// The <paramref name="path" /> parameter is longer than the system-defined maximum length.
|
||||
/// </exception>
|
||||
/// <exception cref="UnauthorizedAccessException">
|
||||
/// The caller does not have the required permission.
|
||||
/// </exception>
|
||||
/// <exception cref="OperationCanceledException">
|
||||
/// Task canceled using <paramref name="ct" />
|
||||
/// </exception>
|
||||
public static async Task Write(byte[] bytes, string path, CancellationToken ct = default)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
path.ThrowIfNullOrWhitespace(nameof(path));
|
||||
|
||||
using var stream = new MemoryStream(bytes);
|
||||
await Write(path, stream, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously writes the content of an <paramref name="sourceStream" /> to a file at <paramref name="path" />.
|
||||
/// </summary>
|
||||
/// <param name="path">File full path to be opened for writing</param>
|
||||
/// <param name="sourceStream"><see cref="Stream" /> to be written into a file.</param>
|
||||
/// <param name="ct">Optional cancellation token, to cancel the asynchronous operation</param>
|
||||
/// <returns>An awaitable writing <see cref="Task" /></returns>
|
||||
/// <exception cref="IOException">
|
||||
/// An I/O error occurred while creating the file.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <paramref name="path" /> is a zero-length string, contains only white space, or
|
||||
/// contains one or more invalid characters. You can query for invalid characters by using the
|
||||
/// <see cref="Path.GetInvalidPathChars" /> method.
|
||||
/// </exception>
|
||||
/// <exception cref="PathTooLongException">
|
||||
/// The <paramref name="path" /> parameter is longer than the system-defined maximum length.
|
||||
/// </exception>
|
||||
/// <exception cref="NotSupportedException">
|
||||
/// <paramref name="sourceStream" /> does not support reading, or the destination stream does not support writing.
|
||||
/// </exception>
|
||||
/// <exception cref="UnauthorizedAccessException">
|
||||
/// The caller does not have the required permission.
|
||||
/// </exception>
|
||||
/// <exception cref="OperationCanceledException">
|
||||
/// Task canceled using <paramref name="ct" />
|
||||
/// </exception>
|
||||
public static Task Write(string path, Stream sourceStream, CancellationToken ct = default)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
path.ThrowIfNullOrWhitespace(nameof(path));
|
||||
sourceStream.ThrowIfNull(nameof(sourceStream));
|
||||
|
||||
string dirName = Path.GetDirectoryName(path);
|
||||
const int bufferSize = 81920;
|
||||
|
||||
async Task WriteInternal()
|
||||
{
|
||||
if (dirName != null)
|
||||
{
|
||||
Directory.CreateDirectory(dirName);
|
||||
}
|
||||
|
||||
using FileStream outputStream = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.None, bufferSize, FileOptions.Asynchronous);
|
||||
await sourceStream.CopyToAsync(outputStream, bufferSize, ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Creating the new file has a performance impact that requires a new thread.
|
||||
return Task.Run(WriteInternal, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously writes text to a file location.
|
||||
/// </summary>
|
||||
/// <param name="path">File full path to be opened for writing</param>
|
||||
/// <param name="text">Text to write</param>
|
||||
/// <param name="encoding">Optional file encoding</param>
|
||||
/// <param name="append">Optional boolean telling whether to append or override. Default behaviour is to override.</param>
|
||||
/// <param name="ct">Optional cancellation token, to cancel the asynchronous operation</param>
|
||||
/// <returns>Awaitable task</returns>
|
||||
/// <exception cref="OperationCanceledException">
|
||||
/// Task canceled using <paramref name="ct" />
|
||||
/// </exception>
|
||||
public static Task WriteText(string path, string text, Encoding encoding = default, bool append = false, CancellationToken ct = default)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
path.ThrowIfNullOrWhitespace(nameof(path));
|
||||
|
||||
encoding ??= DefaultEncoding;
|
||||
string dirName = Path.GetDirectoryName(path);
|
||||
|
||||
async Task WriteInternal()
|
||||
{
|
||||
if (dirName != null)
|
||||
{
|
||||
Directory.CreateDirectory(dirName);
|
||||
}
|
||||
|
||||
using StreamWriter sw = new StreamWriter(path, append, encoding);
|
||||
await sw.WriteAsync(text).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Creating the new file has a performance impact that requires a new thread.
|
||||
return Task.Run(WriteInternal, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Types & Data
|
||||
|
||||
private static readonly Encoding DefaultEncoding = new UTF8Encoding(false);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
3
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/IO/FileExt.cs.meta
vendored
Normal file
3
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/IO/FileExt.cs.meta
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7f1327e2dd4ce0241b881966c84b67a6
|
||||
timeCreated: 1620127914
|
||||
124
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/IO/PathExt.cs
vendored
Normal file
124
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/IO/PathExt.cs
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="PathExt.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UltimateXR.Core;
|
||||
using UltimateXR.Core.Settings;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Extensions.System.IO
|
||||
{
|
||||
public static class PathExt
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Like .NET's <see cref="Path.Combine(string,string)" /> but addressing some issues discussed in
|
||||
/// https://www.davidboike.dev/2020/06/path-combine-isnt-as-cross-platform-as-you-think-it-is/
|
||||
/// </summary>
|
||||
/// <param name="basePath">Base path</param>
|
||||
/// <param name="additional">Additional segments or multi-segment paths</param>
|
||||
/// <returns>Path result of combining the base path and the additional segments or multi-segment paths</returns>
|
||||
public static string Combine(string basePath, params string[] additional)
|
||||
{
|
||||
string[][] splits = additional.Select(s => s.Split(PathSplitCharacters)).ToArray();
|
||||
int totalLength = splits.Sum(arr => arr.Length);
|
||||
string[] segments = new string[totalLength + 1];
|
||||
|
||||
segments[0] = basePath;
|
||||
var i = 0;
|
||||
|
||||
foreach (string[] split in splits)
|
||||
{
|
||||
foreach (string value in split)
|
||||
{
|
||||
i++;
|
||||
segments[i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return Path.Combine(segments);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes a path or sub-path so that any wrong directory separator char is fixed for the current platform.
|
||||
/// </summary>
|
||||
/// <param name="multiSegment">pathOrSubPath</param>
|
||||
/// <returns>Normalized path</returns>
|
||||
public static string Normalize(string pathOrSubPath)
|
||||
{
|
||||
if (Path.IsPathFullyQualified(pathOrSubPath))
|
||||
{
|
||||
return Path.GetFullPath(new Uri(pathOrSubPath).LocalPath).TrimEnd(PathSplitCharacters);
|
||||
}
|
||||
|
||||
foreach (char separator in PathSplitCharacters.Where(c => c != Path.DirectorySeparatorChar))
|
||||
{
|
||||
pathOrSubPath = pathOrSubPath.Replace(separator, Path.DirectorySeparatorChar);
|
||||
}
|
||||
|
||||
return pathOrSubPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a path is a child of another path.
|
||||
/// Adapted from https://stackoverflow.com/questions/8091829/how-to-check-if-one-path-is-a-child-of-another-path
|
||||
/// </summary>
|
||||
/// <param name="candidate">Path candidate</param>
|
||||
/// <param name="other">Path to check against</param>
|
||||
/// <param name="canBeSame">Whether to also consider the same directory as valid</param>
|
||||
/// <returns>Whether the path is child of the parent path</returns>
|
||||
public static bool IsSubDirectoryOf(string candidate, string other, bool canBeSame = true)
|
||||
{
|
||||
var isChild = false;
|
||||
try
|
||||
{
|
||||
// Some initial corrections to avoid false negatives:
|
||||
|
||||
var candidateInfo = new DirectoryInfo(candidate.Replace(@"\", @"/").TrimEnd('/'));
|
||||
var otherInfo = new DirectoryInfo(other.Replace(@"\", @"/").TrimEnd('/'));
|
||||
|
||||
// Check if same directory
|
||||
|
||||
if (canBeSame && string.Compare(candidateInfo.FullName, otherInfo.FullName, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Start traversing upwards
|
||||
|
||||
while (candidateInfo.Parent != null)
|
||||
{
|
||||
if (string.Equals(candidateInfo.Parent.FullName, otherInfo.FullName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
isChild = true;
|
||||
break;
|
||||
}
|
||||
|
||||
candidateInfo = candidateInfo.Parent;
|
||||
}
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Errors)
|
||||
{
|
||||
Debug.LogError($"{UxrConstants.CoreModule} Unable to check directories {candidate} and {other}: {error}");
|
||||
}
|
||||
}
|
||||
|
||||
return isChild;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Types & Data
|
||||
|
||||
private static readonly char[] PathSplitCharacters = { '/', '\\' };
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/IO/PathExt.cs.meta
vendored
Normal file
11
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/IO/PathExt.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1e021f51e7aad3b4889b7e7ff42776c8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Math.meta
vendored
Normal file
8
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Math.meta
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2cc0d2d42fb0afe48b20d9f36bd35cbd
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
167
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Math/FloatExt.cs
vendored
Normal file
167
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Math/FloatExt.cs
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="FloatExt.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using UltimateXR.Core;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Extensions.System.Math
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="float" /> extensions.
|
||||
/// </summary>
|
||||
public static class FloatExt
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Compares two <c>float</c> values for equality with a specified precision threshold.
|
||||
/// </summary>
|
||||
/// <param name="a">The first <c>float</c> to compare</param>
|
||||
/// <param name="b">The second <c>float</c> to compare</param>
|
||||
/// <param name="precisionThreshold">
|
||||
/// The precision threshold for <c>float</c> comparisons. Defaults to
|
||||
/// <see cref="UxrConstants.Math.DefaultPrecisionThreshold" />.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the <c>float</c> are equal; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool EqualsUsingPrecision(this float a, float b, float precisionThreshold = UxrConstants.Math.DefaultPrecisionThreshold)
|
||||
{
|
||||
return Mathf.Abs(a - b) <= precisionThreshold;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a float value representing time in seconds to a formatted string value.
|
||||
/// </summary>
|
||||
/// <param name="self">Seconds to convert</param>
|
||||
/// <param name="excludeHoursIfZero">Whether to exclude the hours from the string if they are 0</param>
|
||||
/// <param name="includeMilliseconds">Whether to include the milliseconds in the string</param>
|
||||
/// <returns>
|
||||
/// Formatted time hh:mm::ss:mmm applying <paramref name="excludeHoursIfZero" /> and
|
||||
/// <paramref name="includeMilliseconds" /> constraints.
|
||||
/// </returns>
|
||||
public static string SecondsToTimeString(this float self, bool excludeHoursIfZero = false, bool includeMilliseconds = false)
|
||||
{
|
||||
int hours = Mathf.FloorToInt(self / 3600.0f);
|
||||
int minutes = Mathf.FloorToInt((self - hours * 3600.0f) / 60.0f);
|
||||
int seconds = Mathf.FloorToInt(self - hours * 3600.0f - minutes * 60.0f);
|
||||
int milliseconds = (int)(self * 1000 % 1000);
|
||||
|
||||
if (hours >= 1)
|
||||
{
|
||||
return includeMilliseconds ? $"{hours:D2}:{minutes:D2}:{seconds:D2}:{milliseconds:D3}" : $"{hours:D2}:{minutes:D2}:{seconds:D2}";
|
||||
}
|
||||
|
||||
if (excludeHoursIfZero)
|
||||
{
|
||||
return includeMilliseconds ? $"{minutes:D2}:{seconds:D2}:{milliseconds:D3}" : $"{minutes:D2}:{seconds:D2}";
|
||||
}
|
||||
|
||||
return includeMilliseconds ? $"{hours:D2}:{minutes:D2}:{seconds:D2}:{milliseconds:D3}" : $"{hours:D2}:{minutes:D2}:{seconds:D2}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a float value is or is very close to zero.
|
||||
/// </summary>
|
||||
/// <param name="self">Value to check</param>
|
||||
/// <returns>Boolean telling whether the float value is or is very close to zero</returns>
|
||||
public static bool IsAlmostZero(this float self)
|
||||
{
|
||||
return Mathf.Approximately(self, 0.0f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a value in degrees, returns the same angle making sure it's in range [-180, 180]. For example, an
|
||||
/// input of -380.3 would return -20.3.
|
||||
/// </summary>
|
||||
/// <param name="self">Value to process</param>
|
||||
/// <returns>Degrees in range between [-180, 180]</returns>
|
||||
public static float ToEuler180(this float self)
|
||||
{
|
||||
float angle = self % 360.0f;
|
||||
|
||||
if (angle > 180.0f)
|
||||
{
|
||||
angle -= 360.0f;
|
||||
}
|
||||
else if (angle < -180.0f)
|
||||
{
|
||||
angle += 360.0f;
|
||||
}
|
||||
|
||||
return angle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clamps a value so that it doesn't go beyond a given range.
|
||||
/// </summary>
|
||||
/// <param name="self">Value to clamp</param>
|
||||
/// <param name="min">Minimum value</param>
|
||||
/// <param name="max">Maximum value</param>
|
||||
/// <returns>Clamped value between [min, max]</returns>
|
||||
public static float Clamp(this ref float self, float min, float max)
|
||||
{
|
||||
self = Mathf.Clamp(self, min, max);
|
||||
return self;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a clamped value.
|
||||
/// </summary>
|
||||
/// <param name="self">Value to clamp</param>
|
||||
/// <param name="min">Minimum value</param>
|
||||
/// <param name="max">Maximum value</param>
|
||||
/// <returns>Clamped value between [min, max]</returns>
|
||||
public static float Clamped(this float self, float min, float max)
|
||||
{
|
||||
return Mathf.Clamp(self, min, max);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clamps a value to [0.0, 1.0].
|
||||
/// </summary>
|
||||
/// <param name="self">Value to clamp</param>
|
||||
/// <returns>Clamped value between [0.0, 1.0]</returns>
|
||||
public static float Clamp(this ref float self)
|
||||
{
|
||||
self = Mathf.Clamp01(self);
|
||||
return self;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a clamped value in range [0.0, 1.0].
|
||||
/// </summary>
|
||||
/// <param name="self">Value to clamp</param>
|
||||
/// <returns>Clamped value between [0.0, 1.0]</returns>
|
||||
public static float Clamped(this float self)
|
||||
{
|
||||
return Mathf.Clamp01(self);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value from the set with the maximum absolute value, but keeping the sign.
|
||||
/// </summary>
|
||||
/// <param name="values">Set of values</param>
|
||||
/// <returns>Value with the maximum absolute value keeping the sign</returns>
|
||||
public static float SignedAbsMax(params float[] values)
|
||||
{
|
||||
float signedAbsoluteMax = 0.0f;
|
||||
bool initialized = false;
|
||||
|
||||
foreach (float value in values)
|
||||
{
|
||||
if (!initialized || Mathf.Abs(value) > Mathf.Abs(signedAbsoluteMax))
|
||||
{
|
||||
initialized = true;
|
||||
signedAbsoluteMax = value;
|
||||
}
|
||||
}
|
||||
|
||||
return signedAbsoluteMax;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
3
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Math/FloatExt.cs.meta
vendored
Normal file
3
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Math/FloatExt.cs.meta
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c0bd1e8805fb4c841bf65b2e3ceacb4d
|
||||
timeCreated: 1624962547
|
||||
110
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Math/IntExt.cs
vendored
Normal file
110
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Math/IntExt.cs
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="IntExt.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Extensions.System.Math
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="int" /> and <see cref="uint" /> extensions.
|
||||
/// </summary>
|
||||
public static class IntExt
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given int value has one or more flags set.
|
||||
/// </summary>
|
||||
/// <param name="self">int value</param>
|
||||
/// <param name="flags">Flag(s) to check for</param>
|
||||
/// <returns>Whether the int value has the given flag(s) set</returns>
|
||||
public static bool HasFlags(this int self, int flags)
|
||||
{
|
||||
return flags == (flags & self);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given uint value has one or more flags set.
|
||||
/// </summary>
|
||||
/// <param name="self">uint value</param>
|
||||
/// <param name="flags">Flag(s) to check for</param>
|
||||
/// <returns>Whether the uint value has the given flag(s) set</returns>
|
||||
public static bool HasFlags(this uint self, uint flags)
|
||||
{
|
||||
return flags == (flags & self);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an int value with one or more flags set if they weren't set already.
|
||||
/// </summary>
|
||||
/// <param name="self">int value</param>
|
||||
/// <param name="flags">Flag(s) to set when returned</param>
|
||||
/// <returns>int value with the given flag(s) set</returns>
|
||||
public static int WithFlags(this int self, int flags)
|
||||
{
|
||||
return self | flags;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an uint value with one or more flags set if they weren't set already.
|
||||
/// </summary>
|
||||
/// <param name="self">uint value</param>
|
||||
/// <param name="flags">Flag(s) to set when returned</param>
|
||||
/// <returns>uint value with the given flag(s) set</returns>
|
||||
public static uint WithFlags(this uint self, uint flags)
|
||||
{
|
||||
return self | flags;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an int value with one or more flags cleared if they were set.
|
||||
/// </summary>
|
||||
/// <param name="self">int value</param>
|
||||
/// <param name="flags">Flag(s) to clear when returned</param>
|
||||
/// <returns>int value with the given flag(s) cleared</returns>
|
||||
public static int WithoutFlags(this int self, int flags)
|
||||
{
|
||||
return self & ~flags;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an uint value with one or more flags cleared if they were set.
|
||||
/// </summary>
|
||||
/// <param name="self">uint value</param>
|
||||
/// <param name="flags">Flag(s) to clear when returned</param>
|
||||
/// <returns>uint value with the given flag(s) cleared</returns>
|
||||
public static uint WithoutFlags(this uint self, uint flags)
|
||||
{
|
||||
return self & ~flags;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clamps a value so that it doesn't go beyond a given range.
|
||||
/// </summary>
|
||||
/// <param name="self">Value to clamp</param>
|
||||
/// <param name="min">Minimum value</param>
|
||||
/// <param name="max">Maximum value</param>
|
||||
/// <returns>Clamped value between [min, max]</returns>
|
||||
public static int Clamp(this ref int self, int min, int max)
|
||||
{
|
||||
self = Mathf.Clamp(self, min, max);
|
||||
return self;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a clamped value.
|
||||
/// </summary>
|
||||
/// <param name="self">Value to clamp</param>
|
||||
/// <param name="min">Minimum value</param>
|
||||
/// <param name="max">Maximum value</param>
|
||||
/// <returns>Clamped value between [min, max]</returns>
|
||||
public static int Clamped(this int self, int min, int max)
|
||||
{
|
||||
return Mathf.Clamp(self, min, max);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
3
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Math/IntExt.cs.meta
vendored
Normal file
3
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Math/IntExt.cs.meta
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c2c62d93e9b64c30af3654c58a2e2f10
|
||||
timeCreated: 1624962547
|
||||
273
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/ObjectExt.cs
vendored
Normal file
273
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/ObjectExt.cs
vendored
Normal file
@@ -0,0 +1,273 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="ObjectExt.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using UltimateXR.Core;
|
||||
using UltimateXR.Extensions.System.Collections;
|
||||
using UltimateXR.Extensions.System.Math;
|
||||
using UltimateXR.Extensions.Unity.Math;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Extensions.System
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="object" /> extensions.
|
||||
/// </summary>
|
||||
public static class ObjectExt
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Compares two objects for equality, taking into account the content of collections for collection types.
|
||||
/// </summary>
|
||||
/// <param name="a">The first object to compare</param>
|
||||
/// <param name="b">The second object to compare</param>
|
||||
/// <returns><c>True</c> if the objects are equal; otherwise, <c>false</c></returns>
|
||||
public static bool ValuesEqual(this object a, object b)
|
||||
{
|
||||
return ValuesEqual(a, b, (ea, eb) => EnumerableExt.ContentEqual(ea, eb), (va, vb) => Equals(va, vb));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Same as <see cref="ValuesEqual(object, object)" /> but using a precision threshold for the following types:
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <c>float</c>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <see cref="Vector2" />
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <see cref="Vector3" />
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <see cref="Vector4" />
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <see cref="Quaternion" />
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
/// <param name="a">The first object to compare</param>
|
||||
/// <param name="b">The second object to compare</param>
|
||||
/// <param name="precisionThreshold">
|
||||
/// The floating point precision threshold for the specific types listed above. The
|
||||
/// <see cref="UxrConstants.Math.DefaultPrecisionThreshold" /> constant can be used to provide a standard precision
|
||||
/// across all calls.
|
||||
/// </param>
|
||||
/// <returns><c>True</c> if the objects are equal; otherwise, <c>false</c></returns>
|
||||
public static bool ValuesEqual(this object a, object b, float precisionThreshold)
|
||||
{
|
||||
bool EqualsUsingPrecision(object a, object b, float precision)
|
||||
{
|
||||
// Check supported types with specific comparision using floating point precision
|
||||
|
||||
if (a is float fa && b is float fb)
|
||||
{
|
||||
return fa.EqualsUsingPrecision(fb, precision);
|
||||
}
|
||||
if (a is Vector3 v3a && b is Vector3 v3b)
|
||||
{
|
||||
return v3a.EqualsUsingPrecision(v3b, precision);
|
||||
}
|
||||
if (a is Quaternion qa && b is Quaternion qb)
|
||||
{
|
||||
return qa.EqualsUsingPrecision(qb, precision);
|
||||
}
|
||||
if (a is Vector2 v2a && b is Vector2 v2b)
|
||||
{
|
||||
return v2a.EqualsUsingPrecision(v2b, precision);
|
||||
}
|
||||
if (a is Vector4 v4a && b is Vector4 v4b)
|
||||
{
|
||||
return v4a.EqualsUsingPrecision(v4b, precision);
|
||||
}
|
||||
|
||||
// Default comparison fallback
|
||||
|
||||
return Equals(a, b);
|
||||
}
|
||||
|
||||
return ValuesEqual(a, b, (ea, eb) => EnumerableExt.ContentEqual(ea, eb, precisionThreshold), (va, vb) => EqualsUsingPrecision(va, vb, precisionThreshold));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a deep copy of the specified object, including support for arrays, List<T>, and Dictionary<TKey,
|
||||
/// TValue>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the object to be deep copied</typeparam>
|
||||
/// <param name="obj">The object to be deep copied</param>
|
||||
/// <returns>A deep copy of the original object</returns>
|
||||
/// <remarks>
|
||||
/// This method performs a deep copy, recursively copying all objects referenced by the original object.<br/>
|
||||
/// Types derived from <see cref="Component" /> are not supported, and a reference to the same object will be returned
|
||||
/// instead.<br/>
|
||||
/// If the type of the object is an array, List<T>, or Dictionary<TKey, TValue>, it is handled natively.
|
||||
/// If the type implements ICloneable, it uses the Clone method for copying.
|
||||
/// For value types (primitive types and structs), the method returns the original object as they are inherently
|
||||
/// deep-copied.
|
||||
/// For other types, binary serialization is used for deep copying.
|
||||
/// </remarks>
|
||||
public static T DeepCopy<T>(this T obj)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
|
||||
if (obj is Component)
|
||||
{
|
||||
return obj;
|
||||
}
|
||||
|
||||
Type type = obj.GetType();
|
||||
|
||||
// Check if the type is an array
|
||||
if (type.IsArray)
|
||||
{
|
||||
Type elementType = type.GetElementType();
|
||||
Array originalArray = obj as Array;
|
||||
Array copiedArray = Array.CreateInstance(elementType, originalArray.Length);
|
||||
for (int i = 0; i < originalArray.Length; i++)
|
||||
{
|
||||
copiedArray.SetValue(DeepCopy(originalArray.GetValue(i)), i);
|
||||
}
|
||||
return (T)(object)copiedArray;
|
||||
}
|
||||
|
||||
// Check if it's a List<T>
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
|
||||
{
|
||||
Type genericType = type.GetGenericArguments()[0];
|
||||
IList originalList = (IList)obj;
|
||||
IList copiedList = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(genericType));
|
||||
foreach (object item in originalList)
|
||||
{
|
||||
copiedList.Add(DeepCopy(item));
|
||||
}
|
||||
return (T)copiedList;
|
||||
}
|
||||
|
||||
// Check if it's a Dictionary<TKey, TValue>
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
|
||||
{
|
||||
Type[] genericArguments = type.GetGenericArguments();
|
||||
Type keyType = genericArguments[0];
|
||||
Type valueType = genericArguments[1];
|
||||
IDictionary originalDict = (IDictionary)obj;
|
||||
IDictionary copiedDict = (IDictionary)Activator.CreateInstance(typeof(Dictionary<,>).MakeGenericType(keyType, valueType));
|
||||
foreach (DictionaryEntry kvp in originalDict)
|
||||
{
|
||||
copiedDict.Add(DeepCopy(kvp.Key), DeepCopy(kvp.Value));
|
||||
}
|
||||
return (T)copiedDict;
|
||||
}
|
||||
|
||||
// Check if it's a HashSet<T>
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(HashSet<>))
|
||||
{
|
||||
IEnumerable originalSet = (IEnumerable)obj;
|
||||
|
||||
// Create a new HashSet<T> with the same element type
|
||||
HashSet<object> copiedSet = new HashSet<object>();
|
||||
|
||||
foreach (object item in originalSet)
|
||||
{
|
||||
copiedSet.Add(DeepCopy(item));
|
||||
}
|
||||
|
||||
return (T)(object)copiedSet;
|
||||
}
|
||||
|
||||
// Check if the type implements ICloneable
|
||||
if (typeof(ICloneable).IsAssignableFrom(type))
|
||||
{
|
||||
MethodInfo cloneMethod = type.GetMethod("Clone");
|
||||
if (cloneMethod != null)
|
||||
{
|
||||
return (T)cloneMethod.Invoke(obj, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it's a primitive type or string
|
||||
if (type.IsValueType || type == typeof(string))
|
||||
{
|
||||
return obj;
|
||||
}
|
||||
|
||||
// Use serialization for other types
|
||||
return BinarySerializationCopy(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an exception if the object is null.
|
||||
/// </summary>
|
||||
/// <param name="self">Object to check</param>
|
||||
/// <param name="paramName">Parameter name, used as argument for the exceptions</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown if the object is null</exception>
|
||||
public static void ThrowIfNull(this object self, string paramName)
|
||||
{
|
||||
if (self is null)
|
||||
{
|
||||
throw new ArgumentNullException(paramName);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Compares two objects for equality, taking into account the content of collections for collection types.
|
||||
/// </summary>
|
||||
/// <param name="a">The first object to compare</param>
|
||||
/// <param name="b">The second object to compare</param>
|
||||
/// <returns>True if the objects are equal; otherwise, false</returns>
|
||||
private static bool ValuesEqual(this object a, object b, Func<IEnumerable, IEnumerable, bool> enumerableComparer, Func<object, object, bool> valueComparer)
|
||||
{
|
||||
if (ReferenceEquals(a, b))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (a == null || b == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if both objects are collections
|
||||
if (a is IEnumerable enumerableA && b is IEnumerable enumerableB)
|
||||
{
|
||||
// Use enumerable comparer to compare the contents of collections
|
||||
return enumerableComparer(enumerableA, enumerableB);
|
||||
}
|
||||
|
||||
// Use value comparer for non-collection objects
|
||||
return valueComparer(a, b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a deep copy of an object using serialization.
|
||||
/// </summary>
|
||||
/// <param name="obj">Object to get a deep copy of</param>
|
||||
/// <typeparam name="T">The object type</typeparam>
|
||||
/// <returns>A deep copy of the object</returns>
|
||||
private static T BinarySerializationCopy<T>(T obj)
|
||||
{
|
||||
using MemoryStream memoryStream = new MemoryStream();
|
||||
BinaryFormatter formatter = new BinaryFormatter();
|
||||
formatter.Serialize(memoryStream, obj);
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
return (T)formatter.Deserialize(memoryStream);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/ObjectExt.cs.meta
vendored
Normal file
11
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/ObjectExt.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2cd622a10b541fd44b567959894a4019
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
237
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/StringExt.cs
vendored
Normal file
237
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/StringExt.cs
vendored
Normal file
@@ -0,0 +1,237 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="StringExt.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
namespace UltimateXR.Extensions.System
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="string" /> extensions.
|
||||
/// </summary>
|
||||
public static class StringExt
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of occurrences of a string in another string.
|
||||
/// </summary>
|
||||
/// <param name="self">The string where to perform the search</param>
|
||||
/// <param name="key">The string to find</param>
|
||||
/// <param name="caseSensitive">Whether the search should be case sensitive</param>
|
||||
/// <returns>Number of occurrences of <paramref name="key" /> in the source string</returns>
|
||||
public static int GetOccurrenceCount(this string self, string key, bool caseSensitive = true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(self) || string.IsNullOrEmpty(key))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (caseSensitive)
|
||||
{
|
||||
return (self.Length - self.Replace(key, string.Empty).Length) / key.Length;
|
||||
}
|
||||
|
||||
return (self.Length - self.ToLower().Replace(key.ToLower(), string.Empty).Length) / key.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the SHA-256 hash value of a string.
|
||||
/// </summary>
|
||||
/// <param name="self">String to get the SHA-256 hash value of</param>
|
||||
/// <returns>SHA-256 hash value of the string</returns>
|
||||
public static byte[] GetSha256(this string self)
|
||||
{
|
||||
using HashAlgorithm algorithm = SHA256.Create();
|
||||
return algorithm.ComputeHash(Encoding.ASCII.GetBytes(self));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the MD5 hash value of a string.
|
||||
/// </summary>
|
||||
/// <param name="self">String to get the MD5 hash value of</param>
|
||||
/// <returns>MD5 hash value of the string</returns>
|
||||
public static byte[] GetMd5(this string self)
|
||||
{
|
||||
using HashAlgorithm algorithm = MD5.Create();
|
||||
return algorithm.ComputeHash(Encoding.ASCII.GetBytes(self));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the double SHA-256 hash value of a string.
|
||||
/// </summary>
|
||||
/// <param name="self">String to get the double SHA-256 hash value of</param>
|
||||
/// <returns>Double SHA-256 hash value of the string</returns>
|
||||
public static string GetSha256x2(this string self)
|
||||
{
|
||||
byte[] sha256 = self.GetSha256();
|
||||
return sha256.Aggregate(new StringBuilder(sha256.Length * 2), (sb, b) => sb.AppendFormat("{0:x2}", b)).ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the double MD5 hash value of a string.
|
||||
/// </summary>
|
||||
/// <param name="self">String to get the double MD5 hash value of</param>
|
||||
/// <returns>Double MD5 hash value of the string</returns>
|
||||
public static string GetMd5x2(this string self)
|
||||
{
|
||||
byte[] md5 = self.GetMd5();
|
||||
return md5.Aggregate(new StringBuilder(md5.Length * 2), (sb, b) => sb.AppendFormat("{0:x2}", b)).ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a Guid result of hashing the string using SHA-256 and keeping the first 16 bytes.
|
||||
/// </summary>
|
||||
/// <param name="self">String to get the Guid hash value of</param>
|
||||
/// <returns>Guid hash value of the string</returns>
|
||||
public static Guid GetGuid(this string self)
|
||||
{
|
||||
// Take the first 16 bytes of the hash to create a Guid
|
||||
byte[] guidBytes = new byte[16];
|
||||
Buffer.BlockCopy(GetSha256(self), 0, guidBytes, 0, guidBytes.Length);
|
||||
|
||||
return new Guid(guidBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the invalid characters in a path with a given character.
|
||||
/// </summary>
|
||||
/// <param name="self">The path to process</param>
|
||||
/// <param name="fallbackChar">The valid character to use as replacement</param>
|
||||
/// <param name="invalidChars">The invalid characters to replace</param>
|
||||
/// <returns>New string with the replacements</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">The replacement character is part of the invalid characters</exception>
|
||||
public static string ReplaceInvalidPathChars(this string self, char fallbackChar = PathFallbackChar, params char[] invalidChars)
|
||||
{
|
||||
if (invalidChars.Length == 0)
|
||||
{
|
||||
return self.ReplaceInvalidDirPathChars(fallbackChar);
|
||||
}
|
||||
|
||||
if (invalidChars.Contains(fallbackChar))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(fallbackChar), fallbackChar, "Fallback should be a valid character");
|
||||
}
|
||||
|
||||
return string.Join(fallbackChar.ToString(), self.Split(invalidChars));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the invalid characters in a directory path with a given character.
|
||||
/// </summary>
|
||||
/// <param name="self">The directory path to process</param>
|
||||
/// <param name="fallbackChar">The valid character to use as replacement</param>
|
||||
/// <returns>New string with the replacements</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">The replacement character is part of the invalid characters</exception>
|
||||
public static string ReplaceInvalidDirPathChars(this string self, char fallbackChar = PathFallbackChar)
|
||||
{
|
||||
return self.ReplaceInvalidPathChars(fallbackChar, Path.GetInvalidPathChars());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the invalid characters in a file path with a given character.
|
||||
/// </summary>
|
||||
/// <param name="self">The file path to process</param>
|
||||
/// <param name="fallbackChar">The valid character to use as replacement</param>
|
||||
/// <returns>New string with the replacements</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">The replacement character is part of the invalid characters</exception>
|
||||
public static string ReplaceInvalidFilePathChars(this string self, char fallbackChar = PathFallbackChar)
|
||||
{
|
||||
return self.ReplaceInvalidPathChars(fallbackChar, Path.GetInvalidFileNameChars());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a random string.
|
||||
/// </summary>
|
||||
/// <param name="length">String length</param>
|
||||
/// <param name="includeLetters">Include letters in the string?</param>
|
||||
/// <param name="includeNumbers">Include numbers in the string?</param>
|
||||
/// <returns>Random string with given length or <see cref="string.Empty" /> if no letters and number were specified</returns>
|
||||
public static string RandomString(int length, bool includeLetters, bool includeNumbers)
|
||||
{
|
||||
const string lettersOnly = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
const string numbersOnly = "0123456789";
|
||||
const string lettersAndNumbers = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
|
||||
if (includeLetters && !includeNumbers)
|
||||
{
|
||||
return new string(Enumerable.Repeat(lettersOnly, length).Select(s => s[Random.Range(0, s.Length)]).ToArray());
|
||||
}
|
||||
if (!includeLetters && includeNumbers)
|
||||
{
|
||||
return new string(Enumerable.Repeat(numbersOnly, length).Select(s => s[Random.Range(0, s.Length)]).ToArray());
|
||||
}
|
||||
if (includeLetters && includeNumbers)
|
||||
{
|
||||
return new string(Enumerable.Repeat(lettersAndNumbers, length).Select(s => s[Random.Range(0, s.Length)]).ToArray());
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits a string using CamelCase.
|
||||
/// </summary>
|
||||
/// <param name="self">Input string</param>
|
||||
/// <returns>Output string with added spaces</returns>
|
||||
public static string SplitCamelCase(this string self)
|
||||
{
|
||||
return Regex.Replace(self, "([A-Z])", " $1", RegexOptions.Compiled).Trim();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an exception if the string is null or only contains whitespaces.
|
||||
/// </summary>
|
||||
/// <param name="self">The string to check</param>
|
||||
/// <param name="paramName">The parameter name, used as argument for the exceptions</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="self" /> is <see langword="null" /></exception>
|
||||
/// <exception cref="ArgumentException">Whitespace string is not allowed</exception>
|
||||
public static void ThrowIfNullOrWhitespace(this string self, string paramName)
|
||||
{
|
||||
if (self is null)
|
||||
{
|
||||
throw new ArgumentNullException(paramName);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(self))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(paramName, self, "Value cannot be whitespace");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an exception if the string is null or empty.
|
||||
/// </summary>
|
||||
/// <param name="self">The string</param>
|
||||
/// <param name="paramName">The parameter name, used as arguments for the exceptions</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="self" /> is <see langword="null" /></exception>
|
||||
/// <exception cref="ArgumentException">Empty string is not allowed</exception>
|
||||
public static void ThrowIfNullOrEmpty(this string self, string paramName)
|
||||
{
|
||||
if (self == null)
|
||||
{
|
||||
throw new ArgumentNullException(paramName);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(self))
|
||||
{
|
||||
throw new ArgumentException("Empty string is not allowed", paramName);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Types & Data
|
||||
|
||||
private const char PathFallbackChar = '_';
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
3
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/StringExt.cs.meta
vendored
Normal file
3
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/StringExt.cs.meta
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5449ef1ab6eb4a27ab47977420ceecf5
|
||||
timeCreated: 1626264331
|
||||
8
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Threading.meta
vendored
Normal file
8
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Threading.meta
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 185edcb988fe5984c97a09ccc4946aa6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
137
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Threading/ActionExt.cs
vendored
Normal file
137
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Threading/ActionExt.cs
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Threading/ActionExt.cs.meta
vendored
Normal file
11
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Threading/ActionExt.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4688f45bbeea8e04090b2180af69567b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
314
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Threading/TaskExt.cs
vendored
Normal file
314
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Threading/TaskExt.cs
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Threading/TaskExt.cs.meta
vendored
Normal file
11
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/Threading/TaskExt.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3fc135711d1b402b87818677827bf293
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
119
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/TypeExt.cs
vendored
Normal file
119
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/TypeExt.cs
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="TypeExt.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System;
|
||||
|
||||
namespace UltimateXR.Extensions.System
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="Type" /> extensions.
|
||||
/// </summary>
|
||||
public static class TypeExt
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="InvalidCastException" /> if the type defined by <paramref name="from" /> can't be casted to
|
||||
/// the type defined by <see cref="to" />.
|
||||
/// </summary>
|
||||
/// <param name="from">Source type</param>
|
||||
/// <param name="to">Destination type</param>
|
||||
/// <exception cref="InvalidCastException">Thrown when the source type can't be casted to the destination type</exception>
|
||||
public static void ThrowIfInvalidCast(Type from, Type to)
|
||||
{
|
||||
if (!IsValidCast(from, to))
|
||||
{
|
||||
throw new InvalidCastException($"{from.Name} is not assignable to {to.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the type defined by <paramref name="from" /> can be casted to the type defined by <see cref="to" />.
|
||||
/// </summary>
|
||||
/// <param name="from">Source type</param>
|
||||
/// <param name="to">Destination type</param>
|
||||
/// <returns>Whether it can be casted</returns>
|
||||
public static bool IsValidCast(Type from, Type to)
|
||||
{
|
||||
return to.IsAssignableFrom(from);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="InvalidCastException" /> if the type defined by <paramref name="self" /> can't be casted to
|
||||
/// the type defined by <see cref="to" />.
|
||||
/// </summary>
|
||||
/// <param name="self">Source type</param>
|
||||
/// <param name="to">Destination type</param>
|
||||
/// <exception cref="InvalidCastException">Thrown when the source type can't be casted to the destination type</exception>
|
||||
public static void ThrowIfCannotCastTo(this Type self, Type to)
|
||||
{
|
||||
ThrowIfInvalidCast(self, to);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the type defined by <paramref name="self" /> can be casted to the type defined by <see cref="to" />.
|
||||
/// </summary>
|
||||
/// <param name="self">Source type</param>
|
||||
/// <param name="to">Destination type</param>
|
||||
/// <returns>Whether it can be casted</returns>
|
||||
public static bool CanCastTo(this Type self, Type to)
|
||||
{
|
||||
return IsValidCast(self, to);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a type given the assembly and the type name.
|
||||
/// </summary>
|
||||
/// <param name="typeName">Name of the type to get</param>
|
||||
/// <param name="assemblyName">Assembly name or null if the same assembly from the caller is used</param>
|
||||
/// <returns>Type or null if there was an error</returns>
|
||||
public static Type GetType(string typeName, string assemblyName)
|
||||
{
|
||||
string fullTypeName = string.IsNullOrEmpty(assemblyName) ? typeName : $"{typeName}, {assemblyName}";
|
||||
return Type.GetType(fullTypeName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string that describes the type given by the type name and the assembly where it is located. If the
|
||||
/// assembly is empty or null, it won't return any assembly information and the type is considered to be in the same
|
||||
/// assembly as UltimateXR.
|
||||
/// </summary>
|
||||
/// <param name="typeName">The name of the type</param>
|
||||
/// <param name="assemblyName">The name of the assembly. Null or empty to ignore assembly information</param>
|
||||
/// <returns>A string in the form of Type or Type, Assembly</returns>
|
||||
public static string GetTypeString(string typeName, string assemblyName)
|
||||
{
|
||||
string assemblyString = string.IsNullOrEmpty(assemblyName) ? string.Empty : $", {assemblyName}";
|
||||
return $"{typeName}{assemblyString}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a given type which may be in the same or different assembly as the caller.
|
||||
/// </summary>
|
||||
/// <param name="typeName">Name of the type to instantiate</param>
|
||||
/// <param name="assemblyName">Assembly name or null/empty if the same assembly from the caller is used</param>
|
||||
/// <returns>New object or null if there was an error</returns>
|
||||
public static object CreateInstance(string typeName, string assemblyName)
|
||||
{
|
||||
Type type = GetType(typeName, assemblyName);
|
||||
return type == null ? null : Activator.CreateInstance(type, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a given type which may be in the same or different assembly as the caller.
|
||||
/// </summary>
|
||||
/// <param name="typeName">Name of the type to instantiate</param>
|
||||
/// <param name="assemblyName">Assembly name or null/empty if the same assembly from the caller is used</param>
|
||||
/// <param name="parameters">Optional parameters to call a specific constructor</param>
|
||||
/// <returns>New object or null if there was an error</returns>
|
||||
public static object CreateInstance(string typeName, string assemblyName, params object[] parameters)
|
||||
{
|
||||
Type type = GetType(typeName, assemblyName);
|
||||
return type == null ? null : Activator.CreateInstance(type, parameters);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
11
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/TypeExt.cs.meta
vendored
Normal file
11
Assets/ThirdParty/UltimateXR/Runtime/Scripts/Extensions/System/TypeExt.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 00dc2a5d7a294de409ab56c576ee4774
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user