Merge branch 'master' of https://github.com/Regalis11/Barotrauma into develop
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class CollectionConcat<T> : ICollection<T>
|
||||
{
|
||||
protected readonly IEnumerable<T> enumerableA;
|
||||
protected readonly IEnumerable<T> enumerableB;
|
||||
|
||||
public CollectionConcat(IEnumerable<T> a, IEnumerable<T> b)
|
||||
{
|
||||
enumerableA = a; enumerableB = b;
|
||||
}
|
||||
|
||||
public int Count => enumerableA.Count()+enumerableB.Count();
|
||||
|
||||
public bool IsReadOnly => true;
|
||||
|
||||
public void Add(T item) => throw new InvalidOperationException();
|
||||
|
||||
public void Clear() => throw new InvalidOperationException();
|
||||
|
||||
public bool Remove(T item) => throw new InvalidOperationException();
|
||||
|
||||
public bool Contains(T item) => enumerableA.Contains(item) || enumerableB.Contains(item);
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
void performCopy(IEnumerable<T> enumerable)
|
||||
{
|
||||
if (enumerable is ICollection<T> collection)
|
||||
{
|
||||
collection.CopyTo(array, arrayIndex);
|
||||
arrayIndex += collection.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var item in enumerable)
|
||||
{
|
||||
array[arrayIndex] = item;
|
||||
arrayIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
performCopy(enumerableA);
|
||||
performCopy(enumerableB);
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
foreach (T item in enumerableA) { yield return item; }
|
||||
foreach (T item in enumerableB) { yield return item; }
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
|
||||
public class ListConcat<T> : CollectionConcat<T>, IList<T>, IReadOnlyList<T>
|
||||
{
|
||||
public ListConcat(IEnumerable<T> a, IEnumerable<T> b) : base(a, b) { }
|
||||
|
||||
public int IndexOf(T item)
|
||||
{
|
||||
int aCount = 0;
|
||||
if (enumerableA is IList<T> listA)
|
||||
{
|
||||
int index = listA.IndexOf(item);
|
||||
if (index >= 0) { return index; }
|
||||
aCount = listA.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var a in enumerableA)
|
||||
{
|
||||
if (object.Equals(item, a)) { return aCount; }
|
||||
aCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (enumerableB is IList<T> listB)
|
||||
{
|
||||
int index = listB.IndexOf(item);
|
||||
if (index >= 0) { return index + aCount; }
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var b in enumerableB)
|
||||
{
|
||||
if (object.Equals(item, b)) { return aCount; }
|
||||
aCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void Insert(int index, T item)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
int aCount = enumerableA.Count();
|
||||
return index < aCount ? enumerableA.ElementAt(index) : enumerableB.ElementAt(index - aCount);
|
||||
}
|
||||
set
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public abstract class Either<T, U> where T : notnull where U : notnull
|
||||
{
|
||||
public static implicit operator Either<T, U>(T t) => new EitherT<T, U>(t);
|
||||
public static implicit operator Either<T, U>(U u) => new EitherU<T, U>(u);
|
||||
|
||||
public static explicit operator T(Either<T, U> e) => e.TryGet(out T t) ? t : throw new InvalidCastException($"Contained object is not of type {typeof(T).Name}");
|
||||
public static explicit operator U(Either<T, U> e) => e.TryGet(out U u) ? u : throw new InvalidCastException($"Contained object is not of type {typeof(U).Name}");
|
||||
|
||||
public abstract bool TryGet(out T t);
|
||||
public abstract bool TryGet(out U u);
|
||||
|
||||
public abstract bool TryCast<V>(out V v);
|
||||
|
||||
public abstract override string? ToString();
|
||||
|
||||
public abstract override bool Equals(object? obj);
|
||||
|
||||
public abstract override int GetHashCode();
|
||||
|
||||
public static bool operator ==(Either<T, U>? a, Either<T, U>? b)
|
||||
=> a is null ? b is null : a.Equals(b);
|
||||
|
||||
public static bool operator !=(Either<T, U>? a, Either<T, U>? b)
|
||||
=> !(a == b);
|
||||
|
||||
public V Match<V>(Func<T, V> t, Func<U, V> u)
|
||||
=> this switch
|
||||
{
|
||||
EitherT<T, U> e => t(e.Value),
|
||||
EitherU<T, U> e => u(e.Value),
|
||||
_ => throw new Exception("Invalid Either type")
|
||||
};
|
||||
}
|
||||
|
||||
public sealed class EitherT<T, U> : Either<T, U> where T : notnull where U : notnull
|
||||
{
|
||||
public readonly T Value;
|
||||
|
||||
public EitherT(T value) { Value = value; }
|
||||
|
||||
public override string? ToString()
|
||||
=> $"Either<{typeof(T).NameWithGenerics()}, {typeof(U).NameWithGenerics()}>({Value}: {typeof(T).NameWithGenerics()})";
|
||||
|
||||
public override bool TryGet(out T t) { t = Value; return true; }
|
||||
public override bool TryGet(out U u) { u = default!; return false; }
|
||||
|
||||
public override bool TryCast<V>(out V v)
|
||||
{
|
||||
if (Value is V result)
|
||||
{
|
||||
v = result;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
v = default!;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj switch
|
||||
{
|
||||
EitherT<T, U> other => Value.Equals(other.Value),
|
||||
T value => Value.Equals(value),
|
||||
_ => false
|
||||
};
|
||||
|
||||
public override int GetHashCode() => Value.GetHashCode();
|
||||
}
|
||||
|
||||
public sealed class EitherU<T, U> : Either<T, U> where T : notnull where U : notnull
|
||||
{
|
||||
public readonly U Value;
|
||||
|
||||
public EitherU(U value) { Value = value; }
|
||||
|
||||
public override string? ToString()
|
||||
=> $"Either<{typeof(T).NameWithGenerics()}, {typeof(U).NameWithGenerics()}>({Value}: {typeof(U).NameWithGenerics()})";
|
||||
|
||||
public override bool TryGet(out T t) { t = default!; return false; }
|
||||
public override bool TryGet(out U u) { u = Value; return true; }
|
||||
|
||||
public override bool TryCast<V>(out V v)
|
||||
{
|
||||
if (Value is V result)
|
||||
{
|
||||
v = result;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
v = default!;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj switch
|
||||
{
|
||||
EitherU<T, U> other => Value.Equals(other.Value),
|
||||
U value => Value.Equals(value),
|
||||
_ => false
|
||||
};
|
||||
|
||||
public override int GetHashCode() => Value.GetHashCode();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
namespace Barotrauma;
|
||||
|
||||
public static class GameVersion
|
||||
{
|
||||
public static readonly Version CurrentVersion
|
||||
= Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(0,0,0,0);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Extensions;
|
||||
|
||||
public static class IEnumerableExtensionsCore
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the maximum element in a given enumerable, or null if there
|
||||
/// aren't any elements in the input.
|
||||
/// </summary>
|
||||
/// <param name="enumerable">Input collection</param>
|
||||
/// <returns>Maximum element or null</returns>
|
||||
public static T? MaxOrNull<T>(this IEnumerable<T> enumerable) where T : struct, IComparable<T>
|
||||
{
|
||||
T? retVal = null;
|
||||
foreach (T v in enumerable)
|
||||
{
|
||||
if (!retVal.HasValue || v.CompareTo(retVal.Value) > 0) { retVal = v; }
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public static TOut? MaxOrNull<TIn, TOut>(this IEnumerable<TIn> enumerable, Func<TIn, TOut> conversion)
|
||||
where TOut : struct, IComparable<TOut>
|
||||
=> enumerable.Select(conversion).MaxOrNull();
|
||||
|
||||
public static int FindIndex<T>(this IReadOnlyList<T> list, Predicate<T> predicate)
|
||||
{
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
if (predicate(list[i])) { return i; }
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Same as FirstOrDefault but will always return null instead of default(T) when no element is found
|
||||
/// </summary>
|
||||
public static T? FirstOrNull<T>(this IEnumerable<T> source, Func<T, bool> predicate) where T : struct
|
||||
=> source.FirstOrNone(predicate).TryUnwrap(out T t) ? t : null;
|
||||
|
||||
public static T? FirstOrNull<T>(this IEnumerable<T> source) where T : struct
|
||||
=> source.FirstOrNone().TryUnwrap(out T t) ? t : null;
|
||||
|
||||
public static Option<T> FirstOrNone<T>(this IEnumerable<T> source, Func<T, bool> predicate) where T : notnull
|
||||
{
|
||||
foreach (T t in source)
|
||||
{
|
||||
if (predicate(t)) { return Option.Some(t); }
|
||||
}
|
||||
return Option.None;
|
||||
}
|
||||
|
||||
public static Option<T> FirstOrNone<T>(this IEnumerable<T> source) where T : notnull
|
||||
{
|
||||
using IEnumerator<T> enumerator = source.GetEnumerator();
|
||||
return enumerator.MoveNext()
|
||||
? Option.Some(enumerator.Current)
|
||||
: Option.None;
|
||||
}
|
||||
|
||||
public static IEnumerable<T> NotNone<T>(this IEnumerable<Option<T>> source) where T : notnull
|
||||
{
|
||||
foreach (var o in source)
|
||||
{
|
||||
if (o.TryUnwrap(out var v)) { yield return v; }
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<TSuccess> Successes<TSuccess, TFailure>(
|
||||
this IEnumerable<Result<TSuccess, TFailure>> source)
|
||||
where TSuccess : notnull
|
||||
where TFailure : notnull
|
||||
=> source
|
||||
.OfType<Success<TSuccess, TFailure>>()
|
||||
.Select(s => s.Value);
|
||||
|
||||
public static IEnumerable<TFailure> Failures<TSuccess, TFailure>(
|
||||
this IEnumerable<Result<TSuccess, TFailure>> source)
|
||||
where TSuccess : notnull
|
||||
where TFailure : notnull
|
||||
=> source
|
||||
.OfType<Failure<TSuccess, TFailure>>()
|
||||
.Select(f => f.Error);
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
// Identifier struct to eliminate case-sensitive comparisons
|
||||
public readonly struct Identifier : IComparable, IEquatable<Identifier>
|
||||
{
|
||||
public readonly static Identifier Empty = default;
|
||||
|
||||
private readonly static int emptyHash = "".GetHashCode(StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private readonly string? value;
|
||||
private readonly Lazy<int>? hashCode;
|
||||
|
||||
public string Value => value ?? "";
|
||||
public int HashCode => hashCode?.Value ?? emptyHash;
|
||||
|
||||
public Identifier(string? str)
|
||||
{
|
||||
value = str;
|
||||
hashCode = new Lazy<int>(() => (str ?? "").GetHashCode(StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
public bool IsEmpty => Value.IsNullOrEmpty();
|
||||
|
||||
public Identifier IfEmpty(in Identifier id)
|
||||
=> IsEmpty ? id : this;
|
||||
|
||||
public Identifier Replace(in Identifier subStr, in Identifier newStr)
|
||||
=> Replace(subStr.Value, newStr.Value);
|
||||
|
||||
public Identifier Replace(string subStr, string newStr)
|
||||
=> Value.Replace(subStr, newStr, StringComparison.OrdinalIgnoreCase).ToIdentifier();
|
||||
|
||||
public Identifier Remove(Identifier subStr)
|
||||
=> Remove(subStr.Value);
|
||||
|
||||
public Identifier Remove(string subStr)
|
||||
=> Value.Remove(subStr, StringComparison.OrdinalIgnoreCase).ToIdentifier();
|
||||
|
||||
public override bool Equals(object? obj) =>
|
||||
obj switch
|
||||
{
|
||||
Identifier i => this == i,
|
||||
string s => this == s,
|
||||
_ => base.Equals(obj)
|
||||
};
|
||||
|
||||
public bool StartsWith(string str) => Value.StartsWith(str, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public bool StartsWith(Identifier id) => StartsWith(id.Value);
|
||||
|
||||
public bool EndsWith(string str) => Value.EndsWith(str, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public bool EndsWith(Identifier id) => EndsWith(id.Value);
|
||||
|
||||
public Identifier AppendIfMissing(string suffix)
|
||||
=> EndsWith(suffix) ? this : $"{this}{suffix}".ToIdentifier();
|
||||
|
||||
public Identifier RemoveFromEnd(string suffix)
|
||||
=> Value.RemoveFromEnd(suffix, StringComparison.OrdinalIgnoreCase).ToIdentifier();
|
||||
|
||||
public bool Contains(string str) => Value.Contains(str, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public bool Contains(in Identifier id) => Contains(id.Value);
|
||||
|
||||
public override string ToString() => Value;
|
||||
|
||||
public override int GetHashCode() => HashCode;
|
||||
|
||||
public int CompareTo(object? obj)
|
||||
{
|
||||
return string.Compare(Value, obj?.ToString() ?? "", StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
public bool Equals([AllowNull] Identifier other)
|
||||
{
|
||||
return this == other;
|
||||
}
|
||||
|
||||
private static bool StringEquality(string? a, string? b)
|
||||
=> (a.IsNullOrEmpty() && b.IsNullOrEmpty()) || string.Equals(a, b, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public static bool operator ==(in Identifier a, in Identifier b) =>
|
||||
StringEquality(a.Value, b.Value);
|
||||
|
||||
public static bool operator !=(in Identifier a, in Identifier b) =>
|
||||
!(a == b);
|
||||
|
||||
public static bool operator ==(in Identifier identifier, string? str) =>
|
||||
StringEquality(identifier.Value, str);
|
||||
|
||||
public static bool operator !=(in Identifier identifier, string? str) =>
|
||||
!(identifier == str);
|
||||
|
||||
public static bool operator ==(string? str, in Identifier identifier) =>
|
||||
identifier == str;
|
||||
|
||||
public static bool operator !=(string? str, in Identifier identifier) =>
|
||||
!(identifier == str);
|
||||
|
||||
public static bool operator ==(in Identifier? a, in Identifier? b) =>
|
||||
StringEquality(a?.Value, b?.Value);
|
||||
|
||||
public static bool operator !=(in Identifier? a, in Identifier? b) =>
|
||||
!(a == b);
|
||||
|
||||
public static bool operator ==(in Identifier? a, string? b) =>
|
||||
StringEquality(a?.Value, b);
|
||||
|
||||
public static bool operator !=(in Identifier? a, string? b) =>
|
||||
!(a == b);
|
||||
|
||||
public static bool operator ==(string str, in Identifier? identifier) =>
|
||||
identifier == str;
|
||||
|
||||
public static bool operator !=(string str, in Identifier? identifier) =>
|
||||
!(identifier == str);
|
||||
|
||||
public static implicit operator Identifier(string str)
|
||||
{
|
||||
return new Identifier(str);
|
||||
}
|
||||
public int IndexOf(char c) => Value.IndexOf(c);
|
||||
|
||||
public Identifier this[Range range] => Value[range].ToIdentifier();
|
||||
public Char this[int i] => Value[i];
|
||||
}
|
||||
|
||||
public static class IdentifierExtensions
|
||||
{
|
||||
public static IEnumerable<Identifier> ToIdentifiers(this IEnumerable<string> strings)
|
||||
{
|
||||
foreach (string s in strings)
|
||||
{
|
||||
if (string.IsNullOrEmpty(s)) { continue; }
|
||||
yield return new Identifier(s);
|
||||
}
|
||||
}
|
||||
|
||||
public static Identifier[] ToIdentifiers(this string[] strings)
|
||||
=> ((IEnumerable<string>)strings).ToIdentifiers().ToArray();
|
||||
|
||||
public static Identifier ToIdentifier(this string? s)
|
||||
{
|
||||
return new Identifier(s);
|
||||
}
|
||||
|
||||
public static Identifier ToIdentifier<T>(this T t) where T: notnull
|
||||
{
|
||||
return t.ToString().ToIdentifier();
|
||||
}
|
||||
|
||||
public static bool Contains(this ISet<Identifier> set, string identifier)
|
||||
{
|
||||
return set.Contains(identifier.ToIdentifier());
|
||||
}
|
||||
|
||||
public static bool ContainsKey<T>(this IReadOnlyDictionary<Identifier, T> dictionary, string key)
|
||||
{
|
||||
return dictionary.ContainsKey(key.ToIdentifier());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
/// <summary>
|
||||
/// This type is intended to be used in using statements to automatically
|
||||
/// clean up resources that are allocated incrementally
|
||||
/// </summary>
|
||||
public readonly struct Janitor : IDisposable
|
||||
{
|
||||
private readonly List<Action> cleanupActions;
|
||||
private Janitor(List<Action> cleanupActions)
|
||||
{
|
||||
this.cleanupActions = cleanupActions;
|
||||
}
|
||||
|
||||
public static Janitor Start()
|
||||
=> new Janitor(new List<Action>());
|
||||
|
||||
/// <summary>
|
||||
/// Give the janitor a new action to perform when disposed
|
||||
/// </summary>
|
||||
public void AddAction([NotNull]Action action)
|
||||
{
|
||||
// Null check to punish misuse early instead of having the Janitor blow up upon disposal.
|
||||
// Make sure you use nullable contexts so the compiler will stop you instead!
|
||||
if (action is null)
|
||||
{
|
||||
throw new ArgumentException($"Cannot add null as an action for {nameof(Janitor)}");
|
||||
}
|
||||
cleanupActions.Add(action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Relieve the janitor of all current duties,
|
||||
/// i.e. all of the currently enqueued cleanup
|
||||
/// actions are cleared and will not execute
|
||||
/// </summary>
|
||||
public void Dismiss()
|
||||
=> cleanupActions.Clear();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
cleanupActions.ForEach(a => a());
|
||||
Dismiss();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
public static class UnixTime
|
||||
{
|
||||
public static readonly DateTime UtcEpoch = new DateTime(year: 1970, month: 1, day: 1, hour: 0, minute: 0, second: 0, kind: DateTimeKind.Utc);
|
||||
|
||||
public static Option<DateTime> ParseUtc(string str)
|
||||
{
|
||||
if (!ulong.TryParse(str, out var seconds)) { return Option.None; }
|
||||
return Option.Some(UtcEpoch + TimeSpan.FromSeconds(seconds));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// URL-safe Base64. See https://datatracker.ietf.org/doc/html/rfc4648#section-5
|
||||
/// </summary>
|
||||
public static class Base64Url
|
||||
{
|
||||
public static bool IsBase64Url(this string str)
|
||||
=> str.All(c
|
||||
=> c is
|
||||
(>= 'A' and <= 'Z')
|
||||
or (>= 'a' and <= 'z')
|
||||
or (>= '0' and <= '9')
|
||||
or '-' or '_');
|
||||
|
||||
public static Option<string> DecodeUtf8String(string encodedForm)
|
||||
{
|
||||
return DecodeBytes(encodedForm).Select(bytes => Encoding.UTF8.GetString(bytes.AsSpan()));
|
||||
}
|
||||
|
||||
public static Option<ImmutableArray<byte>> DecodeBytes(string encodedForm)
|
||||
{
|
||||
if (!encodedForm.IsBase64Url()) { return Option.None; }
|
||||
string base64Form = encodedForm.Replace("-", "+").Replace("_", "/");
|
||||
base64Form += new string('=', (4 - (base64Form.Length % 4)) % 4);
|
||||
return Option.Some(Convert.FromBase64String(base64Form).ToImmutableArray());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rudimentary JSON Web Token implementation. See https://en.wikipedia.org/wiki/JSON_Web_Token.
|
||||
/// This is used by continuance tokens and ID tokens as part of their internal representation.
|
||||
/// We can use the data encoded in them for some things, such as determining a token's expiry time.
|
||||
/// </summary>
|
||||
public readonly record struct JsonWebToken(
|
||||
string Header,
|
||||
string Payload,
|
||||
string Signature)
|
||||
{
|
||||
public bool IsValid => Header.IsBase64Url() && Payload.IsBase64Url() && Signature.IsBase64Url();
|
||||
|
||||
public override string ToString()
|
||||
=> $"{Header}.{Payload}.{Signature}";
|
||||
|
||||
public string HeaderDecoded => Base64Url.DecodeUtf8String(Header).Fallback("");
|
||||
public string PayloadDecoded => Base64Url.DecodeUtf8String(Payload).Fallback("");
|
||||
|
||||
public static Option<JsonWebToken> Parse(string str)
|
||||
{
|
||||
if (str.Split(".") is not { Length: 3 } split) { return Option.None; }
|
||||
var newToken = new JsonWebToken(
|
||||
Header: split[0],
|
||||
Payload: split[1],
|
||||
Signature: split[2]);
|
||||
if (!newToken.IsValid) { return Option.None; }
|
||||
return Option.Some(newToken);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public sealed class NamedEvent<T> : IDisposable
|
||||
{
|
||||
private readonly ConcurrentDictionary<Identifier, Action<T>> events = new ConcurrentDictionary<Identifier, Action<T>>();
|
||||
|
||||
public void Register(Identifier identifier, Action<T> action)
|
||||
{
|
||||
if (!events.TryAdd(identifier, action))
|
||||
{
|
||||
throw new ArgumentException($"Event with the identifier \"{identifier}\" has already been registered.", nameof(identifier));
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterOverwriteExisting(Identifier identifier, Action<T> action)
|
||||
{
|
||||
events.AddOrUpdate(identifier, action, (k, v) => action);
|
||||
}
|
||||
|
||||
public void Deregister(Identifier identifier)
|
||||
{
|
||||
events.TryRemove(identifier, out _);
|
||||
}
|
||||
|
||||
public void TryDeregister(Identifier identifier)
|
||||
{
|
||||
if (!HasEvent(identifier)) { return; }
|
||||
Deregister(identifier);
|
||||
}
|
||||
|
||||
public bool HasEvent(Identifier identifier)
|
||||
=> events.ContainsKey(identifier);
|
||||
|
||||
public void Invoke(T data)
|
||||
{
|
||||
foreach (var (_, action) in events)
|
||||
{
|
||||
action?.Invoke(data);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
events.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
/// <summary>
|
||||
/// Discriminated union of three types.
|
||||
/// Essentially the same thing as Either<T1, T2>, except for three types instead of two types.
|
||||
/// </summary>
|
||||
public readonly struct OneOf<T1, T2, T3>
|
||||
where T1 : notnull
|
||||
where T2 : notnull
|
||||
where T3 : notnull
|
||||
{
|
||||
private readonly Option<T1> value1;
|
||||
private readonly Option<T2> value2;
|
||||
private readonly Option<T3> value3;
|
||||
|
||||
private OneOf(Option<T1> value1, Option<T2> value2, Option<T3> value3)
|
||||
{
|
||||
this.value1 = value1;
|
||||
this.value2 = value2;
|
||||
this.value3 = value3;
|
||||
}
|
||||
|
||||
public static implicit operator OneOf<T1, T2, T3>(T1 value1)
|
||||
=> new OneOf<T1, T2, T3>(value1: Option.Some(value1), value2: Option.None, value3: Option.None);
|
||||
public static implicit operator OneOf<T1, T2, T3>(T2 value2)
|
||||
=> new OneOf<T1, T2, T3>(value1: Option.None, value2: Option.Some(value2), value3: Option.None);
|
||||
public static implicit operator OneOf<T1, T2, T3>(T3 value3)
|
||||
=> new OneOf<T1, T2, T3>(value1: Option.None, value2: Option.None, value3: Option.Some(value3));
|
||||
|
||||
public bool TryGet([NotNullWhen(returnValue: true)] out T1? t1)
|
||||
=> value1.TryUnwrap(out t1);
|
||||
public bool TryGet([NotNullWhen(returnValue: true)] out T2? t2)
|
||||
=> value2.TryUnwrap(out t2);
|
||||
public bool TryGet([NotNullWhen(returnValue: true)] out T3? t3)
|
||||
=> value3.TryUnwrap(out t3);
|
||||
|
||||
private static string ObjectToStringWithType<T>(T obj)
|
||||
=> $"{obj}: {typeof(T).Name}";
|
||||
|
||||
public override string ToString()
|
||||
=> $"OneOf<{typeof(T1).Name}, {typeof(T2).Name}, {typeof(T3).Name}>("
|
||||
+ value1.Select(ObjectToStringWithType)
|
||||
.Fallback(value2.Select(ObjectToStringWithType))
|
||||
.Fallback(value3.Select(ObjectToStringWithType))
|
||||
.Fallback("None")
|
||||
+ ")";
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public readonly struct Option<T> where T : notnull
|
||||
{
|
||||
private readonly bool hasValue;
|
||||
private readonly T? value;
|
||||
|
||||
private Option(bool hasValue, T? value)
|
||||
{
|
||||
this.hasValue = hasValue;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public bool IsSome() => hasValue;
|
||||
public bool IsNone() => !IsSome();
|
||||
|
||||
public bool TryUnwrap<T1>([NotNullWhen(returnValue: true)] out T1? outValue) where T1 : T
|
||||
{
|
||||
bool hasValueOfGivenType = false;
|
||||
outValue = default;
|
||||
|
||||
if (hasValue && value is T1 t1)
|
||||
{
|
||||
hasValueOfGivenType = true;
|
||||
outValue = t1;
|
||||
}
|
||||
|
||||
return hasValueOfGivenType;
|
||||
}
|
||||
|
||||
public bool TryUnwrap([NotNullWhen(returnValue: true)] out T? outValue)
|
||||
=> TryUnwrap<T>(out outValue);
|
||||
|
||||
public Option<TType> Select<TType>(Func<T, TType> selector) where TType : notnull
|
||||
=> TryUnwrap(out T? selfValue) ? Option.Some(selector(selfValue)) : Option.None;
|
||||
|
||||
public Option<TType> Bind<TType>(Func<T, Option<TType>> binder) where TType : notnull
|
||||
=> TryUnwrap(out T? selfValue) ? binder(selfValue) : Option.None;
|
||||
|
||||
public async Task<Option<TType>> Bind<TType>(Func<T, Task<Option<TType>>> binder) where TType : notnull
|
||||
=> TryUnwrap(out T? selfValue) ? await binder(selfValue) : Option.None;
|
||||
|
||||
public T Match(Func<T, T> some, Func<T> none)
|
||||
=> TryUnwrap(out T? selfValue) ? some(selfValue) : none();
|
||||
|
||||
public void Match(Action<T> some, Action none)
|
||||
{
|
||||
if (TryUnwrap(out T? selfValue))
|
||||
{
|
||||
some(selfValue);
|
||||
return;
|
||||
}
|
||||
none();
|
||||
}
|
||||
|
||||
public T Fallback(T fallback)
|
||||
=> TryUnwrap(out var v) ? v : fallback;
|
||||
|
||||
public Option<T> Fallback(Option<T> fallback)
|
||||
=> IsSome() ? this : fallback;
|
||||
|
||||
public static Option<T> Some(T value)
|
||||
=> typeof(T) switch
|
||||
{
|
||||
var t when t == typeof(bool)
|
||||
=> throw new Exception("Option type rejects booleans"),
|
||||
{IsConstructedGenericType: true} t when t.GetGenericTypeDefinition() == typeof(Option<>)
|
||||
=> throw new Exception("Option type rejects nested Option"),
|
||||
{IsConstructedGenericType: true} t when t.GetGenericTypeDefinition() == typeof(Nullable<>)
|
||||
=> throw new Exception("Option type rejects Nullable"),
|
||||
_
|
||||
=> new Option<T>(hasValue: true, value: value ?? throw new Exception("Option type rejects null"))
|
||||
};
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj switch
|
||||
{
|
||||
Option<T> otherOption when otherOption.IsNone()
|
||||
=> IsNone(),
|
||||
Option<T> otherOption when otherOption.TryUnwrap(out var otherValue)
|
||||
=> ValueEquals(otherValue),
|
||||
T otherValue
|
||||
=> ValueEquals(otherValue),
|
||||
_
|
||||
=> false
|
||||
};
|
||||
|
||||
public bool ValueEquals(T otherValue)
|
||||
=> TryUnwrap(out T? selfValue) && selfValue.Equals(otherValue);
|
||||
|
||||
public override int GetHashCode()
|
||||
=> TryUnwrap(out T? selfValue) ? selfValue.GetHashCode() : 0;
|
||||
|
||||
public static bool operator ==(Option<T> a, Option<T> b)
|
||||
=> a.Equals(b);
|
||||
|
||||
public static bool operator !=(Option<T> a, Option<T> b)
|
||||
=> !(a == b);
|
||||
|
||||
public static Option<T> None()
|
||||
=> default;
|
||||
|
||||
public static implicit operator Option<T>(in Option.UnspecifiedNone _)
|
||||
=> None();
|
||||
|
||||
public override string ToString()
|
||||
=> TryUnwrap(out var selfValue)
|
||||
? $"Some<{typeof(T).Name}>({selfValue})"
|
||||
: $"None<{typeof(T).Name}>";
|
||||
}
|
||||
|
||||
public static class Option
|
||||
{
|
||||
public static Option<T> Some<T>(T value) where T : notnull
|
||||
=> Option<T>.Some(value);
|
||||
|
||||
public static UnspecifiedNone None
|
||||
=> default;
|
||||
|
||||
public readonly ref struct UnspecifiedNone
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
/// <summary>
|
||||
/// An inclusive range, i.e. [Start, End] where Start <= End
|
||||
/// </summary>
|
||||
public struct Range<T> where T : notnull, IComparable<T>
|
||||
{
|
||||
private T start; private T end;
|
||||
public T Start
|
||||
{
|
||||
get { return start; }
|
||||
set
|
||||
{
|
||||
start = value;
|
||||
VerifyStartLessThanEnd();
|
||||
}
|
||||
}
|
||||
|
||||
public T End
|
||||
{
|
||||
get { return end; }
|
||||
set
|
||||
{
|
||||
end = value;
|
||||
VerifyEndGreaterThanStart();
|
||||
}
|
||||
}
|
||||
|
||||
public readonly bool Contains(in T v)
|
||||
=> start.CompareTo(v) <= 0 && end.CompareTo(v) >= 0;
|
||||
|
||||
private void VerifyStartLessThanEnd()
|
||||
{
|
||||
if (start.CompareTo(end) > 0) { throw new InvalidOperationException($"Range<{typeof(T).Name}>.Start set to a value greater than End ({start} > {end})"); }
|
||||
}
|
||||
|
||||
private void VerifyEndGreaterThanStart()
|
||||
{
|
||||
if (end.CompareTo(start) < 0) { throw new InvalidOperationException($"Range<{typeof(T).Name}>.End set to a value less than Start ({end} < {start})"); }
|
||||
}
|
||||
|
||||
public Range(T start, T end)
|
||||
{
|
||||
this.start = start; this.end = end;
|
||||
VerifyEndGreaterThanStart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public static class ReflectionUtils
|
||||
{
|
||||
private static readonly ConcurrentDictionary<Assembly, ImmutableArray<Type>> CachedNonAbstractTypes = new();
|
||||
private static readonly ConcurrentDictionary<string, ImmutableArray<Type>> TypeSearchCache = new();
|
||||
|
||||
public static T GetValueFromStaticProperty<T>(this PropertyInfo property)
|
||||
{
|
||||
if (property.GetMethod is not { IsStatic: true })
|
||||
{
|
||||
throw new ArgumentException($"Property {property} is not static");
|
||||
}
|
||||
|
||||
var value = property.GetValue(obj: null);
|
||||
if (value is not T castValue)
|
||||
{
|
||||
throw new ArgumentException($"Property {property} is null or not of type {typeof(T)}");
|
||||
}
|
||||
|
||||
return castValue;
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> GetDerivedNonAbstract<T>()
|
||||
{
|
||||
Type t = typeof(T);
|
||||
string typeName = t.FullName ?? t.Name;
|
||||
|
||||
// search quick lookup cache
|
||||
if (TypeSearchCache.TryGetValue(typeName, out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
// doesn't exist so let's add it.
|
||||
Assembly assembly = typeof(T).Assembly;
|
||||
if (!CachedNonAbstractTypes.ContainsKey(assembly))
|
||||
{
|
||||
AddNonAbstractAssemblyTypes(assembly);
|
||||
}
|
||||
|
||||
// build cache from registered assemblies' types.
|
||||
var list = CachedNonAbstractTypes.Values
|
||||
.SelectMany(arr => arr.Where(type => type.IsSubclassOf(t)))
|
||||
.ToImmutableArray();
|
||||
|
||||
if (list.Length == 0)
|
||||
{
|
||||
return ImmutableArray<Type>.Empty; // No types, don't add to cache
|
||||
}
|
||||
|
||||
if (!TypeSearchCache.TryAdd(typeName, list))
|
||||
{
|
||||
DebugConsole.LogError($"ReflectionUtils::AddNonAbstractAssemblyTypes() | Error while adding to quick lookup cache.");
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an assembly's Non-Abstract Types to the cache for Barotrauma's Type lookup.
|
||||
/// </summary>
|
||||
/// <param name="assembly">Assembly to be added</param>
|
||||
/// <param name="overwrite">Whether or not to overwrite an entry if the assembly already exists within it.</param>
|
||||
public static void AddNonAbstractAssemblyTypes(Assembly assembly, bool overwrite = false)
|
||||
{
|
||||
if (CachedNonAbstractTypes.ContainsKey(assembly))
|
||||
{
|
||||
if (!overwrite)
|
||||
{
|
||||
DebugConsole.LogError(
|
||||
$"ReflectionUtils::AddNonAbstractAssemblyTypes() | The assembly [{assembly.GetName()}] already exists in the cache.");
|
||||
return;
|
||||
}
|
||||
|
||||
CachedNonAbstractTypes.Remove(assembly, out _);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!CachedNonAbstractTypes.TryAdd(assembly, assembly.GetSafeTypes().Where(t => !t.IsAbstract).ToImmutableArray()))
|
||||
{
|
||||
DebugConsole.LogError($"ReflectionUtils::AddNonAbstractAssemblyTypes() | Unable to add types from Assembly to cache.");
|
||||
}
|
||||
else
|
||||
{
|
||||
TypeSearchCache.Clear(); // Needs to be rebuilt to include potential new types
|
||||
}
|
||||
}
|
||||
catch (ReflectionTypeLoadException e)
|
||||
{
|
||||
DebugConsole.LogError($"ReflectionUtils::AddNonAbstractAssemblyTypes() | RTFException: Unable to load Assembly Types from {assembly.GetName()}.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an assembly from the cache for Barotrauma's Type lookup.
|
||||
/// </summary>
|
||||
/// <param name="assembly">Assembly to remove.</param>
|
||||
public static void RemoveAssemblyFromCache(Assembly assembly)
|
||||
{
|
||||
CachedNonAbstractTypes.Remove(assembly, out _);
|
||||
TypeSearchCache.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all cached assembly data and rebuilds types list only to include base Barotrauma types.
|
||||
/// </summary>
|
||||
internal static void ResetCache()
|
||||
{
|
||||
CachedNonAbstractTypes.Clear();
|
||||
CachedNonAbstractTypes.TryAdd(typeof(ReflectionUtils).Assembly, typeof(ReflectionUtils).Assembly.GetSafeTypes().ToImmutableArray());
|
||||
TypeSearchCache.Clear();
|
||||
}
|
||||
|
||||
public static Type? GetType(string nameWithNamespace)
|
||||
{
|
||||
if (Type.GetType(nameWithNamespace) is Type t) { return t; }
|
||||
|
||||
var entryAssembly = Assembly.GetEntryAssembly();
|
||||
if (entryAssembly?.GetType(nameWithNamespace) is Type t2) { return t2; }
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Option<TBase> ParseDerived<TBase, TInput>(TInput input) where TInput : notnull where TBase : notnull
|
||||
where TBase : notnull
|
||||
where TInput : notnull
|
||||
{
|
||||
static Option<TBase> none() => Option<TBase>.None();
|
||||
|
||||
var derivedTypes = GetDerivedNonAbstract<TBase>();
|
||||
|
||||
Option<TBase> parseOfType(Type t)
|
||||
{
|
||||
//every TBase type is expected to have a method with the following signature:
|
||||
// public static Option<T> Parse(TInput str)
|
||||
var parseFunc = t.GetMethod("Parse", BindingFlags.Public | BindingFlags.Static);
|
||||
if (parseFunc is null) { return none(); }
|
||||
|
||||
var parameters = parseFunc.GetParameters();
|
||||
if (parameters.Length != 1) { return none(); }
|
||||
|
||||
var returnType = parseFunc.ReturnType;
|
||||
if (!returnType.IsConstructedGenericType) { return none(); }
|
||||
if (returnType.GetGenericTypeDefinition() != typeof(Option<>)) { return none(); }
|
||||
if (returnType.GenericTypeArguments[0] != t) { return none(); }
|
||||
|
||||
//some hacky business to convert from Option<T2> to Option<TBase> when we only know T2 at runtime
|
||||
static Option<TBase> convert<T2>(Option<T2> option) where T2 : TBase
|
||||
=> option.Select(v => (TBase)v);
|
||||
Func<Option<TBase>, Option<TBase>> f = convert;
|
||||
var genericArgs = f.Method.GetGenericArguments();
|
||||
genericArgs[^1] = t;
|
||||
var constructedConverter =
|
||||
f.Method.GetGenericMethodDefinition().MakeGenericMethod(genericArgs);
|
||||
|
||||
return constructedConverter.Invoke(null, new[] { parseFunc.Invoke(null, new object[] { input }) })
|
||||
as Option<TBase>? ?? none();
|
||||
}
|
||||
|
||||
return derivedTypes.Select(parseOfType).FirstOrDefault(t => t.IsSome());
|
||||
}
|
||||
|
||||
public static string NameWithGenerics(this Type t)
|
||||
{
|
||||
if (!t.IsGenericType) { return t.Name; }
|
||||
|
||||
string result = t.Name[..t.Name.IndexOf('`')];
|
||||
result += $"<{string.Join(", ", t.GetGenericArguments().Select(NameWithGenerics))}>";
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public abstract class Result<TSuccess, TFailure>
|
||||
where TSuccess: notnull
|
||||
where TFailure: notnull
|
||||
{
|
||||
public abstract bool IsSuccess { get; }
|
||||
public bool IsFailure => !IsSuccess;
|
||||
|
||||
public static Success<TSuccess, TFailure> Success(TSuccess value)
|
||||
=> new Success<TSuccess, TFailure>(value);
|
||||
|
||||
public static Failure<TSuccess, TFailure> Failure(TFailure error)
|
||||
=> new Failure<TSuccess, TFailure>(error);
|
||||
|
||||
public abstract bool TryUnwrapSuccess([NotNullWhen(returnValue: true)] out TSuccess? value);
|
||||
public abstract bool TryUnwrapFailure([NotNullWhen(returnValue: true)] out TFailure? value);
|
||||
|
||||
public abstract override string ToString();
|
||||
|
||||
public static (Func<TSuccess, Result<TSuccess, TFailure>> Success, Func<TFailure, Result<TSuccess, TFailure>> Failure) GetFactoryMethods()
|
||||
=> (Success, Failure);
|
||||
|
||||
public static implicit operator Result<TSuccess, TFailure>(Result.UnspecifiedSuccess<TSuccess> unspecifiedSuccess)
|
||||
=> Success(unspecifiedSuccess.Value);
|
||||
|
||||
public static implicit operator Result<TSuccess, TFailure>(Result.UnspecifiedFailure<TFailure> unspecifiedFailure)
|
||||
=> Failure(unspecifiedFailure.Value);
|
||||
|
||||
public void Match(Action<TSuccess> success, Action<TFailure> failure)
|
||||
{
|
||||
if (TryUnwrapSuccess(out var successValue)) { success(successValue); }
|
||||
if (TryUnwrapFailure(out var failureValue)) { failure(failureValue); }
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class Success<TSuccess, TFailure> : Result<TSuccess, TFailure>
|
||||
where TSuccess: notnull
|
||||
where TFailure: notnull
|
||||
{
|
||||
public readonly TSuccess Value;
|
||||
public override bool IsSuccess => true;
|
||||
|
||||
public override bool TryUnwrapSuccess([MaybeNullWhen(returnValue: false)] out TSuccess value)
|
||||
{
|
||||
value = Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool TryUnwrapFailure([MaybeNullWhen(returnValue: false)] out TFailure value)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"Success<{typeof(TSuccess).NameWithGenerics()}, {typeof(TFailure).NameWithGenerics()}>({Value})";
|
||||
|
||||
public Success(TSuccess value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class Failure<TSuccess, TFailure> : Result<TSuccess, TFailure>
|
||||
where TSuccess: notnull
|
||||
where TFailure: notnull
|
||||
{
|
||||
public readonly TFailure Error;
|
||||
|
||||
public override bool IsSuccess => false;
|
||||
|
||||
public override bool TryUnwrapSuccess([MaybeNullWhen(returnValue: false)] out TSuccess value)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool TryUnwrapFailure([MaybeNullWhen(returnValue: false)] out TFailure value)
|
||||
{
|
||||
value = Error;
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"Failure<{typeof(TSuccess).NameWithGenerics()}, {typeof(TFailure).NameWithGenerics()}>({Error})";
|
||||
|
||||
public Failure(TFailure error)
|
||||
{
|
||||
Error = error;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Result
|
||||
{
|
||||
public readonly ref struct UnspecifiedSuccess<TSuccess>
|
||||
where TSuccess : notnull
|
||||
{
|
||||
internal readonly TSuccess Value;
|
||||
internal UnspecifiedSuccess(TSuccess value) { Value = value; }
|
||||
}
|
||||
|
||||
public readonly ref struct UnspecifiedFailure<TFailure>
|
||||
where TFailure : notnull
|
||||
{
|
||||
internal readonly TFailure Value;
|
||||
internal UnspecifiedFailure(TFailure value) { Value = value; }
|
||||
}
|
||||
|
||||
public static UnspecifiedSuccess<TSuccess> Success<TSuccess>(TSuccess value) where TSuccess : notnull
|
||||
=> new UnspecifiedSuccess<TSuccess>(value);
|
||||
|
||||
public static UnspecifiedFailure<TFailure> Failure<TFailure>(TFailure value) where TFailure : notnull
|
||||
=> new UnspecifiedFailure<TFailure>(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public static class TaskExtensionsCore
|
||||
{
|
||||
public static async Task<Option<T>> ToOptionTask<T>(this Task<T?> nullableTask) where T : struct
|
||||
{
|
||||
var nullableResult = await nullableTask;
|
||||
return nullableResult is { } result
|
||||
? Option.Some(result)
|
||||
: Option.None;
|
||||
}
|
||||
|
||||
public static bool TryGetResult<T>(this Task task, [NotNullWhen(returnValue: true)]out T? result) where T : notnull
|
||||
{
|
||||
if (task is Task<T> { IsCompletedSuccessfully: true, Result: not null } castTask)
|
||||
{
|
||||
result = castTask.Result;
|
||||
return true;
|
||||
}
|
||||
#if DEBUG
|
||||
if (task.Exception != null)
|
||||
{
|
||||
var ex = task.Exception.GetInnermost();
|
||||
throw new InvalidOperationException($"Failed to get result from task: task failed with exception {ex.Message} ({ex.GetType()}) {ex.StackTrace}");
|
||||
}
|
||||
if (task is not Task<T>)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to get result from task: expected Task<{typeof(T).NameWithGenerics()}>, got {task.GetType().NameWithGenerics()}");
|
||||
}
|
||||
#endif
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public static class TaskPool
|
||||
{
|
||||
/// <summary>
|
||||
/// Empty callback that can be used when we do not care about the completion status of a task.
|
||||
/// </summary>
|
||||
public static void IgnoredCallback(Task task) { }
|
||||
|
||||
const int MaxTasks = 5000;
|
||||
|
||||
private struct TaskAction
|
||||
{
|
||||
public string Name;
|
||||
public Task Task;
|
||||
public Action<Task, object?> OnCompletion;
|
||||
public object? UserData;
|
||||
}
|
||||
|
||||
private static readonly List<TaskAction> taskActions = new List<TaskAction>();
|
||||
|
||||
public static void ListTasks(Action<string> log)
|
||||
{
|
||||
lock (taskActions)
|
||||
{
|
||||
log($"Task count: {taskActions.Count}");
|
||||
for (int i = 0; i < taskActions.Count; i++)
|
||||
{
|
||||
log($" -{i}: {taskActions[i].Name}, {taskActions[i].Task.Status}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsTaskRunning(string name)
|
||||
{
|
||||
lock (taskActions)
|
||||
{
|
||||
return taskActions.Any(t => t.Name == name);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddInternal(string name, Task task, Action<Task, object?> onCompletion, object? userdata, bool addIfFound = true)
|
||||
{
|
||||
lock (taskActions)
|
||||
{
|
||||
if (!addIfFound)
|
||||
{
|
||||
if (taskActions.Any(t => t.Name == name)) { return; }
|
||||
}
|
||||
if (taskActions.Count >= MaxTasks)
|
||||
{
|
||||
throw new Exception(
|
||||
"Too many tasks in the TaskPool:\n" + string.Join('\n', taskActions.Select(ta => ta.Name))
|
||||
);
|
||||
}
|
||||
taskActions.Add(new TaskAction() { Name = name, Task = task, OnCompletion = onCompletion, UserData = userdata });
|
||||
}
|
||||
}
|
||||
|
||||
public static Unit Add(string name, Task task, Action<Task>? onCompletion)
|
||||
{
|
||||
AddInternal(name, task, (t, _) => { onCompletion?.Invoke(t); }, null);
|
||||
return Unit.Value;
|
||||
}
|
||||
|
||||
public static Unit AddWithResult<T>(string name, Task<T> task, Action<T>? onCompletion) where T : notnull
|
||||
{
|
||||
AddInternal(name, task, (t, _) =>
|
||||
{
|
||||
if (t.TryGetResult(out T? result)) { onCompletion?.Invoke(result); }
|
||||
}, null);
|
||||
return Unit.Value;
|
||||
}
|
||||
public static Unit AddIfNotFound(string name, Task task, Action<Task> onCompletion)
|
||||
{
|
||||
AddInternal(name, task, (t, _) => { onCompletion?.Invoke(t); }, null, addIfFound: false);
|
||||
return Unit.Value;
|
||||
}
|
||||
|
||||
public static void Update()
|
||||
{
|
||||
lock (taskActions)
|
||||
{
|
||||
for (int i = 0; i < taskActions.Count; i++)
|
||||
{
|
||||
if (taskActions[i].Task.IsCompleted)
|
||||
{
|
||||
taskActions[i].OnCompletion?.Invoke(taskActions[i].Task, taskActions[i].UserData);
|
||||
taskActions[i].Task.Dispose();
|
||||
taskActions.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void PrintTaskExceptions(Task task, string msg, Action<string> throwError)
|
||||
{
|
||||
throwError(msg);
|
||||
foreach (Exception e in task.Exception?.InnerExceptions ?? Enumerable.Empty<Exception>())
|
||||
{
|
||||
throwError($"{e.Message}\n{e.StackTrace}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Threading;
|
||||
|
||||
namespace Barotrauma.Threading
|
||||
{
|
||||
public readonly ref struct ReadLock
|
||||
{
|
||||
private readonly ReaderWriterLockSlim rwl;
|
||||
public ReadLock(ReaderWriterLockSlim rwl)
|
||||
{
|
||||
this.rwl = rwl;
|
||||
rwl.EnterReadLock();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
rwl.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
public readonly ref struct WriteLock
|
||||
{
|
||||
private readonly ReaderWriterLockSlim rwl;
|
||||
public WriteLock(ReaderWriterLockSlim rwl)
|
||||
{
|
||||
this.rwl = rwl;
|
||||
rwl.EnterWriteLock();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
rwl.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
public static class ToolBoxCore
|
||||
{
|
||||
public static string ByteArrayToHexString(IReadOnlyList<byte> ba)
|
||||
{
|
||||
var hex = new StringBuilder(ba.Count * 2);
|
||||
foreach (byte b in ba)
|
||||
{
|
||||
hex.AppendFormat("{0:X2}", b);
|
||||
}
|
||||
return hex.ToString();
|
||||
}
|
||||
|
||||
public static byte[] HexStringToByteArray(string str)
|
||||
{
|
||||
var byteRepresentation = new byte[str.Length / 2];
|
||||
for (int i = 0; i < byteRepresentation.Length; i++)
|
||||
{
|
||||
byteRepresentation[i] = Convert.ToByte(str.Substring(i * 2, 2), 16);
|
||||
}
|
||||
|
||||
return byteRepresentation;
|
||||
}
|
||||
|
||||
public static bool IsHexadecimalDigit(this char c)
|
||||
=> char.IsDigit(c)
|
||||
|| c is (>= 'a' and <= 'f') or (>= 'A' and <= 'F');
|
||||
|
||||
public static bool IsHexString(this string s)
|
||||
=> !s.IsNullOrEmpty() && s.All(IsHexadecimalDigit);
|
||||
|
||||
public static UInt32 IdentifierToUint32Hash(Identifier id, MD5 md5)
|
||||
=> StringToUInt32Hash(id.Value.ToLowerInvariant(), md5);
|
||||
|
||||
public static UInt32 StringToUInt32Hash(string str, MD5 md5)
|
||||
{
|
||||
//calculate key based on MD5 hash instead of string.GetHashCode
|
||||
//to ensure consistent results across platforms
|
||||
byte[] inputBytes = Encoding.UTF8.GetBytes(str);
|
||||
byte[] hash = md5.ComputeHash(inputBytes);
|
||||
|
||||
UInt32 key = (UInt32)((str.Length & 0xff) << 24); //could use more of the hash here instead?
|
||||
key |= (UInt32)(hash[hash.Length - 3] << 16);
|
||||
key |= (UInt32)(hash[hash.Length - 2] << 8);
|
||||
key |= (UInt32)(hash[hash.Length - 1]);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a HSV value into a RGB value.
|
||||
/// </summary>
|
||||
/// <param name="hue">Value between 0 and 360</param>
|
||||
/// <param name="saturation">Value between 0 and 1</param>
|
||||
/// <param name="value">Value between 0 and 1</param>
|
||||
/// <see href="https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB">Reference</see>
|
||||
/// <returns></returns>
|
||||
public static Color HSVToRGB(float hue, float saturation, float value)
|
||||
{
|
||||
float c = value * saturation;
|
||||
|
||||
float h = Math.Clamp(hue, 0, 360) / 60f;
|
||||
|
||||
float x = c * (1 - Math.Abs(h % 2 - 1));
|
||||
|
||||
float r = 0,
|
||||
g = 0,
|
||||
b = 0;
|
||||
|
||||
if (0 <= h && h <= 1) { r = c; g = x; b = 0; }
|
||||
else if (1 < h && h <= 2) { r = x; g = c; b = 0; }
|
||||
else if (2 < h && h <= 3) { r = 0; g = c; b = x; }
|
||||
else if (3 < h && h <= 4) { r = 0; g = x; b = c; }
|
||||
else if (4 < h && h <= 5) { r = x; g = 0; b = c; }
|
||||
else if (5 < h && h <= 6) { r = c; g = 0; b = x; }
|
||||
|
||||
float m = value - c;
|
||||
|
||||
return new Color(r + m, g + m, b + m);
|
||||
}
|
||||
|
||||
public static Exception GetInnermost(this Exception e)
|
||||
{
|
||||
while (e.InnerException != null) { e = e.InnerException; }
|
||||
|
||||
return e;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Barotrauma;
|
||||
|
||||
/// <summary>
|
||||
/// Unit type, i.e. type with only one possible value.
|
||||
/// Can be used instead of void to form expressions and
|
||||
/// fill in generic parameters.
|
||||
/// </summary>
|
||||
public enum Unit { Value }
|
||||
@@ -0,0 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
public sealed class UnreachableCodeException : Exception
|
||||
{
|
||||
public UnreachableCodeException() : base(message: "Code that was supposed to be unreachable was executed.") { }
|
||||
}
|
||||
Reference in New Issue
Block a user