From 092961c2119c5881cb0517e5226458ca7315cc10 Mon Sep 17 00:00:00 2001 From: Nikita Grishin Date: Fri, 1 Nov 2024 00:10:37 +0300 Subject: [PATCH] recode basics project and tests --- Directory.Build.props | 35 +- Directory.Packages.props | 14 + ruleset.ruleset | 2 + src/Common/Basics/AccessorExtensions.cs | 33 + src/Common/Basics/ActionExecutionInfo.cs | 91 -- src/Common/Basics/AssembliesExtensions.cs | 278 ----- src/Common/Basics/AssemblyExtensions.cs | 235 ++++ src/Common/Basics/AsyncExtensions.cs | 72 +- src/Common/Basics/AsyncLazy.cs | 41 + .../Basics/AsyncOperationExecutionInfo.cs | 199 --- src/Common/Basics/AttributesExtensions.cs | 134 ++ src/Common/Basics/Basics.csproj | 82 +- src/Common/Basics/BooleanExtensions.cs | 9 + src/Common/Basics/Comparable.cs | 140 +-- src/Common/Basics/CompressionExtensions.cs | 62 +- src/Common/Basics/DeconstructExtensions.cs | 45 + src/Common/Basics/Deconstruction.cs | 93 -- .../Basics/Delegates/ActionExecutionInfo.cs | 68 + .../Delegates/AsyncOperationExecutionInfo.cs | 148 +++ .../Basics/Delegates/FunctionExecutionInfo.cs | 72 ++ .../Delegates/StatelessActionExecutionInfo.cs | 66 + .../StatelessFunctionExecutionInfo.cs | 68 + src/Common/Basics/DictionaryExtensions.cs | 138 +- .../Basics/Disposables/AsyncDisposable.cs | 19 + .../Disposables/AsyncDisposableAction.cs | 36 + .../Basics/Disposables/CompositeDisposable.cs | 28 + src/Common/Basics/Disposables/Disposable.cs | 35 + .../Basics/Disposables/DisposableAction.cs | 35 + .../Disposables/EmptyAsyncDisposable.cs | 12 + .../Basics/Disposables/EmptyDisposable.cs | 10 + src/Common/Basics/EnOrderingDirection.cs | 14 + src/Common/Basics/EnumExtensions.cs | 51 +- src/Common/Basics/EnumerableExtensions.cs | 496 +++----- .../Enumerations/EnOrderingDirection.cs | 18 - .../Enumerations/EnUnitOfWorkBehavior.cs | 26 - .../AssemblyByNameEqualityComparer.cs | 30 +- .../ReferenceEqualityComparer.cs | 31 +- src/Common/Basics/Equatable.cs | 86 +- src/Common/Basics/ExceptionExtensions.cs | 73 +- .../Exceptions/AttributeRequiredException.cs | 46 +- .../Basics/Exceptions/NotFoundException.cs | 20 +- .../Exceptions/TypeMismatchException.cs | 22 +- src/Common/Basics/Exclusive.cs | 51 - src/Common/Basics/ExecutionExtensions.cs | 151 +-- src/Common/Basics/ExpressionExtensions.cs | 27 + .../Expressions/ReplaceParameterVisitor.cs | 23 + .../UnwrapUnaryExpressionVisitor.cs | 26 + src/Common/Basics/FileSystemExtensions.cs | 284 ++--- src/Common/Basics/FunctionExecutionInfo.cs | 98 -- src/Common/Basics/Heap/BinaryHeap.cs | 380 ++++++ src/Common/Basics/Heap/HeapEntry.cs | 87 ++ src/Common/Basics/Heap/IHeap.cs | 27 + .../Basics/Heap/RootNodeChangedEventArgs.cs | 14 + src/Common/Basics/ICloneable.cs | 19 +- src/Common/Basics/ISafelyComparable.cs | 18 +- src/Common/Basics/ISafelyEquatable.cs | 18 +- src/Common/Basics/IntegerExtensions.cs | 179 +-- src/Common/Basics/IsExternalInit.cs | 20 - src/Common/Basics/LogicalExtensions.cs | 16 - src/Common/Basics/MemberExtensions.cs | 417 ++----- src/Common/Basics/MethodExecutionInfo.cs | 207 --- src/Common/Basics/MethodExtensions.cs | 45 +- src/Common/Basics/MethodFinder.cs | 127 -- src/Common/Basics/NullableObjectExtensions.cs | 27 - .../Basics/ObjectExtensions.DeepCopy.cs | 229 ++-- .../Basics/ObjectExtensions.Nullable.cs | 16 + .../Basics/ObjectExtensions.ToString.cs | 213 ++-- .../Basics/OrderedEnumerableExtensions.cs | 22 - src/Common/Basics/PredicateExtensions.cs | 175 +-- .../Basics/Primitives/AsyncAutoResetEvent.cs | 78 -- .../Basics/Primitives/AsyncCountdownEvent.cs | 109 -- .../Basics/Primitives/AsyncDisposable.cs | 40 - .../Primitives/AsyncDisposableAction.cs | 49 - src/Common/Basics/Primitives/AsyncLazy.cs | 52 - .../Primitives/AsyncManualResetEvent.cs | 80 -- .../Basics/Primitives/AsyncUnitOfWork.cs | 132 -- src/Common/Basics/Primitives/BinaryHeap.cs | 403 ------ .../Basics/Primitives/CompositeDisposable.cs | 37 - src/Common/Basics/Primitives/DeferredQueue.cs | 260 ---- src/Common/Basics/Primitives/Disposable.cs | 75 -- .../Basics/Primitives/DisposableAction.cs | 48 - .../Basics/Primitives/EmptyAsyncDisposable.cs | 19 - .../Basics/Primitives/EmptyDisposable.cs | 17 - src/Common/Basics/Primitives/HeapEntry.cs | 143 --- src/Common/Basics/Primitives/IAsyncQueue.cs | 27 - .../Basics/Primitives/IAsyncUnitOfWork.cs | 27 - src/Common/Basics/Primitives/IHeap.cs | 67 - src/Common/Basics/Primitives/IQueue.cs | 58 - src/Common/Basics/Primitives/MessageQueue.cs | 118 -- src/Common/Basics/Primitives/PriorityQueue.cs | 77 -- .../Primitives/RootNodeChangedEventArgs.cs | 28 - .../TaskCancellationCompletionSource.cs | 40 - src/Common/Basics/Queue/DeferredQueue.cs | 244 ++++ src/Common/Basics/Queue/IAsyncQueue.cs | 12 + src/Common/Basics/Queue/IQueue.cs | 20 + src/Common/Basics/Queue/MessageQueue.cs | 102 ++ src/Common/Basics/Queue/PriorityQueue.cs | 62 + src/Common/Basics/QueueExtensions.cs | 13 + .../Basics/Reflection/MethodExecutionInfo.cs | 147 +++ src/Common/Basics/Reflection/MethodFinder.cs | 116 ++ src/Common/Basics/SolutionExtensions.cs | 162 +-- .../Basics/StatelessActionExecutionInfo.cs | 87 -- .../Basics/StatelessFunctionExecutionInfo.cs | 92 -- src/Common/Basics/StreamExtensions.cs | 199 ++- src/Common/Basics/StringExtensions.cs | 102 +- .../AsyncAutoResetEvent.cs | 64 + .../AsyncCountdownEvent.cs | 86 ++ .../AsyncManualResetEvent.cs | 62 + .../SynchronizationPrimitives/Exclusive.cs | 41 + .../SynchronizationPrimitivesExtensions.cs | 180 ++- .../TaskCancellationCompletionSource.cs | 32 + src/Common/Basics/TaskExtensions.cs | 45 +- src/Common/Basics/TypeExtensions.cs | 1108 ++++++----------- src/Common/Basics/TypeInfo.cs | 177 ++- src/Common/Basics/TypeInfoStorage.cs | 112 +- src/Common/Basics/TypeNode.cs | 378 +++--- .../Basics/UnitOfWork/AsyncUnitOfWork.cs | 110 ++ .../Basics/UnitOfWork/EnUnitOfWorkBehavior.cs | 22 + .../Basics/UnitOfWork/IAsyncUnitOfWork.cs | 14 + .../AsyncSynchronizationPrimitivesTest.cs | 264 ++-- .../Tests/Basics.Test/AsyncUnitOfWorkTest.cs | 295 +++-- tests/Tests/Basics.Test/Basics.Test.csproj | 66 +- tests/Tests/Basics.Test/BasicsTestBase.cs | 27 +- tests/Tests/Basics.Test/DeepCopy/TestEnum.cs | 23 +- .../DeepCopy/TestReferenceWithSystemTypes.cs | 52 +- .../TestReferenceWithoutSystemTypes.cs | 109 +- tests/Tests/Basics.Test/DeferredQueueTest.cs | 398 +++--- .../Basics.Test/EnumerableExtensionsTest.cs | 398 +++--- .../ExecutionExtensionsActionsTest.cs | 212 ++-- .../ExecutionExtensionsFunctionsTest.cs | 270 ++-- tests/Tests/Basics.Test/HeapTest.cs | 171 ++- .../Tests/Basics.Test/MethodExtensionsTest.cs | 376 +++--- .../ObjectExtensionsDeepCopyTest.cs | 355 +++--- .../Basics.Test/OrderByDependencyTestData.cs | 64 - .../Tests/Basics.Test/StreamExtensionsTest.cs | 136 +- .../Tests/Basics.Test/StringExtensionsTest.cs | 30 +- tests/Tests/Basics.Test/TestExtensions.cs | 30 +- tests/Tests/Basics.Test/TestRecord.cs | 9 +- tests/Tests/Basics.Test/TypeExtensionsTest.cs | 554 ++++----- .../OrderByDependenciesTest.cs | 71 ++ .../OrderByDependencyTestData.cs | 61 + 141 files changed, 6749 insertions(+), 9124 deletions(-) create mode 100644 Directory.Packages.props create mode 100644 src/Common/Basics/AccessorExtensions.cs delete mode 100644 src/Common/Basics/ActionExecutionInfo.cs delete mode 100644 src/Common/Basics/AssembliesExtensions.cs create mode 100644 src/Common/Basics/AssemblyExtensions.cs create mode 100644 src/Common/Basics/AsyncLazy.cs delete mode 100644 src/Common/Basics/AsyncOperationExecutionInfo.cs create mode 100644 src/Common/Basics/AttributesExtensions.cs create mode 100644 src/Common/Basics/BooleanExtensions.cs create mode 100644 src/Common/Basics/DeconstructExtensions.cs delete mode 100644 src/Common/Basics/Deconstruction.cs create mode 100644 src/Common/Basics/Delegates/ActionExecutionInfo.cs create mode 100644 src/Common/Basics/Delegates/AsyncOperationExecutionInfo.cs create mode 100644 src/Common/Basics/Delegates/FunctionExecutionInfo.cs create mode 100644 src/Common/Basics/Delegates/StatelessActionExecutionInfo.cs create mode 100644 src/Common/Basics/Delegates/StatelessFunctionExecutionInfo.cs create mode 100644 src/Common/Basics/Disposables/AsyncDisposable.cs create mode 100644 src/Common/Basics/Disposables/AsyncDisposableAction.cs create mode 100644 src/Common/Basics/Disposables/CompositeDisposable.cs create mode 100644 src/Common/Basics/Disposables/Disposable.cs create mode 100644 src/Common/Basics/Disposables/DisposableAction.cs create mode 100644 src/Common/Basics/Disposables/EmptyAsyncDisposable.cs create mode 100644 src/Common/Basics/Disposables/EmptyDisposable.cs create mode 100644 src/Common/Basics/EnOrderingDirection.cs delete mode 100644 src/Common/Basics/Enumerations/EnOrderingDirection.cs delete mode 100644 src/Common/Basics/Enumerations/EnUnitOfWorkBehavior.cs delete mode 100644 src/Common/Basics/Exclusive.cs create mode 100644 src/Common/Basics/ExpressionExtensions.cs create mode 100644 src/Common/Basics/Expressions/ReplaceParameterVisitor.cs create mode 100644 src/Common/Basics/Expressions/UnwrapUnaryExpressionVisitor.cs delete mode 100644 src/Common/Basics/FunctionExecutionInfo.cs create mode 100644 src/Common/Basics/Heap/BinaryHeap.cs create mode 100644 src/Common/Basics/Heap/HeapEntry.cs create mode 100644 src/Common/Basics/Heap/IHeap.cs create mode 100644 src/Common/Basics/Heap/RootNodeChangedEventArgs.cs delete mode 100644 src/Common/Basics/IsExternalInit.cs delete mode 100644 src/Common/Basics/LogicalExtensions.cs delete mode 100644 src/Common/Basics/MethodExecutionInfo.cs delete mode 100644 src/Common/Basics/MethodFinder.cs delete mode 100644 src/Common/Basics/NullableObjectExtensions.cs create mode 100644 src/Common/Basics/ObjectExtensions.Nullable.cs delete mode 100644 src/Common/Basics/OrderedEnumerableExtensions.cs delete mode 100644 src/Common/Basics/Primitives/AsyncAutoResetEvent.cs delete mode 100644 src/Common/Basics/Primitives/AsyncCountdownEvent.cs delete mode 100644 src/Common/Basics/Primitives/AsyncDisposable.cs delete mode 100644 src/Common/Basics/Primitives/AsyncDisposableAction.cs delete mode 100644 src/Common/Basics/Primitives/AsyncLazy.cs delete mode 100644 src/Common/Basics/Primitives/AsyncManualResetEvent.cs delete mode 100644 src/Common/Basics/Primitives/AsyncUnitOfWork.cs delete mode 100644 src/Common/Basics/Primitives/BinaryHeap.cs delete mode 100644 src/Common/Basics/Primitives/CompositeDisposable.cs delete mode 100644 src/Common/Basics/Primitives/DeferredQueue.cs delete mode 100644 src/Common/Basics/Primitives/Disposable.cs delete mode 100644 src/Common/Basics/Primitives/DisposableAction.cs delete mode 100644 src/Common/Basics/Primitives/EmptyAsyncDisposable.cs delete mode 100644 src/Common/Basics/Primitives/EmptyDisposable.cs delete mode 100644 src/Common/Basics/Primitives/HeapEntry.cs delete mode 100644 src/Common/Basics/Primitives/IAsyncQueue.cs delete mode 100644 src/Common/Basics/Primitives/IAsyncUnitOfWork.cs delete mode 100644 src/Common/Basics/Primitives/IHeap.cs delete mode 100644 src/Common/Basics/Primitives/IQueue.cs delete mode 100644 src/Common/Basics/Primitives/MessageQueue.cs delete mode 100644 src/Common/Basics/Primitives/PriorityQueue.cs delete mode 100644 src/Common/Basics/Primitives/RootNodeChangedEventArgs.cs delete mode 100644 src/Common/Basics/Primitives/TaskCancellationCompletionSource.cs create mode 100644 src/Common/Basics/Queue/DeferredQueue.cs create mode 100644 src/Common/Basics/Queue/IAsyncQueue.cs create mode 100644 src/Common/Basics/Queue/IQueue.cs create mode 100644 src/Common/Basics/Queue/MessageQueue.cs create mode 100644 src/Common/Basics/Queue/PriorityQueue.cs create mode 100644 src/Common/Basics/QueueExtensions.cs create mode 100644 src/Common/Basics/Reflection/MethodExecutionInfo.cs create mode 100644 src/Common/Basics/Reflection/MethodFinder.cs delete mode 100644 src/Common/Basics/StatelessActionExecutionInfo.cs delete mode 100644 src/Common/Basics/StatelessFunctionExecutionInfo.cs create mode 100644 src/Common/Basics/SynchronizationPrimitives/AsyncAutoResetEvent.cs create mode 100644 src/Common/Basics/SynchronizationPrimitives/AsyncCountdownEvent.cs create mode 100644 src/Common/Basics/SynchronizationPrimitives/AsyncManualResetEvent.cs create mode 100644 src/Common/Basics/SynchronizationPrimitives/Exclusive.cs create mode 100644 src/Common/Basics/TaskCancellationCompletionSource.cs create mode 100644 src/Common/Basics/UnitOfWork/AsyncUnitOfWork.cs create mode 100644 src/Common/Basics/UnitOfWork/EnUnitOfWorkBehavior.cs create mode 100644 src/Common/Basics/UnitOfWork/IAsyncUnitOfWork.cs delete mode 100644 tests/Tests/Basics.Test/OrderByDependencyTestData.cs create mode 100644 tests/Tests/CompositionRoot.Test/OrderByDependenciesTest.cs create mode 100644 tests/Tests/CompositionRoot.Test/OrderByDependencyTestData.cs diff --git a/Directory.Build.props b/Directory.Build.props index 22851dc0..e58cd078 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,23 +1,34 @@ - latest - enable Nikita Grishin + true + + ..\..\..\ruleset.ruleset MightyMorphinSpaceEngineers + Copyright (c) 2023 + + true + full + + false + true + false + + false + + latest + + enable + git https://github.com/warning-explosive/Core - Copyright (c) 2023 - ..\..\..\ruleset.ruleset + + true true true - true - false - false - true - true - true + + netstandard2.1 true - full - true + true \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 00000000..58c1b7b9 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ruleset.ruleset b/ruleset.ruleset index c2730a5b..8528f8b6 100644 --- a/ruleset.ruleset +++ b/ruleset.ruleset @@ -5,6 +5,7 @@ + @@ -19,6 +20,7 @@ + diff --git a/src/Common/Basics/AccessorExtensions.cs b/src/Common/Basics/AccessorExtensions.cs new file mode 100644 index 00000000..4ca15d3b --- /dev/null +++ b/src/Common/Basics/AccessorExtensions.cs @@ -0,0 +1,33 @@ +namespace SpaceEngineers.Core.Basics; + +using System; +using System.Linq; +using System.Reflection; + +public static class AccessorExtensions +{ + private static readonly Type[] IsExternalInitTypes = + new[] + { + TypeExtensions.FindType("System.Private.CoreLib System.Runtime.CompilerServices.IsExternalInit"), + }; + + public static bool GetIsAccessible(this PropertyInfo property) + { + var getMethod = property.GetGetMethod(true); + return getMethod != null && getMethod.IsAccessible(); + } + + public static bool SetIsAccessible(this PropertyInfo property) + { + var setMethod = property.GetSetMethod(true); + return setMethod != null && setMethod.IsAccessible(); + } + + public static bool HasInitializer(this PropertyInfo propertyInfo) + { + return propertyInfo.SetMethod != default + && propertyInfo.SetMethod.ReturnParameter != null + && propertyInfo.SetMethod.ReturnParameter.GetRequiredCustomModifiers().Any(IsExternalInitTypes.Contains); + } +} \ No newline at end of file diff --git a/src/Common/Basics/ActionExecutionInfo.cs b/src/Common/Basics/ActionExecutionInfo.cs deleted file mode 100644 index 88f632fd..00000000 --- a/src/Common/Basics/ActionExecutionInfo.cs +++ /dev/null @@ -1,91 +0,0 @@ -namespace SpaceEngineers.Core.Basics -{ - using System; - using System.Collections.Generic; - - /// - /// ActionExecutionInfo - /// - /// TState type-argument - public class ActionExecutionInfo - { - private static readonly Action EmptyExceptionHandler = _ => { }; - - private readonly TState _state; - private readonly Action _clientAction; - private readonly IDictionary> _exceptionHandlers; - - private Action? _finallyAction; - - /// .ctor - /// State - /// Client action - public ActionExecutionInfo(TState state, Action clientAction) - { - _state = state; - _clientAction = clientAction; - _exceptionHandlers = new Dictionary>(); - } - - /// - /// Catch block - /// Catch exception of TException type - /// - /// Exception handler - /// Real exception type-argument - /// ActionExecutionInfo - public ActionExecutionInfo Catch(Action? exceptionHandler = null) - { - _exceptionHandlers[typeof(TException)] = exceptionHandler ?? EmptyExceptionHandler; - - return this; - } - - /// - /// Finally block - /// - /// Finally action - /// ActionExecutionInfo - public ActionExecutionInfo Finally(Action finallyAction) - { - _finallyAction = finallyAction; - - return this; - } - - /// - /// Invoke client's action - /// - public void Invoke() - { - try - { - _clientAction.Invoke(_state); - } - catch (Exception ex) when (ExecutionExtensions.CanBeCaught(ex.RealException())) - { - var realException = ex.RealException(); - var handled = false; - - foreach (var pair in _exceptionHandlers) - { - if (pair.Key.IsInstanceOfType(realException)) - { - pair.Value.Invoke(realException); - handled = true; - break; - } - } - - if (!handled) - { - throw realException.Rethrow(); - } - } - finally - { - _finallyAction?.Invoke(); - } - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/AssembliesExtensions.cs b/src/Common/Basics/AssembliesExtensions.cs deleted file mode 100644 index c0ac5dff..00000000 --- a/src/Common/Basics/AssembliesExtensions.cs +++ /dev/null @@ -1,278 +0,0 @@ -namespace SpaceEngineers.Core.Basics -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Reflection; - using System.Threading; - using EqualityComparers; - - /// - /// Assemblies extensions - /// - public static class AssembliesExtensions - { - private const string Dot = "."; - - private const string Duplicate = "xunit.runner.visualstudio.dotnetcore.testadapter"; - - private static readonly string[] ExcludedAssemblies = new[] - { - nameof(System), - nameof(Microsoft), - "Windows" - }; - - private static readonly string[] RootAssemblies = new[] - { - "SpaceEngineers.Core.Basics", - "SpaceEngineers.Core.AutoRegistration.Api", - "SpaceEngineers.Core.CompositionRoot", - - "SpaceEngineers.Core.Analyzers.Api", - "SpaceEngineers.Core.Benchmark.Api" - }; - - private static readonly Lazy<(Assembly[] OurAssemblies, Assembly[] AllAssemblies)> AllAssembliesLoadedInCurrentAppDomain - = new Lazy<(Assembly[] OurAssemblies, Assembly[] AllAssemblies)>(WarmUpAppDomain, LazyThreadSafetyMode.ExecutionAndPublication); - - /// - /// Gets assembly version - /// - /// Assembly - /// Assembly version - public static string GetAssemblyVersion(this Assembly assembly) - { - return assembly.GetCustomAttribute()?.Version - ?? assembly.GetCustomAttribute()?.Version - ?? assembly.GetCustomAttribute()?.InformationalVersion - ?? "1.0.0.0"; - } - - /// - /// Build name - /// - /// Name parts - /// Built name - public static string BuildName(params string[] nameParts) - { - return nameParts.ToString(Dot); - } - - /// - /// Find required assembly in current AppDomain - /// - /// Assembly short name - /// Found assembly - public static Assembly FindRequiredAssembly(string assemblyName) - { - return FindAssembly(assemblyName) - ?? throw new InvalidOperationException($"Assembly {assemblyName} should be found in current {nameof(AppDomain)}"); - } - - /// - /// Find assembly in current AppDomain - /// - /// Assembly short name - /// Found assembly - public static Assembly? FindAssembly(string assemblyName) - { - return AllAssembliesFromCurrentDomain() - .SingleOrDefault(assembly => assembly.GetName().Name.Equals(assemblyName, StringComparison.Ordinal)); - } - - /// - /// Get all assemblies from current app domain - /// - /// All assemblies from current app domain - public static Assembly[] AllAssembliesFromCurrentDomain() - { - return AllAssembliesLoadedInCurrentAppDomain.Value.AllAssemblies; - } - - /// - /// Get all our assemblies from current app domain - /// - /// All our assemblies from current app domain - public static Assembly[] AllOurAssembliesFromCurrentDomain() - { - return AllAssembliesLoadedInCurrentAppDomain.Value.OurAssemblies; - } - - /// - /// Is our reference - /// - /// All assemblies - /// Root assemblies - /// Is our reference func - public static Func IsOurReference( - IEnumerable assemblies, - IEnumerable rootAssemblies) - { - var map = assemblies.ToDictionary(assembly => assembly.GetName().FullName); - - var visited = rootAssemblies - .Distinct(new AssemblyByNameEqualityComparer()) - .ToDictionary(root => root.GetName().FullName, _ => true); - - return assembly => IsOurReferenceInternal(assembly, map, visited); - - static bool IsOurReferenceInternal( - Assembly assembly, - IReadOnlyDictionary map, - IDictionary visited) - { - var key = string.Intern(assembly.GetName().FullName); - - if (visited.ContainsKey(key) && visited[key]) - { - return true; - } - - var exclusiveReferences = assembly.GetReferencedAssemblies(); - - var isReferencedDirectly = exclusiveReferences - .Any(assemblyName => - visited.ContainsKey(assemblyName.FullName) - && visited[assemblyName.FullName] - && ExcludedAssemblies.All(ex => !assemblyName.FullName.StartsWith(ex, StringComparison.OrdinalIgnoreCase))); - - if (isReferencedDirectly) - { - visited[key] = true; - return true; - } - - var isIndirectlyReferenced = exclusiveReferences - .Where(unknownReference => - !visited.ContainsKey(unknownReference.FullName) - && ExcludedAssemblies.All(ex => !unknownReference.FullName.StartsWith(ex, StringComparison.OrdinalIgnoreCase))) - .Any(unknownReference => - map.TryGetValue(unknownReference.FullName, out var unknownAssembly) - && IsOurReferenceInternal(unknownAssembly, map, visited)); - - visited[key] = isIndirectlyReferenced; - return isIndirectlyReferenced; - } - } - - /// - /// Get all assemblies referenced directly or indirectly to specified one - /// - /// All assemblies - /// Bound assembly - /// All assemblies below bound includes bound - public static Assembly[] Below(this Assembly[] allAssemblies, Assembly assembly) - { - var all = allAssemblies - .Union(new[] { assembly }) - .Distinct(new AssemblyByNameEqualityComparer()) - .ToDictionary(a => string.Intern(a.GetName().FullName)); - - var visited = new HashSet(); - - return BelowReference(assembly.GetName(), all, visited).ToArray(); - } - - private static IEnumerable BelowReference( - AssemblyName assemblyName, - IReadOnlyDictionary all, - HashSet visited) - { - var key = string.Intern(assemblyName.FullName); - - if (!visited.Add(key)) - { - return Enumerable.Empty(); - } - - if (!all.TryGetValue(key, out var assembly)) - { - return Enumerable.Empty(); - } - - return new[] { assembly } - .Concat(assembly - .GetReferencedAssemblies() - .SelectMany(name => BelowReference(name, all, visited))); - } - - private static (Assembly[] OurAssemblies, Assembly[] AllAssemblies) WarmUpAppDomain() - { - var loaded = new HashSet(); - - _ = Directory - .GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll", SearchOption.TopDirectoryOnly) - .Select(AssemblyName.GetAssemblyName) - .SelectMany(name => LoadReferences(name, loaded)) - .ToList(); - - var allAssemblies = AppDomain.CurrentDomain - .GetAssemblies() - .GroupBy(assembly => assembly.GetName().Name) - .SelectMany(RemoveDuplicates) - .ToArray(); - - var rootAssemblies = RootAssemblies - .Select(optionalAssemblyName => allAssemblies - .SingleOrDefault(a => a.GetName().Name.Equals(optionalAssemblyName, StringComparison.OrdinalIgnoreCase))) - .Where(optionalAssembly => optionalAssembly != null); - - var isOurReference = IsOurReference(allAssemblies, rootAssemblies); - - var ourAssemblies = allAssemblies.Where(isOurReference).ToArray(); - - return (ourAssemblies, allAssemblies); - } - - private static IEnumerable LoadReferences(AssemblyName assemblyName, HashSet loaded) - { - var name = string.Intern(assemblyName.FullName); - - if (!loaded.Add(name)) - { - return Enumerable.Empty(); - } - - if (assemblyName.ContentType == AssemblyContentType.WindowsRuntime) - { - return Enumerable.Empty(); - } - - var assembly = LoadByName(assemblyName); - - if (assembly == null) - { - return Enumerable.Empty(); - } - - return new[] { assembly } - .Concat(assembly - .GetReferencedAssemblies() - .SelectMany(referenceName => LoadReferences(referenceName, loaded))); - } - - private static Assembly? LoadByName(AssemblyName assemblyName) - { - return ExecutionExtensions - .Try(AppDomain.CurrentDomain.Load, assemblyName) - .Catch() - .Invoke(_ => default); - } - - private static IEnumerable RemoveDuplicates(IGrouping grp) - { - if (grp.Key.Equals(Duplicate, StringComparison.OrdinalIgnoreCase)) - { - yield return grp.First(); - yield break; - } - - foreach (var item in grp) - { - yield return item; - } - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/AssemblyExtensions.cs b/src/Common/Basics/AssemblyExtensions.cs new file mode 100644 index 00000000..7af495c6 --- /dev/null +++ b/src/Common/Basics/AssemblyExtensions.cs @@ -0,0 +1,235 @@ +namespace SpaceEngineers.Core.Basics; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading; +using EqualityComparers; + +public static class AssemblyExtensions +{ + private const string Dot = "."; + + private const string Duplicate = "xunit.runner.visualstudio.dotnetcore.testadapter"; + + private static readonly string[] ExcludedAssemblies = new[] + { + nameof(System), + nameof(Microsoft), + "Windows" + }; + + private static readonly string[] RootAssemblies = new[] + { + "SpaceEngineers.Core.Basics", + "SpaceEngineers.Core.AutoRegistration.Api", + "SpaceEngineers.Core.CompositionRoot", + + "SpaceEngineers.Core.Analyzers.Api", + "SpaceEngineers.Core.Benchmark.Api" + }; + + private static readonly Lazy<(Assembly[] OurAssemblies, Assembly[] AllAssemblies)> AllAssembliesLoadedInCurrentAppDomain + = new Lazy<(Assembly[] OurAssemblies, Assembly[] AllAssemblies)>(WarmUpAppDomain, LazyThreadSafetyMode.ExecutionAndPublication); + + public static string GetAssemblyVersion(this Assembly assembly) + { + return assembly.GetCustomAttribute()?.Version + ?? assembly.GetCustomAttribute()?.Version + ?? assembly.GetCustomAttribute()?.InformationalVersion + ?? "1.0.0.0"; + } + + public static string BuildName(params string[] nameParts) + { + return nameParts.ToString(Dot); + } + + public static Assembly FindRequiredAssembly(string assemblyName) + { + return FindAssembly(assemblyName) + ?? throw new InvalidOperationException($"Assembly {assemblyName} should be found in current {nameof(AppDomain)}"); + } + + public static Assembly? FindAssembly(string assemblyName) + { + return AllAssembliesFromCurrentDomain() + .SingleOrDefault(assembly => assembly.GetName().Name.Equals(assemblyName, StringComparison.Ordinal)); + } + + public static Assembly[] AllAssembliesFromCurrentDomain() + { + return AllAssembliesLoadedInCurrentAppDomain.Value.AllAssemblies; + } + + public static Assembly[] AllOurAssembliesFromCurrentDomain() + { + return AllAssembliesLoadedInCurrentAppDomain.Value.OurAssemblies; + } + + // TODO: remove "our" references + public static Func IsOurReference( + IEnumerable assemblies, + IEnumerable rootAssemblies) + { + var map = assemblies.ToDictionary(assembly => assembly.GetName().FullName); + + var visited = rootAssemblies + .Distinct(new AssemblyByNameEqualityComparer()) + .ToDictionary(root => root.GetName().FullName, _ => true); + + return assembly => IsOurReferenceInternal(assembly, map, visited); + + static bool IsOurReferenceInternal( + Assembly assembly, + IReadOnlyDictionary map, + IDictionary visited) + { + var key = string.Intern(assembly.GetName().FullName); + + if (visited.ContainsKey(key) && visited[key]) + { + return true; + } + + var exclusiveReferences = assembly.GetReferencedAssemblies(); + + var isReferencedDirectly = exclusiveReferences + .Any(assemblyName => + visited.ContainsKey(assemblyName.FullName) + && visited[assemblyName.FullName] + && ExcludedAssemblies.All(ex => !assemblyName.FullName.StartsWith(ex, StringComparison.OrdinalIgnoreCase))); + + if (isReferencedDirectly) + { + visited[key] = true; + return true; + } + + var isIndirectlyReferenced = exclusiveReferences + .Where(unknownReference => + !visited.ContainsKey(unknownReference.FullName) + && ExcludedAssemblies.All(ex => !unknownReference.FullName.StartsWith(ex, StringComparison.OrdinalIgnoreCase))) + .Any(unknownReference => + map.TryGetValue(unknownReference.FullName, out var unknownAssembly) + && IsOurReferenceInternal(unknownAssembly, map, visited)); + + visited[key] = isIndirectlyReferenced; + return isIndirectlyReferenced; + } + } + + public static Assembly[] Below(this Assembly[] allAssemblies, Assembly assembly) + { + var all = allAssemblies + .Union(new[] { assembly }) + .Distinct(new AssemblyByNameEqualityComparer()) + .ToDictionary(a => string.Intern(a.GetName().FullName)); + + var visited = new HashSet(); + + return BelowReference(assembly.GetName(), all, visited).ToArray(); + } + + private static IEnumerable BelowReference( + AssemblyName assemblyName, + IReadOnlyDictionary all, + HashSet visited) + { + var key = string.Intern(assemblyName.FullName); + + if (!visited.Add(key)) + { + return Enumerable.Empty(); + } + + if (!all.TryGetValue(key, out var assembly)) + { + return Enumerable.Empty(); + } + + return new[] { assembly } + .Concat(assembly + .GetReferencedAssemblies() + .SelectMany(name => BelowReference(name, all, visited))); + } + + private static (Assembly[] OurAssemblies, Assembly[] AllAssemblies) WarmUpAppDomain() + { + var loaded = new HashSet(); + + _ = Directory + .GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll", SearchOption.TopDirectoryOnly) + .Select(AssemblyName.GetAssemblyName) + .SelectMany(name => LoadReferences(name, loaded)) + .ToList(); + + var allAssemblies = AppDomain.CurrentDomain + .GetAssemblies() + .GroupBy(assembly => assembly.GetName().Name) + .SelectMany(RemoveDuplicates) + .ToArray(); + + var rootAssemblies = RootAssemblies + .Select(optionalAssemblyName => allAssemblies + .SingleOrDefault(a => a.GetName().Name.Equals(optionalAssemblyName, StringComparison.OrdinalIgnoreCase))) + .Where(optionalAssembly => optionalAssembly != null); + + var isOurReference = IsOurReference(allAssemblies, rootAssemblies); + + var ourAssemblies = allAssemblies.Where(isOurReference).ToArray(); + + return (ourAssemblies, allAssemblies); + } + + private static IEnumerable LoadReferences(AssemblyName assemblyName, HashSet loaded) + { + var name = string.Intern(assemblyName.FullName); + + if (!loaded.Add(name)) + { + return Enumerable.Empty(); + } + + if (assemblyName.ContentType == AssemblyContentType.WindowsRuntime) + { + return Enumerable.Empty(); + } + + var assembly = LoadByName(assemblyName); + + if (assembly == null) + { + return Enumerable.Empty(); + } + + return new[] { assembly } + .Concat(assembly + .GetReferencedAssemblies() + .SelectMany(referenceName => LoadReferences(referenceName, loaded))); + } + + private static Assembly? LoadByName(AssemblyName assemblyName) + { + return ExecutionExtensions + .Try(AppDomain.CurrentDomain.Load, assemblyName) + .Catch() + .Invoke(_ => default); + } + + private static IEnumerable RemoveDuplicates(IGrouping grp) + { + if (grp.Key.Equals(Duplicate, StringComparison.OrdinalIgnoreCase)) + { + yield return grp.First(); + yield break; + } + + foreach (var item in grp) + { + yield return item; + } + } +} \ No newline at end of file diff --git a/src/Common/Basics/AsyncExtensions.cs b/src/Common/Basics/AsyncExtensions.cs index 0cb3a788..e29a6e8c 100644 --- a/src/Common/Basics/AsyncExtensions.cs +++ b/src/Common/Basics/AsyncExtensions.cs @@ -1,56 +1,34 @@ -namespace SpaceEngineers.Core.Basics -{ - using System.Collections.Generic; - using System.Threading; - using System.Threading.Tasks; +namespace SpaceEngineers.Core.Basics; - /// - /// AsyncExtensions - /// - public static class AsyncExtensions - { - /// - /// WhenAll extension method - /// - /// Source - /// Composite WhenAll task - public static Task WhenAll(this IEnumerable source) - { - return Task.WhenAll(source); - } +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; - /// - /// WhenAll extension method - /// - /// Source - /// TResult type-argument - /// Composite WhenAll task - public static Task WhenAll(this IEnumerable> source) - { - return Task.WhenAll(source); - } +public static class AsyncExtensions +{ + public static Task WhenAll(this IEnumerable source) + { + return Task.WhenAll(source); + } - /// - /// Converts Enumerable source to IAsyncEnumerable source - /// - /// Source - /// Cancellation token - /// T type-argument - /// Enumerable over async enumerable source - public static async Task> AsEnumerable(this IAsyncEnumerable source, CancellationToken token) - { - var list = new List(); + public static Task WhenAll(this IEnumerable> source) + { + return Task.WhenAll(source); + } - var asyncSource = source - .WithCancellation(token) - .ConfigureAwait(false); + public static async Task> AsEnumerable(this IAsyncEnumerable source, CancellationToken token) + { + var list = new List(); - await foreach (var item in asyncSource) - { - list.Add(item); - } + var asyncSource = source + .WithCancellation(token) + .ConfigureAwait(false); - return list; + await foreach (var item in asyncSource) + { + list.Add(item); } + + return list; } } \ No newline at end of file diff --git a/src/Common/Basics/AsyncLazy.cs b/src/Common/Basics/AsyncLazy.cs new file mode 100644 index 00000000..d925077c --- /dev/null +++ b/src/Common/Basics/AsyncLazy.cs @@ -0,0 +1,41 @@ +namespace SpaceEngineers.Core.Basics; + +using System; +using System.Threading; +using System.Threading.Tasks; +using SynchronizationPrimitives; + +public class AsyncLazy +{ + private readonly AsyncManualResetEvent _manualResetEvent; + private readonly Func> _producer; + + private T? _value; + private int _produced; + + public AsyncLazy(Func> producer) + { + _manualResetEvent = new AsyncManualResetEvent(false); + _producer = producer; + + _value = default; + _produced = 0; + } + + public async Task GetValue(CancellationToken? token = null) + { + token ??= CancellationToken.None; + + if (Interlocked.Exchange(ref _produced, 1) == 1) + { + await _manualResetEvent.WaitAsync(token.Value).ConfigureAwait(false); + } + else + { + _value = await _producer(token.Value).ConfigureAwait(false); + _manualResetEvent.Set(); + } + + return _value !; + } +} \ No newline at end of file diff --git a/src/Common/Basics/AsyncOperationExecutionInfo.cs b/src/Common/Basics/AsyncOperationExecutionInfo.cs deleted file mode 100644 index 42f9a928..00000000 --- a/src/Common/Basics/AsyncOperationExecutionInfo.cs +++ /dev/null @@ -1,199 +0,0 @@ -namespace SpaceEngineers.Core.Basics -{ - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Threading; - using System.Threading.Tasks; - - /// - /// AsyncOperationExecutionInfo - /// - public class AsyncOperationExecutionInfo - { - private static readonly Func EmptyExceptionHandler = (_, _) => Task.CompletedTask; - - private readonly Task _asyncOperation; - private readonly bool _configureAwait; - private readonly IDictionary> _exceptionHandlers; - - private Func? _finallyAction; - - /// .cctor - /// Async operation - /// Configure await option - public AsyncOperationExecutionInfo( - Task asyncOperation, - bool configureAwait = false) - { - _asyncOperation = asyncOperation; - _configureAwait = configureAwait; - _exceptionHandlers = new Dictionary>(); - } - - /// - /// Async catch block - /// Catch exception of TException type - /// - /// Async exception handler - /// Real exception type-argument - /// AsyncOperationExecutionInfo - public AsyncOperationExecutionInfo Catch(Func? exceptionHandler = null) - { - _exceptionHandlers[typeof(TException)] = exceptionHandler ?? EmptyExceptionHandler; - - return this; - } - - /// - /// Async finally block - /// - /// Finally action factory - /// AsyncOperationExecutionInfo - public AsyncOperationExecutionInfo Finally(Func finallyActionFactory) - { - _finallyAction = finallyActionFactory; - - return this; - } - - /// - /// Invoke client's async operation - /// - /// Cancellation token - /// Ongoing client async action wrapped with error handling - public async Task Invoke(CancellationToken token) - { - try - { - await _asyncOperation.ConfigureAwait(_configureAwait); - } - catch (Exception ex) when (ExecutionExtensions.CanBeCaught(ex.RealException())) - { - var realException = ex.RealException(); - var handled = false; - - foreach (var pair in _exceptionHandlers) - { - if (pair.Key.IsInstanceOfType(realException)) - { - await pair.Value.Invoke(realException, token).ConfigureAwait(_configureAwait); - handled = true; - break; - } - } - - if (!handled) - { - throw realException.Rethrow(); - } - } - finally - { - if (_finallyAction != null) - { - await _finallyAction.Invoke(token).ConfigureAwait(_configureAwait); - } - } - } - } - - /// - /// AsyncOperationExecutionInfo - /// - /// TResult type-argument - [SuppressMessage("Analysis", "SA1402", Justification = "generic data type with the same name")] - public class AsyncOperationExecutionInfo - { - private static readonly Func EmptyExceptionHandler = (_, _) => Task.CompletedTask; - - private readonly Task _asyncOperation; - private readonly bool _configureAwait; - private readonly IDictionary> _exceptionHandlers; - - private Func? _finallyAction; - - /// .cctor - /// Client async operation factory - /// Configure await option - public AsyncOperationExecutionInfo( - Task asyncOperation, - bool configureAwait = false) - { - _asyncOperation = asyncOperation; - _configureAwait = configureAwait; - _exceptionHandlers = new Dictionary>(); - } - - /// - /// Async catch block - /// Catch exception of TException type - /// - /// Async exception handler - /// Real exception type-argument - /// AsyncOperationExecutionInfo - public AsyncOperationExecutionInfo Catch(Func? exceptionHandler = null) - { - _exceptionHandlers[typeof(TException)] = exceptionHandler ?? EmptyExceptionHandler; - - return this; - } - - /// - /// Async finally block - /// - /// Finally action factory - /// AsyncOperationExecutionInfo - public AsyncOperationExecutionInfo Finally(Func finallyActionFactory) - { - _finallyAction = finallyActionFactory; - - return this; - } - - /// - /// Invoke client's async operation - /// - /// Creates result from handled exception - /// Cancellation token - /// Ongoing client async action wrapped with error handling - public async Task Invoke( - Func exceptionResultFactory, - CancellationToken token) - { - try - { - return await _asyncOperation.ConfigureAwait(_configureAwait); - } - catch (Exception ex) when (ExecutionExtensions.CanBeCaught(ex.RealException())) - { - var realException = ex.RealException(); - var handled = false; - - foreach (var pair in _exceptionHandlers) - { - if (pair.Key.IsInstanceOfType(realException)) - { - await pair.Value.Invoke(realException, token).ConfigureAwait(_configureAwait); - handled = true; - break; - } - } - - if (!handled) - { - throw realException.Rethrow(); - } - - return exceptionResultFactory(realException); - } - finally - { - if (_finallyAction != null) - { - await _finallyAction.Invoke(token).ConfigureAwait(_configureAwait); - } - } - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/AttributesExtensions.cs b/src/Common/Basics/AttributesExtensions.cs new file mode 100644 index 00000000..2ea8d594 --- /dev/null +++ b/src/Common/Basics/AttributesExtensions.cs @@ -0,0 +1,134 @@ +namespace SpaceEngineers.Core.Basics; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Exceptions; + +public static class AttributesExtensions +{ + public static TAttribute GetRequiredAttribute(this Type type) + where TAttribute : Attribute + { + return TypeInfoStorage + .Get(type) + .Attributes + .OfType() + .InformativeSingleOrDefault(Amb) + ?? throw new AttributeRequiredException(typeof(TAttribute), type); + + static string Amb(IEnumerable arg) + { + return $"Type has more than one {typeof(TAttribute)}"; + } + } + + public static IEnumerable GetAttributes(this Type type) + where TAttribute : Attribute + { + return TypeInfoStorage + .Get(type) + .Attributes + .OfType(); + } + + public static TAttribute? GetAttribute(this Type type) + where TAttribute : Attribute + { + return TypeInfoStorage + .Get(type) + .Attributes + .OfType() + .InformativeSingleOrDefault(Amb); + + static string Amb(IEnumerable arg) + { + return $"Type has more than one {typeof(TAttribute)}"; + } + } + + public static bool HasAttribute(this Type type) + where TAttribute : Attribute + { + return TypeInfoStorage + .Get(type) + .Attributes + .OfType() + .Any(); + } + + public static bool HasAttribute(this Type type, Type attributeType) + { + return TypeInfoStorage + .Get(type) + .Attributes + .Any(attributeType.IsInstanceOfType); + } + + public static TAttribute GetRequiredAttribute(this MemberInfo memberInfo) + where TAttribute : Attribute + { + return memberInfo + .GetCustomAttributes() + .InformativeSingleOrDefault(Amb) + ?? throw new AttributeRequiredException(typeof(TAttribute), memberInfo); + + static string Amb(IEnumerable arg) + { + return $"Type has more than one {typeof(TAttribute)}"; + } + } + + public static TAttribute? GetAttribute(this MemberInfo memberInfo) + where TAttribute : Attribute + { + return memberInfo + .GetCustomAttributes() + .InformativeSingleOrDefault(Amb); + + static string Amb(IEnumerable arg) + { + return $"Type has more than one {typeof(TAttribute)}"; + } + } + + public static bool HasAttribute(this MemberInfo memberInfo) + where TAttribute : Attribute + { + return memberInfo.GetCustomAttributes().Any(); + } + + public static TAttribute GetRequiredAttribute(this MethodInfo methodInfo) + where TAttribute : Attribute + { + return methodInfo + .GetCustomAttributes() + .InformativeSingleOrDefault(Amb) + ?? throw new AttributeRequiredException(typeof(TAttribute), methodInfo); + + static string Amb(IEnumerable arg) + { + return $"Type has more than one {typeof(TAttribute)}"; + } + } + + public static TAttribute? GetAttribute(this MethodInfo methodInfo) + where TAttribute : Attribute + { + return methodInfo + .GetCustomAttributes() + .InformativeSingleOrDefault(Amb); + + static string Amb(IEnumerable arg) + { + return $"Type has more than one {typeof(TAttribute)}"; + } + } + + public static bool HasAttribute(this MethodInfo methodInfo) + where TAttribute : Attribute + { + return methodInfo.GetCustomAttributes().Any(); + } +} \ No newline at end of file diff --git a/src/Common/Basics/Basics.csproj b/src/Common/Basics/Basics.csproj index f79799d7..6250d5c1 100644 --- a/src/Common/Basics/Basics.csproj +++ b/src/Common/Basics/Basics.csproj @@ -1,90 +1,12 @@  - netstandard2.1 SpaceEngineers.Core.Basics SpaceEngineers.Core.Basics true - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - ExecutionExtensions.cs - - - ExecutionExtensions.cs - - - MethodExtensions.cs - - - MethodExtensions.cs - - - IHeap.cs - - - IHeap.cs - - - IQueue.cs - - - IAsyncQueue.cs - - - IAsyncQueue.cs - - - IQueue.cs - - - Comparable.cs - - - Equatable.cs - - - IHeap.cs - - - ExecutionExtensions.cs - - - ExecutionExtensions.cs - - - ExecutionExtensions.cs - - - TypeInfoStorage.cs - - - TypeExtensions.cs - - - Disposable.cs - - - Disposable.cs - - - Disposable.cs - - - AsyncDisposable.cs - - - AsyncDisposable.cs - - - ExecutionExtensions.cs - + + \ No newline at end of file diff --git a/src/Common/Basics/BooleanExtensions.cs b/src/Common/Basics/BooleanExtensions.cs new file mode 100644 index 00000000..c56a53e9 --- /dev/null +++ b/src/Common/Basics/BooleanExtensions.cs @@ -0,0 +1,9 @@ +namespace SpaceEngineers.Core.Basics; + +public static class BooleanExtensions +{ + public static int Bit(this bool condition) + { + return condition ? 1 : 0; + } +} \ No newline at end of file diff --git a/src/Common/Basics/Comparable.cs b/src/Common/Basics/Comparable.cs index 24863e44..d029f1f9 100644 --- a/src/Common/Basics/Comparable.cs +++ b/src/Common/Basics/Comparable.cs @@ -1,108 +1,62 @@ -namespace SpaceEngineers.Core.Basics +namespace SpaceEngineers.Core.Basics; + +using System; + +public static class Comparable { - using System; + public static bool Less(T? left, T? right) + where T : ISafelyComparable + { + return Compare(left, right) < 0; + } - /// - /// Comparable - /// - public static class Comparable + public static bool Greater(T? left, T? right) + where T : ISafelyComparable { - /// - /// GreaterOrEquals - /// - /// Left - /// Right - /// T type-argument - /// Comparison result - public static bool Less(T? left, T? right) - where T : ISafelyComparable - { - return Compare(left, right) < 0; - } + return Compare(left, right) > 0; + } - /// - /// GreaterOrEquals - /// - /// Left - /// Right - /// T type-argument - /// Comparison result - public static bool Greater(T? left, T? right) - where T : ISafelyComparable - { - return Compare(left, right) > 0; - } + public static bool LessOrEquals(T? left, T? right) + where T : ISafelyComparable + { + return Compare(left, right) <= 0; + } - /// - /// GreaterOrEquals - /// - /// Left - /// Right - /// T type-argument - /// Comparison result - public static bool LessOrEquals(T? left, T? right) - where T : ISafelyComparable - { - return Compare(left, right) <= 0; - } + public static bool GreaterOrEquals(T? left, T? right) + where T : ISafelyComparable + { + return Compare(left, right) >= 0; + } - /// - /// GreaterOrEquals - /// - /// Left - /// Right - /// T type-argument - /// Comparison result - public static bool GreaterOrEquals(T? left, T? right) - where T : ISafelyComparable - { - return Compare(left, right) >= 0; - } + public static int CompareTo(T source, object? obj) + where T : ISafelyComparable + { + return obj is T other + ? Compare(source, other) + : throw new ArgumentException($"Object should be of type {typeof(T).FullName}"); + } - /// - /// CompareTo - /// - /// Source - /// Object - /// T type-argument - /// Comparison result - public static int CompareTo(T source, object? obj) - where T : ISafelyComparable - { - return obj is T other - ? Compare(source, other) - : throw new ArgumentException($"Object should be of type {typeof(T).FullName}"); - } + public static int CompareTo(T source, T? other) + where T : ISafelyComparable + { + return Compare(source, other); + } - /// - /// CompareTo - /// - /// Source - /// Other - /// T type-argument - /// Comparison result - public static int CompareTo(T source, T? other) - where T : ISafelyComparable + private static int Compare(T? left, T? right) + where T : ISafelyComparable + { + if (ReferenceEquals(null, left)) { - return Compare(source, other); + return 1; } - private static int Compare(T? left, T? right) - where T : ISafelyComparable + if (ReferenceEquals(null, right)) { - if (ReferenceEquals(null, left)) - { - return 1; - } - - if (ReferenceEquals(null, right)) - { - return 1; - } - - return ReferenceEquals(left, right) - ? 0 - : left.SafeCompareTo(right); + return 1; } + + return ReferenceEquals(left, right) + ? 0 + : left.SafeCompareTo(right); } } \ No newline at end of file diff --git a/src/Common/Basics/CompressionExtensions.cs b/src/Common/Basics/CompressionExtensions.cs index 231c1554..fc2baa4e 100644 --- a/src/Common/Basics/CompressionExtensions.cs +++ b/src/Common/Basics/CompressionExtensions.cs @@ -1,50 +1,36 @@ -namespace SpaceEngineers.Core.Basics -{ - using System; - using System.IO; - using System.IO.Compression; +namespace SpaceEngineers.Core.Basics; + +using System; +using System.IO; +using System.IO.Compression; - /// - /// Compression extensions - /// - public static class CompressionExtensions +public static class CompressionExtensions +{ + public static ReadOnlyMemory Compress(this ReadOnlySpan bytes) { - /// - /// Compresses data - /// - /// Decompressed data - /// Compressed data - public static ReadOnlyMemory Compress(this ReadOnlySpan bytes) + using (var to = new MemoryStream()) + using (var zipStream = new GZipStream(to, CompressionMode.Compress, leaveOpen: false)) { - using (var to = new MemoryStream()) - using (var zipStream = new GZipStream(to, CompressionMode.Compress, leaveOpen: false)) - { - zipStream.Write(bytes); + zipStream.Write(bytes); - zipStream.Close(); // committing changes into underlying stream + zipStream.Close(); // committing changes into underlying stream - return to.AsBytes(); - } + return to.AsBytes(); } + } - /// - /// Decompresses data - /// - /// Compressed data - /// Decompressed data - public static ReadOnlyMemory Decompress(this ReadOnlySpan bytes) + public static ReadOnlyMemory Decompress(this ReadOnlySpan bytes) + { + using (var from = bytes.AsMemoryStream()) + using (var zipStream = new GZipStream(from, CompressionMode.Decompress, leaveOpen: false)) { - using (var from = bytes.AsMemoryStream()) - using (var zipStream = new GZipStream(from, CompressionMode.Decompress, leaveOpen: false)) + try + { + return zipStream.AsBytes(); + } + finally { - try - { - return zipStream.AsBytes(); - } - finally - { - zipStream.Close(); - } + zipStream.Close(); } } } diff --git a/src/Common/Basics/DeconstructExtensions.cs b/src/Common/Basics/DeconstructExtensions.cs new file mode 100644 index 00000000..f6d7f094 --- /dev/null +++ b/src/Common/Basics/DeconstructExtensions.cs @@ -0,0 +1,45 @@ +namespace SpaceEngineers.Core.Basics; + +using System.Collections.Generic; +using System.Linq; + +public static class DeconstructExtensions +{ + public static void Deconstruct( + this IGrouping grouping, + out TKey key, + out IEnumerable values) + { + key = grouping.Key; + values = grouping; + } + + public static void Deconstruct(this IEnumerable source, out T first, out IEnumerable rest) + { + first = source.FirstOrDefault(); + rest = source.Skip(1); + } + + public static void Deconstruct(this IEnumerable source, out T first, out T second, out IEnumerable rest) + => (first, (second, rest)) = source; + + public static void Deconstruct(this IEnumerable source, out T first, out T second, out T third, out IEnumerable rest) + => (first, second, (third, rest)) = source; + + public static IEnumerable ConstructEnumerable(this (T first, T second) source) + { + var (first, second) = source; + + yield return first; + yield return second; + } + + public static IEnumerable ConstructEnumerable(this (T first, T second, T third) source) + { + var (first, second, third) = source; + + yield return first; + yield return second; + yield return third; + } +} \ No newline at end of file diff --git a/src/Common/Basics/Deconstruction.cs b/src/Common/Basics/Deconstruction.cs deleted file mode 100644 index 002f9616..00000000 --- a/src/Common/Basics/Deconstruction.cs +++ /dev/null @@ -1,93 +0,0 @@ -namespace SpaceEngineers.Core.Basics -{ - using System.Collections.Generic; - using System.Linq; - - /// - /// Deconstruction - /// - public static class Deconstruction - { - /// - /// Deconstructs IGrouping - /// - /// IGrouping - /// Key - /// Values - /// TKey type-argument - /// TValue type-argument - public static void Deconstruct( - this IGrouping grouping, - out TKey key, - out IEnumerable values) - { - key = grouping.Key; - values = grouping; - } - - /// - /// Deconstructs source collection - /// - /// Source - /// First element - /// Rest elements - /// T type-argument - public static void Deconstruct(this IEnumerable source, out T first, out IEnumerable rest) - { - first = source.FirstOrDefault(); - rest = source.Skip(1); - } - - /// - /// Deconstructs source collection - /// - /// Source - /// First element - /// Second element - /// Rest elements - /// T type-argument - public static void Deconstruct(this IEnumerable source, out T first, out T second, out IEnumerable rest) - => (first, (second, rest)) = source; - - /// - /// Deconstructs source collection - /// - /// Source - /// First element - /// Second element - /// Third element - /// Rest elements - /// T type-argument - public static void Deconstruct(this IEnumerable source, out T first, out T second, out T third, out IEnumerable rest) - => (first, second, (third, rest)) = source; - - /// - /// Constructs source stream - /// - /// Source - /// T type-argument - /// Stream - public static IEnumerable ConstructEnumerable(this (T first, T second) source) - { - var (first, second) = source; - - yield return first; - yield return second; - } - - /// - /// Constructs source stream - /// - /// Source - /// T type-argument - /// Stream - public static IEnumerable ConstructEnumerable(this (T first, T second, T third) source) - { - var (first, second, third) = source; - - yield return first; - yield return second; - yield return third; - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/Delegates/ActionExecutionInfo.cs b/src/Common/Basics/Delegates/ActionExecutionInfo.cs new file mode 100644 index 00000000..d4905495 --- /dev/null +++ b/src/Common/Basics/Delegates/ActionExecutionInfo.cs @@ -0,0 +1,68 @@ +namespace SpaceEngineers.Core.Basics.Delegates; + +using System; +using System.Collections.Generic; + +public class ActionExecutionInfo +{ + private static readonly Action EmptyExceptionHandler = _ => { }; + + private readonly TState _state; + private readonly Action _clientAction; + private readonly IDictionary> _exceptionHandlers; + + private Action? _finallyAction; + + public ActionExecutionInfo(TState state, Action clientAction) + { + _state = state; + _clientAction = clientAction; + _exceptionHandlers = new Dictionary>(); + } + + public ActionExecutionInfo Catch(Action? exceptionHandler = null) + { + _exceptionHandlers[typeof(TException)] = exceptionHandler ?? EmptyExceptionHandler; + + return this; + } + + public ActionExecutionInfo Finally(Action finallyAction) + { + _finallyAction = finallyAction; + + return this; + } + + public void Invoke() + { + try + { + _clientAction.Invoke(_state); + } + catch (Exception ex) when (ExecutionExtensions.CanBeCaught(ex.RealException())) + { + var realException = ex.RealException(); + var handled = false; + + foreach (var pair in _exceptionHandlers) + { + if (pair.Key.IsInstanceOfType(realException)) + { + pair.Value.Invoke(realException); + handled = true; + break; + } + } + + if (!handled) + { + throw realException.Rethrow(); + } + } + finally + { + _finallyAction?.Invoke(); + } + } +} \ No newline at end of file diff --git a/src/Common/Basics/Delegates/AsyncOperationExecutionInfo.cs b/src/Common/Basics/Delegates/AsyncOperationExecutionInfo.cs new file mode 100644 index 00000000..8895f050 --- /dev/null +++ b/src/Common/Basics/Delegates/AsyncOperationExecutionInfo.cs @@ -0,0 +1,148 @@ +namespace SpaceEngineers.Core.Basics.Delegates; + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +public class AsyncOperationExecutionInfo +{ + private static readonly Func EmptyExceptionHandler = (_, _) => Task.CompletedTask; + + private readonly Task _asyncOperation; + private readonly bool _configureAwait; + private readonly IDictionary> _exceptionHandlers; + + private Func? _finallyAction; + + public AsyncOperationExecutionInfo( + Task asyncOperation, + bool configureAwait = false) + { + _asyncOperation = asyncOperation; + _configureAwait = configureAwait; + _exceptionHandlers = new Dictionary>(); + } + + public AsyncOperationExecutionInfo Catch(Func? exceptionHandler = null) + { + _exceptionHandlers[typeof(TException)] = exceptionHandler ?? EmptyExceptionHandler; + + return this; + } + + public AsyncOperationExecutionInfo Finally(Func finallyActionFactory) + { + _finallyAction = finallyActionFactory; + + return this; + } + + public async Task Invoke(CancellationToken token) + { + try + { + await _asyncOperation.ConfigureAwait(_configureAwait); + } + catch (Exception ex) when (ExecutionExtensions.CanBeCaught(ex.RealException())) + { + var realException = ex.RealException(); + var handled = false; + + foreach (var pair in _exceptionHandlers) + { + if (pair.Key.IsInstanceOfType(realException)) + { + await pair.Value.Invoke(realException, token).ConfigureAwait(_configureAwait); + handled = true; + break; + } + } + + if (!handled) + { + throw realException.Rethrow(); + } + } + finally + { + if (_finallyAction != null) + { + await _finallyAction.Invoke(token).ConfigureAwait(_configureAwait); + } + } + } +} + +public class AsyncOperationExecutionInfo +{ + private static readonly Func EmptyExceptionHandler = (_, _) => Task.CompletedTask; + + private readonly Task _asyncOperation; + private readonly bool _configureAwait; + private readonly IDictionary> _exceptionHandlers; + + private Func? _finallyAction; + + public AsyncOperationExecutionInfo( + Task asyncOperation, + bool configureAwait = false) + { + _asyncOperation = asyncOperation; + _configureAwait = configureAwait; + _exceptionHandlers = new Dictionary>(); + } + + public AsyncOperationExecutionInfo Catch(Func? exceptionHandler = null) + { + _exceptionHandlers[typeof(TException)] = exceptionHandler ?? EmptyExceptionHandler; + + return this; + } + + public AsyncOperationExecutionInfo Finally(Func finallyActionFactory) + { + _finallyAction = finallyActionFactory; + + return this; + } + + public async Task Invoke( + Func exceptionResultFactory, + CancellationToken token) + { + try + { + return await _asyncOperation.ConfigureAwait(_configureAwait); + } + catch (Exception ex) when (ExecutionExtensions.CanBeCaught(ex.RealException())) + { + var realException = ex.RealException(); + var handled = false; + + foreach (var pair in _exceptionHandlers) + { + if (pair.Key.IsInstanceOfType(realException)) + { + await pair.Value.Invoke(realException, token).ConfigureAwait(_configureAwait); + handled = true; + break; + } + } + + if (!handled) + { + throw realException.Rethrow(); + } + + return exceptionResultFactory(realException); + } + finally + { + if (_finallyAction != null) + { + await _finallyAction.Invoke(token).ConfigureAwait(_configureAwait); + } + } + } +} \ No newline at end of file diff --git a/src/Common/Basics/Delegates/FunctionExecutionInfo.cs b/src/Common/Basics/Delegates/FunctionExecutionInfo.cs new file mode 100644 index 00000000..1c6ce409 --- /dev/null +++ b/src/Common/Basics/Delegates/FunctionExecutionInfo.cs @@ -0,0 +1,72 @@ +namespace SpaceEngineers.Core.Basics.Delegates; + +using System; +using System.Collections.Generic; + +public class FunctionExecutionInfo +{ + private static readonly Action EmptyExceptionHandler = _ => { }; + + private readonly TState _state; + private readonly Func _clientFunction; + private readonly IDictionary> _exceptionHandlers; + + private Action? _finallyAction; + + public FunctionExecutionInfo( + TState state, + Func clientFunction) + { + _state = state; + _clientFunction = clientFunction; + _exceptionHandlers = new Dictionary>(); + } + + public FunctionExecutionInfo Catch(Action? exceptionHandler = null) + { + _exceptionHandlers[typeof(TException)] = exceptionHandler ?? EmptyExceptionHandler; + + return this; + } + + public FunctionExecutionInfo Finally(Action finallyAction) + { + _finallyAction = finallyAction; + + return this; + } + + public TResult Invoke(Func exceptionResultFactory) + { + try + { + return _clientFunction.Invoke(_state); + } + catch (Exception ex) when (ExecutionExtensions.CanBeCaught(ex.RealException())) + { + var realException = ex.RealException(); + var handled = false; + + foreach (var pair in _exceptionHandlers) + { + if (pair.Key.IsInstanceOfType(realException)) + { + pair.Value.Invoke(realException); + handled = true; + break; + } + } + + if (!handled) + { + throw realException.Rethrow(); + } + + return exceptionResultFactory.Invoke(realException); + } + finally + { + _finallyAction?.Invoke(); + } + } +} \ No newline at end of file diff --git a/src/Common/Basics/Delegates/StatelessActionExecutionInfo.cs b/src/Common/Basics/Delegates/StatelessActionExecutionInfo.cs new file mode 100644 index 00000000..1c90b84c --- /dev/null +++ b/src/Common/Basics/Delegates/StatelessActionExecutionInfo.cs @@ -0,0 +1,66 @@ +namespace SpaceEngineers.Core.Basics.Delegates; + +using System; +using System.Collections.Generic; + +public class StatelessActionExecutionInfo +{ + private static readonly Action EmptyExceptionHandler = _ => { }; + + private readonly Action _clientAction; + private readonly IDictionary> _exceptionHandlers; + + private Action? _finallyAction; + + public StatelessActionExecutionInfo(Action clientAction) + { + _clientAction = clientAction; + _exceptionHandlers = new Dictionary>(); + } + + public StatelessActionExecutionInfo Catch(Action? exceptionHandler = null) + { + _exceptionHandlers[typeof(TException)] = exceptionHandler ?? EmptyExceptionHandler; + + return this; + } + + public StatelessActionExecutionInfo Finally(Action finallyAction) + { + _finallyAction = finallyAction; + + return this; + } + + public void Invoke() + { + try + { + _clientAction.Invoke(); + } + catch (Exception ex) when (ExecutionExtensions.CanBeCaught(ex.RealException())) + { + var realException = ex.RealException(); + var handled = false; + + foreach (var pair in _exceptionHandlers) + { + if (pair.Key.IsInstanceOfType(realException)) + { + pair.Value.Invoke(realException); + handled = true; + break; + } + } + + if (!handled) + { + throw realException.Rethrow(); + } + } + finally + { + _finallyAction?.Invoke(); + } + } +} \ No newline at end of file diff --git a/src/Common/Basics/Delegates/StatelessFunctionExecutionInfo.cs b/src/Common/Basics/Delegates/StatelessFunctionExecutionInfo.cs new file mode 100644 index 00000000..41b07b00 --- /dev/null +++ b/src/Common/Basics/Delegates/StatelessFunctionExecutionInfo.cs @@ -0,0 +1,68 @@ +namespace SpaceEngineers.Core.Basics.Delegates; + +using System; +using System.Collections.Generic; + +public class StatelessFunctionExecutionInfo +{ + private static readonly Action EmptyExceptionHandler = _ => { }; + + private readonly Func _clientFunction; + private readonly IDictionary> _exceptionHandlers; + + private Action? _finallyAction; + + public StatelessFunctionExecutionInfo(Func clientFunction) + { + _clientFunction = clientFunction; + _exceptionHandlers = new Dictionary>(); + } + + public StatelessFunctionExecutionInfo Catch(Action? exceptionHandler = null) + { + _exceptionHandlers[typeof(TException)] = exceptionHandler ?? EmptyExceptionHandler; + + return this; + } + + public StatelessFunctionExecutionInfo Finally(Action finallyAction) + { + _finallyAction = finallyAction; + + return this; + } + + public TResult Invoke(Func exceptionResultFactory) + { + try + { + return _clientFunction.Invoke(); + } + catch (Exception ex) when (ExecutionExtensions.CanBeCaught(ex.RealException())) + { + var realException = ex.RealException(); + var handled = false; + + foreach (var pair in _exceptionHandlers) + { + if (pair.Key.IsInstanceOfType(realException)) + { + pair.Value.Invoke(realException); + handled = true; + break; + } + } + + if (!handled) + { + throw realException.Rethrow(); + } + + return exceptionResultFactory.Invoke(realException); + } + finally + { + _finallyAction?.Invoke(); + } + } +} \ No newline at end of file diff --git a/src/Common/Basics/DictionaryExtensions.cs b/src/Common/Basics/DictionaryExtensions.cs index 9ef169f9..2d7e519c 100644 --- a/src/Common/Basics/DictionaryExtensions.cs +++ b/src/Common/Basics/DictionaryExtensions.cs @@ -1,103 +1,63 @@ -namespace SpaceEngineers.Core.Basics -{ - using System; - using System.Collections.Concurrent; - using System.Collections.Generic; +namespace SpaceEngineers.Core.Basics; + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; - /// - /// System.Collections.Generic.Dictionary extensions - /// - public static class DictionaryExtensions +public static class DictionaryExtensions +{ + public static TValue AddOrUpdate( + this IDictionary dictionary, + TKey key, + Func add, + Func update) { - /// - /// Add or update - /// - /// Dictionary - /// Key - /// Add producer - /// Update producer - /// TKey type-argument - /// TValue type-argument - /// Added or updated value - public static TValue AddOrUpdate( - this IDictionary dictionary, - TKey key, - Func add, - Func update) + if (dictionary.ContainsKey(key)) { - if (dictionary.ContainsKey(key)) - { - var value = dictionary[key]; - var newValue = update(key, value); - dictionary[key] = newValue; - return newValue; - } - else - { - var newValue = add(key); - dictionary[key] = newValue; - return newValue; - } + var value = dictionary[key]; + var newValue = update(key, value); + dictionary[key] = newValue; + return newValue; } - - /// - /// Get or add - /// - /// Dictionary - /// Key - /// Value producer - /// TKey type-argument - /// TValue type-argument - /// Existed or produced value - public static TValue GetOrAdd( - this IDictionary dictionary, - TKey key, - Func producer) + else { - if (dictionary.TryGetValue(key, out var taken)) - { - return taken; - } - - var value = producer(key); - dictionary[key] = value; - return value; + var newValue = add(key); + dictionary[key] = newValue; + return newValue; } + } - /// - /// Get or add - /// - /// Dictionary - /// Key - /// Value - /// TKey type-argument - /// TValue type-argument - /// Existed or produced value - public static TValue GetOrAdd( - this IDictionary dictionary, - TKey key, - TValue value) + public static TValue GetOrAdd( + this IDictionary dictionary, + TKey key, + Func producer) + { + if (dictionary.TryGetValue(key, out var taken)) { - if (dictionary.TryGetValue(key, out var taken)) - { - return taken; - } - - dictionary[key] = value; - return value; + return taken; } - /// - /// Add entry into ConcurrentDictionary - /// - /// ConcurrentDictionary - /// Key - /// Value - /// TKey type-argument - /// TValue type-argument - public static void Add(this ConcurrentDictionary dictionary, TKey key, TValue value) + var value = producer(key); + dictionary[key] = value; + return value; + } + + public static TValue GetOrAdd( + this IDictionary dictionary, + TKey key, + TValue value) + { + if (dictionary.TryGetValue(key, out var taken)) { - (dictionary as IDictionary).Add(key, value); + return taken; } + + dictionary[key] = value; + return value; + } + + public static void Add(this ConcurrentDictionary dictionary, TKey key, TValue value) + { + (dictionary as IDictionary).Add(key, value); } } \ No newline at end of file diff --git a/src/Common/Basics/Disposables/AsyncDisposable.cs b/src/Common/Basics/Disposables/AsyncDisposable.cs new file mode 100644 index 00000000..f16d47c8 --- /dev/null +++ b/src/Common/Basics/Disposables/AsyncDisposable.cs @@ -0,0 +1,19 @@ +namespace SpaceEngineers.Core.Basics.Disposables; + +using System; +using System.Threading.Tasks; + +public static class AsyncDisposable +{ + public static EmptyAsyncDisposable Empty { get; } = default; + + public static AsyncDisposableAction Create(Func finallyAction) + { + return new AsyncDisposableAction(finallyAction.Invoke); + } + + public static AsyncDisposableAction Create(TState state, Func finallyAction) + { + return new AsyncDisposableAction(state, finallyAction); + } +} \ No newline at end of file diff --git a/src/Common/Basics/Disposables/AsyncDisposableAction.cs b/src/Common/Basics/Disposables/AsyncDisposableAction.cs new file mode 100644 index 00000000..74f5423f --- /dev/null +++ b/src/Common/Basics/Disposables/AsyncDisposableAction.cs @@ -0,0 +1,36 @@ +namespace SpaceEngineers.Core.Basics.Disposables; + +using System; +using System.Threading.Tasks; + +public struct AsyncDisposableAction : IAsyncDisposable +{ + private readonly TState _state; + private readonly Func _finallyAction; + + internal AsyncDisposableAction(TState state, Func finallyAction) + { + _state = state; + _finallyAction = finallyAction; + } + + public async ValueTask DisposeAsync() + { + await _finallyAction.Invoke(_state).ConfigureAwait(false); + } +} + +public struct AsyncDisposableAction : IAsyncDisposable +{ + private readonly Func _finallyAction; + + internal AsyncDisposableAction(Func finallyAction) + { + _finallyAction = finallyAction; + } + + public async ValueTask DisposeAsync() + { + await _finallyAction.Invoke().ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/src/Common/Basics/Disposables/CompositeDisposable.cs b/src/Common/Basics/Disposables/CompositeDisposable.cs new file mode 100644 index 00000000..4fb016bd --- /dev/null +++ b/src/Common/Basics/Disposables/CompositeDisposable.cs @@ -0,0 +1,28 @@ +namespace SpaceEngineers.Core.Basics.Disposables; + +using System; +using System.Collections.Generic; + +public struct CompositeDisposable : IDisposable +{ + private readonly Stack _disposables = new Stack(); + + internal CompositeDisposable(params IDisposable[] disposables) + { + foreach (var disposable in disposables) + { + _disposables.Push(disposable); + } + } + + public void Push(IDisposable disposable) + { + _disposables.Push(disposable); + } + + public void Dispose() + { + _disposables.Each(d => d.Dispose()); + _disposables.Clear(); + } +} \ No newline at end of file diff --git a/src/Common/Basics/Disposables/Disposable.cs b/src/Common/Basics/Disposables/Disposable.cs new file mode 100644 index 00000000..2805dca0 --- /dev/null +++ b/src/Common/Basics/Disposables/Disposable.cs @@ -0,0 +1,35 @@ +namespace SpaceEngineers.Core.Basics.Disposables; + +using System; + +public static class Disposable +{ + public static EmptyDisposable Empty { get; } = default; + + public static DisposableAction Create(TState state, Action openScopeAction, Action finallyAction) + { + openScopeAction(state); + return new DisposableAction(state, finallyAction); + } + + public static DisposableAction Create(TState state, Action finallyAction) + { + return new DisposableAction(state, finallyAction); + } + + public static DisposableAction Create(Action openScopeAction, Action finallyAction) + { + openScopeAction(); + return new DisposableAction(finallyAction); + } + + public static DisposableAction Create(Action finallyAction) + { + return new DisposableAction(finallyAction); + } + + public static CompositeDisposable CreateComposite(params IDisposable[] disposables) + { + return new CompositeDisposable(disposables); + } +} \ No newline at end of file diff --git a/src/Common/Basics/Disposables/DisposableAction.cs b/src/Common/Basics/Disposables/DisposableAction.cs new file mode 100644 index 00000000..d913f653 --- /dev/null +++ b/src/Common/Basics/Disposables/DisposableAction.cs @@ -0,0 +1,35 @@ +namespace SpaceEngineers.Core.Basics.Disposables; + +using System; + +public struct DisposableAction : IDisposable +{ + private readonly TState _state; + private readonly Action _finallyAction; + + internal DisposableAction(TState state, Action finallyAction) + { + _state = state; + _finallyAction = finallyAction; + } + + public void Dispose() + { + _finallyAction.Invoke(_state); + } +} + +public struct DisposableAction : IDisposable +{ + private readonly Action _finallyAction; + + internal DisposableAction(Action finallyAction) + { + _finallyAction = finallyAction; + } + + public void Dispose() + { + _finallyAction.Invoke(); + } +} \ No newline at end of file diff --git a/src/Common/Basics/Disposables/EmptyAsyncDisposable.cs b/src/Common/Basics/Disposables/EmptyAsyncDisposable.cs new file mode 100644 index 00000000..6f8fd8e5 --- /dev/null +++ b/src/Common/Basics/Disposables/EmptyAsyncDisposable.cs @@ -0,0 +1,12 @@ +namespace SpaceEngineers.Core.Basics.Disposables; + +using System; +using System.Threading.Tasks; + +public struct EmptyAsyncDisposable : IAsyncDisposable +{ + public async ValueTask DisposeAsync() + { + await Task.CompletedTask.ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/src/Common/Basics/Disposables/EmptyDisposable.cs b/src/Common/Basics/Disposables/EmptyDisposable.cs new file mode 100644 index 00000000..34b73597 --- /dev/null +++ b/src/Common/Basics/Disposables/EmptyDisposable.cs @@ -0,0 +1,10 @@ +namespace SpaceEngineers.Core.Basics.Disposables; + +using System; + +public struct EmptyDisposable : IDisposable +{ + public void Dispose() + { + } +} \ No newline at end of file diff --git a/src/Common/Basics/EnOrderingDirection.cs b/src/Common/Basics/EnOrderingDirection.cs new file mode 100644 index 00000000..60944fc1 --- /dev/null +++ b/src/Common/Basics/EnOrderingDirection.cs @@ -0,0 +1,14 @@ +namespace SpaceEngineers.Core.Basics; + +public enum EnOrderingDirection +{ + /// + /// Ascending + /// + Asc = 0, + + /// + /// Descending + /// + Desc = 1 +} \ No newline at end of file diff --git a/src/Common/Basics/EnumExtensions.cs b/src/Common/Basics/EnumExtensions.cs index b6ea38fc..dccc44cb 100644 --- a/src/Common/Basics/EnumExtensions.cs +++ b/src/Common/Basics/EnumExtensions.cs @@ -1,35 +1,26 @@ -namespace SpaceEngineers.Core.Basics -{ - using System; - using System.Globalization; - using System.Linq; +namespace SpaceEngineers.Core.Basics; + +using System; +using System.Globalization; +using System.Linq; - /// - /// Enum extensions - /// - public static class EnumExtensions +public static class EnumExtensions +{ + public static Array EnumFlagsValues(this Enum source) { - /// - /// Get enum flags values flatten to array - /// - /// source - /// Flatten enum flags values - public static Array EnumFlagsValues(this Enum source) - { - var type = source.GetType(); + var type = source.GetType(); - return Enum - .GetValues(type) - .OfType() - .Where(flag => - { - // Checks whether x is a power of 2 - var value = Convert.ToInt64(flag, CultureInfo.InvariantCulture); - return value != 0 && (value & (value - 1)) == 0; - }) - .Where(source.HasFlag) - .Select(flag => Convert.ChangeType(flag, type, CultureInfo.InvariantCulture)) - .ToArray(); - } + return Enum + .GetValues(type) + .OfType() + .Where(flag => + { + // Checks whether x is a power of 2 + var value = Convert.ToInt64(flag, CultureInfo.InvariantCulture); + return value != 0 && (value & (value - 1)) == 0; + }) + .Where(source.HasFlag) + .Select(flag => Convert.ChangeType(flag, type, CultureInfo.InvariantCulture)) + .ToArray(); } } \ No newline at end of file diff --git a/src/Common/Basics/EnumerableExtensions.cs b/src/Common/Basics/EnumerableExtensions.cs index 3e902918..1df5c587 100644 --- a/src/Common/Basics/EnumerableExtensions.cs +++ b/src/Common/Basics/EnumerableExtensions.cs @@ -1,359 +1,261 @@ -namespace SpaceEngineers.Core.Basics +namespace SpaceEngineers.Core.Basics; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Exceptions; + +public static class EnumerableExtensions { - using System; - using System.Collections; - using System.Collections.Generic; - using System.Linq; - using System.Reflection; - using Exceptions; - - /// - /// Enumerable extensions - /// - public static class EnumerableExtensions + public static IEnumerable DistinctBy( + this IEnumerable source, + Func keySelector, + IEqualityComparer? comparer = null) { - /// - /// Distinct by specified key selector - /// - /// Source - /// Key selector - /// Custom equality comparer - /// TKey type-argument - /// TValue type-argument - /// Distinct source - public static IEnumerable DistinctBy( - this IEnumerable source, - Func keySelector, - IEqualityComparer? comparer = null) - { - return source - .GroupBy(keySelector, comparer) - .Select(it => it.First()); - } + return source + .GroupBy(keySelector, comparer) + .Select(it => it.First()); + } + + public static IEnumerable>> Stack( + this IEnumerable source, + Func keySelector) + { + return source + .Aggregate(new Stack>>(), Aggregate) + .Reverse() + .Select(pair => new KeyValuePair>(pair.Key, pair.Value)); - /// - /// Stacks source collection into separate piles defined by key selector - /// - /// Source - /// Key selector - /// TKey type-argument - /// TValue type-argument - /// Separate piles - public static IEnumerable>> Stack( - this IEnumerable source, - Func keySelector) + Stack>> Aggregate(Stack>> acc, TValue next) { - return source - .Aggregate(new Stack>>(), Aggregate) - .Reverse() - .Select(pair => new KeyValuePair>(pair.Key, pair.Value)); + var key = keySelector(next); - Stack>> Aggregate(Stack>> acc, TValue next) + if (!acc.TryPeek(out var peek) + || !EqualityComparer.Default.Equals(key, peek.Key)) { - var key = keySelector(next); - - if (!acc.TryPeek(out var peek) - || !EqualityComparer.Default.Equals(key, peek.Key)) - { - acc.Push(new KeyValuePair>(key, new List { next })); - } - else - { - peek.Value.Add(next); - } - - return acc; + acc.Push(new KeyValuePair>(key, new List { next })); + } + else + { + peek.Value.Add(next); } - } - /// - /// Full outer join - /// - /// Left source - /// Right source - /// Left key selector - /// Right key selector - /// Result selector (left/right could be null for reference types) - /// Custom equality comparer - /// TLeft type-argument - /// TRight type-argument - /// TKey type-argument - /// TResult type-argument - /// Full outer join result - public static IEnumerable FullOuterJoin( - this IEnumerable leftSource, - IEnumerable rightSource, - Func leftKeySelector, - Func rightKeySelector, - Func resultSelector, - IEqualityComparer? comparer = null) - { - var leftLookup = leftSource.ToLookup(leftKeySelector); - var rightLookup = rightSource.ToLookup(rightKeySelector); - - var keys = leftLookup - .Select(p => p.Key) - .Concat(rightLookup.Select(p => p.Key)) - .ToHashSet(comparer ?? EqualityComparer.Default); - - return from key in keys - from left in leftLookup[key].DefaultIfEmpty() - from right in rightLookup[key].DefaultIfEmpty() - select resultSelector(left, right); + return acc; } + } - /// - /// Left outer join - /// - /// Left source - /// Right source - /// Left key selector - /// Right key selector - /// Result selector (right could be null for reference types) - /// Custom equality comparer - /// TLeft type-argument - /// TRight type-argument - /// TKey type-argument - /// TResult type-argument - /// Left join result - public static IEnumerable LeftJoin( - this IEnumerable leftSource, - IEnumerable rightSource, - Func leftKeySelector, - Func rightKeySelector, - Func resultSelector, - IEqualityComparer? comparer = null) - { - var leftLookup = leftSource.ToLookup(leftKeySelector); - var rightLookup = rightSource.ToLookup(rightKeySelector); + public static IEnumerable FullOuterJoin( + this IEnumerable leftSource, + IEnumerable rightSource, + Func leftKeySelector, + Func rightKeySelector, + Func resultSelector, + IEqualityComparer? comparer = null) + { + var leftLookup = leftSource.ToLookup(leftKeySelector); + var rightLookup = rightSource.ToLookup(rightKeySelector); + + var keys = leftLookup + .Select(p => p.Key) + .Concat(rightLookup.Select(p => p.Key)) + .ToHashSet(comparer ?? EqualityComparer.Default); + + return from key in keys + from left in leftLookup[key].DefaultIfEmpty() + from right in rightLookup[key].DefaultIfEmpty() + select resultSelector(left, right); + } - var keys = leftLookup - .Select(p => p.Key) - .ToHashSet(comparer ?? EqualityComparer.Default); + public static IEnumerable LeftJoin( + this IEnumerable leftSource, + IEnumerable rightSource, + Func leftKeySelector, + Func rightKeySelector, + Func resultSelector, + IEqualityComparer? comparer = null) + { + var leftLookup = leftSource.ToLookup(leftKeySelector); + var rightLookup = rightSource.ToLookup(rightKeySelector); - return from key in keys - from left in leftLookup[key] - from right in rightLookup[key].DefaultIfEmpty() - select resultSelector(left, right); - } + var keys = leftLookup + .Select(p => p.Key) + .ToHashSet(comparer ?? EqualityComparer.Default); + + return from key in keys + from left in leftLookup[key] + from right in rightLookup[key].DefaultIfEmpty() + select resultSelector(left, right); + } + + public static IEnumerable Flatten( + this T source, + Func> unfold) + { + return new[] { source }.Concat(unfold(source).SelectMany(z => Flatten(z, unfold))); + } + + public static IEnumerable Flatten(this IEnumerable source, Func> unfold) + { + return source.SelectMany(item => item.Flatten(unfold)); + } - /// - /// Flatten source stream - /// - /// Source stream - /// Unfold function - /// Element type-argument - /// Flatten source - public static IEnumerable Flatten( - this T source, - Func> unfold) + // TODO: what is this + public static IEnumerable> ColumnsCartesianProduct(this IEnumerable> sourceColumns) + { + if (!sourceColumns.Any()) { - return new[] { source }.Concat(unfold(source).SelectMany(z => Flatten(z, unfold))); + return Enumerable.Empty>(); } - /// - /// Flatten source stream - /// - /// Source stream - /// Unfold function - /// Element type-argument - /// Flatten source - public static IEnumerable Flatten(this IEnumerable source, Func> unfold) + IEnumerable> seed = sourceColumns + .Take(1) + .Single() + .Select(item => new List { item }); + + return sourceColumns + .Skip(1) + .Aggregate(seed, Aggregate); + + static IEnumerable> Aggregate(IEnumerable> acc, IEnumerable next) { - return source.SelectMany(item => item.Flatten(unfold)); + return acc.Join(next, + _ => true, + _ => true, + (left, right) => new List(left) { right }); } + } - /// Produce cartesian product of source columns - /// Source columns - /// Item type-argument - /// Cartesian product of source columns - public static IEnumerable> ColumnsCartesianProduct(this IEnumerable> sourceColumns) + public static void Each(this IEnumerable source, Action action) + { + foreach (var item in source) { - if (!sourceColumns.Any()) - { - return Enumerable.Empty>(); - } - - IEnumerable> seed = sourceColumns - .Take(1) - .Single() - .Select(item => new List { item }); + action(item); + } + } - return sourceColumns - .Skip(1) - .Aggregate(seed, Aggregate); + public static void Each(this IEnumerable source, Action action) + { + using (var enumerator = source.GetEnumerator()) + { + var i = 0; - static IEnumerable> Aggregate(IEnumerable> acc, IEnumerable next) + while (enumerator.MoveNext()) { - return acc.Join(next, - _ => true, - _ => true, - (left, right) => new List(left) { right }); + action(enumerator.Current, i++); } } + } + + public static IEnumerable AsEnumerable(this IEnumerable enumerable) + { + return enumerable.GetEnumerator().AsEnumerable(); + } - /// Execute action on each element - /// A sequence of values to invoke an action on - /// An action to apply to each source element - /// The type of the elements of - public static void Each(this IEnumerable source, Action action) + public static IEnumerable AsEnumerable(this IEnumerator numerator) + { + while (numerator.MoveNext()) { - foreach (var item in source) + if (numerator.Current is T typed) { - action(item); + yield return typed; } } + } - /// Execute action on each element with index - /// A sequence of values to invoke an action on - /// An action to apply to each source element - /// The type of the elements of - public static void Each(this IEnumerable source, Action action) - { - using (var enumerator = source.GetEnumerator()) - { - var i = 0; + public static IOrderedEnumerable AsOrderedEnumerable(this IEnumerable source) + { + return source.OrderBy(_ => 1); + } - while (enumerator.MoveNext()) - { - action(enumerator.Current, i++); - } - } - } + public static IOrderedEnumerable OrderByDependencies( + this IEnumerable source, + Func getKey, + Func> getDependencies) + { + return source.OrderBy(SortFunc); - /// Select collection from untyped IEnumerable - /// IEnumerable - /// T type-argument - /// Collection of objects - public static IEnumerable AsEnumerable(this IEnumerable enumerable) + int SortFunc(TSource item) { - return enumerable.GetEnumerator().AsEnumerable(); - } + var key = getKey(item); + var dependencies = getDependencies(item).ToList(); + var dependenciesKeys = dependencies.Select(getKey).ToList(); - /// Select collection from IEnumerator - /// IEnumerator - /// T type-argument - /// Collection of objects - public static IEnumerable AsEnumerable(this IEnumerator numerator) - { - while (numerator.MoveNext()) + var depth = 0; + + while (dependenciesKeys.Any()) { - if (numerator.Current is T typed) + if (dependenciesKeys.Contains(key)) { - yield return typed; + throw new InvalidOperationException($"{key} has cycle dependency"); } + + ++depth; + + dependencies = dependencies.SelectMany(getDependencies).ToList(); + dependenciesKeys = dependencies.Select(getKey).ToList(); } + + return depth; } + } - /// - /// Enqueue ordered collection into queue instance - /// First the queue is cleared - /// - /// Target queue - /// Ordered collection - /// Item type-argument - /// Filled queue - public static Queue EnqueueMany(this Queue queue, IReadOnlyCollection source) - { - source.Each(queue.Enqueue); + public static T InformativeSingle(this IEnumerable source, Func, string> amb) + { + var items = source.Take(2).ToList(); - return queue; + if (!items.Any()) + { + throw new NotFoundException("Source collection is empty"); } - /// - /// Informative single extraction - /// - /// Source collection - /// Ambiguous message factory - /// Collection item type-argument - /// Single item with informative errors - /// Throws if source is empty - /// Throws if source contains more than one element - public static T InformativeSingle(this IEnumerable source, Func, string> amb) + if (items.Count != 1) { - var items = source.Take(2).ToList(); + throw new AmbiguousMatchException(amb(items)); + } - if (!items.Any()) - { - throw new NotFoundException("Source collection is empty"); - } + return items.Single(); + } - if (items.Count != 1) - { - throw new AmbiguousMatchException(amb(items)); - } + public static T InformativeSingle(this IEnumerable source, Func, string> amb, TState state) + { + var items = source.Take(2).ToList(); - return items.Single(); + if (!items.Any()) + { + throw new NotFoundException("Source collection is empty"); } - /// - /// Informative single extraction - /// - /// Source collection - /// Ambiguous message factory - /// State - /// T type-argument - /// TState type-argument - /// Single item with informative errors - /// Throws if source is empty - /// Throws if source contains more than one element - public static T InformativeSingle(this IEnumerable source, Func, string> amb, TState state) + if (items.Count != 1) { - var items = source.Take(2).ToList(); - - if (!items.Any()) - { - throw new NotFoundException("Source collection is empty"); - } - - if (items.Count != 1) - { - throw new AmbiguousMatchException(amb(state, items)); - } - - return items.Single(); + throw new AmbiguousMatchException(amb(state, items)); } - /// - /// Informative single or default extraction - /// - /// Source collection - /// Ambiguous message factory - /// Collection item type-argument - /// Single item with informative errors - /// Throws if source contains more than one element - public static T InformativeSingleOrDefault(this IEnumerable source, Func, string> amb) - { - var items = source.Take(2).ToList(); + return items.Single(); + } - if (items.Count >= 2) - { - throw new AmbiguousMatchException(amb(items)); - } + public static T InformativeSingleOrDefault(this IEnumerable source, Func, string> amb) + { + var items = source.Take(2).ToList(); - return items.SingleOrDefault(); + if (items.Count >= 2) + { + throw new AmbiguousMatchException(amb(items)); } - /// - /// Informative single or default extraction - /// - /// Source collection - /// Ambiguous message factory - /// State - /// T type-argument - /// TState type-argument - /// Single item with informative errors - /// Throws if source contains more than one element - public static T InformativeSingleOrDefault(this IEnumerable source, Func, string> amb, TState state) - { - var items = source.Take(2).ToList(); + return items.SingleOrDefault(); + } - if (items.Count >= 2) - { - throw new AmbiguousMatchException(amb(state, items)); - } + public static T InformativeSingleOrDefault(this IEnumerable source, Func, string> amb, TState state) + { + var items = source.Take(2).ToList(); - return items.SingleOrDefault(); + if (items.Count >= 2) + { + throw new AmbiguousMatchException(amb(state, items)); } + + return items.SingleOrDefault(); } } \ No newline at end of file diff --git a/src/Common/Basics/Enumerations/EnOrderingDirection.cs b/src/Common/Basics/Enumerations/EnOrderingDirection.cs deleted file mode 100644 index 42935d19..00000000 --- a/src/Common/Basics/Enumerations/EnOrderingDirection.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace SpaceEngineers.Core.Basics.Enumerations -{ - /// - /// EnOrderingDirection - /// - public enum EnOrderingDirection - { - /// - /// Ascending - /// - Asc = 0, - - /// - /// Descending - /// - Desc = 1 - } -} \ No newline at end of file diff --git a/src/Common/Basics/Enumerations/EnUnitOfWorkBehavior.cs b/src/Common/Basics/Enumerations/EnUnitOfWorkBehavior.cs deleted file mode 100644 index cd0ef32f..00000000 --- a/src/Common/Basics/Enumerations/EnUnitOfWorkBehavior.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace SpaceEngineers.Core.Basics.Enumerations -{ - /// - /// EnUnitOfWorkBehavior - /// - public enum EnUnitOfWorkBehavior - { - /// - /// Regular behavior - /// After successful transaction opening tries to execute producer and finish the unit of work gracefully - /// - Regular = 0, - - /// - /// Skip producer behavior - /// After successful transaction opening skips producer execution and tries finish the unit of work gracefully - /// - SkipProducer = 1, - - /// - /// Do not run behavior - /// The unit of work is considered as non started, producer execution and graceful finish will be skipped - /// - DoNotRun = 2 - } -} \ No newline at end of file diff --git a/src/Common/Basics/EqualityComparers/AssemblyByNameEqualityComparer.cs b/src/Common/Basics/EqualityComparers/AssemblyByNameEqualityComparer.cs index 9d3fb792..de640cb7 100644 --- a/src/Common/Basics/EqualityComparers/AssemblyByNameEqualityComparer.cs +++ b/src/Common/Basics/EqualityComparers/AssemblyByNameEqualityComparer.cs @@ -1,22 +1,18 @@ -namespace SpaceEngineers.Core.Basics.EqualityComparers -{ - using System; - using System.Collections.Generic; - using System.Reflection; +namespace SpaceEngineers.Core.Basics.EqualityComparers; + +using System; +using System.Collections.Generic; +using System.Reflection; - /// - public class AssemblyByNameEqualityComparer : EqualityComparer +public class AssemblyByNameEqualityComparer : EqualityComparer +{ + public override bool Equals(Assembly x, Assembly y) { - /// - public override bool Equals(Assembly x, Assembly y) - { - return string.Intern(x.GetName().FullName).Equals(string.Intern(y.GetName().FullName), StringComparison.Ordinal); - } + return string.Intern(x.GetName().FullName).Equals(string.Intern(y.GetName().FullName), StringComparison.Ordinal); + } - /// - public override int GetHashCode(Assembly obj) - { - return obj.GetName().FullName.GetHashCode(StringComparison.Ordinal); - } + public override int GetHashCode(Assembly obj) + { + return obj.GetName().FullName.GetHashCode(StringComparison.Ordinal); } } \ No newline at end of file diff --git a/src/Common/Basics/EqualityComparers/ReferenceEqualityComparer.cs b/src/Common/Basics/EqualityComparers/ReferenceEqualityComparer.cs index fd52d4f4..0af5fe4d 100644 --- a/src/Common/Basics/EqualityComparers/ReferenceEqualityComparer.cs +++ b/src/Common/Basics/EqualityComparers/ReferenceEqualityComparer.cs @@ -1,24 +1,17 @@ -namespace SpaceEngineers.Core.Basics.EqualityComparers -{ - using System.Collections.Generic; +namespace SpaceEngineers.Core.Basics.EqualityComparers; + +using System.Collections.Generic; - /// - /// Object ReferenceEqualityComparer - /// - /// Type-Argument - public class ReferenceEqualityComparer : EqualityComparer - where T : class +public class ReferenceEqualityComparer : EqualityComparer + where T : class +{ + public override bool Equals(T x, T y) { - /// - public override bool Equals(T x, T y) - { - return ReferenceEquals(x, y); - } + return ReferenceEquals(x, y); + } - /// - public override int GetHashCode(T? obj) - { - return obj == null ? 0 : obj.GetHashCode(); - } + public override int GetHashCode(T? obj) + { + return obj == null ? 0 : obj.GetHashCode(); } } \ No newline at end of file diff --git a/src/Common/Basics/Equatable.cs b/src/Common/Basics/Equatable.cs index 7c6e0180..84f44cb4 100644 --- a/src/Common/Basics/Equatable.cs +++ b/src/Common/Basics/Equatable.cs @@ -1,67 +1,49 @@ -namespace SpaceEngineers.Core.Basics +namespace SpaceEngineers.Core.Basics; + +public static class Equatable { - /// - /// Equatable - /// - public static class Equatable + public static bool Equals(T? source, T? other) + where T : ISafelyEquatable { - /// - /// Equals - /// - /// Source - /// Other - /// T type-argument - /// Comparison result - public static bool Equals(T? source, T? other) - where T : ISafelyEquatable + if (ReferenceEquals(source, other)) { - if (ReferenceEquals(source, other)) - { - return true; - } + return true; + } - if (ReferenceEquals(null, source) - || ReferenceEquals(null, other)) - { - return false; - } + if (ReferenceEquals(null, source) + || ReferenceEquals(null, other)) + { + return false; + } - var typeMatches = source.GetType() == other.GetType(); - var valueMatches = source.SafeEquals(other); + var typeMatches = source.GetType() == other.GetType(); + var valueMatches = source.SafeEquals(other); - return typeMatches && valueMatches; - } + return typeMatches && valueMatches; + } - /// - /// Equals - /// - /// Source - /// Object - /// T type-argument - /// Comparison result - public static bool Equals(T source, object? obj) - where T : ISafelyEquatable + public static bool Equals(T source, object? obj) + where T : ISafelyEquatable + { + if (ReferenceEquals(null, obj)) { - if (ReferenceEquals(null, obj)) - { - return false; - } + return false; + } - if (ReferenceEquals(source, obj)) - { - return true; - } + if (ReferenceEquals(source, obj)) + { + return true; + } - var typeMatches = source.GetType() == obj.GetType(); + var typeMatches = source.GetType() == obj.GetType(); - if (!typeMatches) - { - return false; - } + if (!typeMatches) + { + return false; + } - var valueMatches = source.SafeEquals((T)obj); + var valueMatches = source.SafeEquals((T)obj); - return valueMatches; - } + return valueMatches; } } \ No newline at end of file diff --git a/src/Common/Basics/ExceptionExtensions.cs b/src/Common/Basics/ExceptionExtensions.cs index ab799472..794f0904 100644 --- a/src/Common/Basics/ExceptionExtensions.cs +++ b/src/Common/Basics/ExceptionExtensions.cs @@ -1,54 +1,35 @@ -namespace SpaceEngineers.Core.Basics -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Reflection; - using System.Runtime.ExceptionServices; +namespace SpaceEngineers.Core.Basics; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.ExceptionServices; - /// - /// Exception extension methods - /// - public static class ExceptionExtensions +public static class ExceptionExtensions +{ + public static Exception Rethrow(this Exception exception) { - /// - /// Rethrows exception and keeps original stack trace - /// - /// Original exception - /// Exception to throw - public static Exception Rethrow(this Exception exception) - { - ExceptionDispatchInfo.Capture(exception).Throw(); - return exception; - } + ExceptionDispatchInfo.Capture(exception).Throw(); + return exception; + } - /// - /// Unwrap TargetInvocationException - /// - /// exception - /// Real exception hidden beside TargetInvocationException - public static Exception RealException(this Exception exception) - { - return exception - .Flatten() - .First(ex => ex is not TargetInvocationException - && ex is not AggregateException); - } + public static Exception RealException(this Exception exception) + { + return exception + .Flatten() + .First(ex => ex is not TargetInvocationException + && ex is not AggregateException); + } - /// - /// Gets flatten exception object hierarchy - /// - /// Source exception - /// Flatten exception object hierarchy - public static IEnumerable Flatten(this Exception exception) + public static IEnumerable Flatten(this Exception exception) + { + switch (exception) { - switch (exception) - { - case AggregateException a: return new[] { a }.Concat(a.Flatten().InnerExceptions.SelectMany(Flatten)); - default: return exception.InnerException != null - ? new[] { exception }.Concat(Flatten(exception.InnerException)) - : new[] { exception }; - } + case AggregateException a: return new[] { a }.Concat(a.Flatten().InnerExceptions.SelectMany(Flatten)); + default: return exception.InnerException != null + ? new[] { exception }.Concat(Flatten(exception.InnerException)) + : new[] { exception }; } } } \ No newline at end of file diff --git a/src/Common/Basics/Exceptions/AttributeRequiredException.cs b/src/Common/Basics/Exceptions/AttributeRequiredException.cs index de17ab24..18bc9bfd 100644 --- a/src/Common/Basics/Exceptions/AttributeRequiredException.cs +++ b/src/Common/Basics/Exceptions/AttributeRequiredException.cs @@ -1,36 +1,22 @@ -namespace SpaceEngineers.Core.Basics.Exceptions -{ - using System; - using System.Reflection; +namespace SpaceEngineers.Core.Basics.Exceptions; + +using System; +using System.Reflection; - /// - /// AttributeRequiredException - /// Type should be marked by attribute - /// - public sealed class AttributeRequiredException : Exception +public sealed class AttributeRequiredException : Exception +{ + public AttributeRequiredException(Type attributeType, Type notMarkedType) + : base($"Type {notMarkedType.FullName} is expected to be marked with {attributeType.FullName} attribute") { - /// .ctor - /// Type of required attribute - /// Type that isn't marked by attribute - public AttributeRequiredException(Type attributeType, Type notMarkedType) - : base($"Type {notMarkedType.FullName} is expected to be marked with {attributeType.FullName} attribute") - { - } + } - /// .ctor - /// Type of required attribute - /// Member that isn't marked by attribute - public AttributeRequiredException(Type attributeType, MemberInfo notMarkedMember) - : base($"Type {notMarkedMember.DeclaringType?.FullName}.{notMarkedMember.Name} is expected to be marked with {attributeType.FullName} attribute") - { - } + public AttributeRequiredException(Type attributeType, MemberInfo notMarkedMember) + : base($"Type {notMarkedMember.DeclaringType?.FullName}.{notMarkedMember.Name} is expected to be marked with {attributeType.FullName} attribute") + { + } - /// .ctor - /// Type of required attribute - /// method that isn't marked by attribute - public AttributeRequiredException(Type attributeType, MethodInfo notMarkedMethod) - : base($"Type {notMarkedMethod.DeclaringType?.FullName}.{notMarkedMethod.Name} is expected to be marked with {attributeType.FullName} attribute") - { - } + public AttributeRequiredException(Type attributeType, MethodInfo notMarkedMethod) + : base($"Type {notMarkedMethod.DeclaringType?.FullName}.{notMarkedMethod.Name} is expected to be marked with {attributeType.FullName} attribute") + { } } \ No newline at end of file diff --git a/src/Common/Basics/Exceptions/NotFoundException.cs b/src/Common/Basics/Exceptions/NotFoundException.cs index 875f3cac..ed3d3403 100644 --- a/src/Common/Basics/Exceptions/NotFoundException.cs +++ b/src/Common/Basics/Exceptions/NotFoundException.cs @@ -1,17 +1,11 @@ -namespace SpaceEngineers.Core.Basics.Exceptions -{ - using System; +namespace SpaceEngineers.Core.Basics.Exceptions; + +using System; - /// - /// NotFoundException - /// - public sealed class NotFoundException : Exception +public sealed class NotFoundException : Exception +{ + public NotFoundException(string message) + : base(message) { - /// .ctor - /// Exception message - public NotFoundException(string message) - : base(message) - { - } } } \ No newline at end of file diff --git a/src/Common/Basics/Exceptions/TypeMismatchException.cs b/src/Common/Basics/Exceptions/TypeMismatchException.cs index 74fda0c0..c37a3a38 100644 --- a/src/Common/Basics/Exceptions/TypeMismatchException.cs +++ b/src/Common/Basics/Exceptions/TypeMismatchException.cs @@ -1,19 +1,11 @@ -namespace SpaceEngineers.Core.Basics.Exceptions -{ - using System; +namespace SpaceEngineers.Core.Basics.Exceptions; + +using System; - /// - /// TypeMismatchException - /// Type is not accessible from expected type - /// - public sealed class TypeMismatchException : Exception +public sealed class TypeMismatchException : Exception +{ + public TypeMismatchException(Type expectedType, Type actualType) + : base($"{expectedType.FullName} is not accessible from {actualType.FullName}") { - /// .ctor - /// Expected type - /// Actual type - public TypeMismatchException(Type expectedType, Type actualType) - : base($"{expectedType.FullName} is not accessible from {actualType.FullName}") - { - } } } \ No newline at end of file diff --git a/src/Common/Basics/Exclusive.cs b/src/Common/Basics/Exclusive.cs deleted file mode 100644 index be4bbb81..00000000 --- a/src/Common/Basics/Exclusive.cs +++ /dev/null @@ -1,51 +0,0 @@ -namespace SpaceEngineers.Core.Basics -{ - using System; - using System.Threading; - using System.Threading.Tasks; - using Primitives; - - /// - /// Exclusive - /// - public class Exclusive - { - private readonly AsyncAutoResetEvent _sync; - - private bool _isTaken; - - /// .cctor - public Exclusive() - { - _sync = new AsyncAutoResetEvent(true); - _isTaken = false; - } - - /// - /// Ensures - /// - /// CancellationToken - /// Ongoing operation - public async Task Run(CancellationToken token) - { - if (_isTaken) - { - throw new InvalidOperationException("Exclusive operation has already been started"); - } - - await _sync - .WaitAsync(token) - .ConfigureAwait(false); - - _isTaken = true; - - return Disposable.Create(this, Finally); - - static void Finally(Exclusive exclusive) - { - exclusive._sync.Set(); - exclusive._isTaken = false; - } - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/ExecutionExtensions.cs b/src/Common/Basics/ExecutionExtensions.cs index 2c93c893..dc261c75 100644 --- a/src/Common/Basics/ExecutionExtensions.cs +++ b/src/Common/Basics/ExecutionExtensions.cs @@ -1,109 +1,62 @@ -namespace SpaceEngineers.Core.Basics -{ - using System; - using System.Linq; - using System.Threading.Tasks; +namespace SpaceEngineers.Core.Basics; - /// - /// Execution extension methods - /// - public static class ExecutionExtensions - { - private static readonly Type[] ExceptionTypesForSkip = new[] - { - typeof(StackOverflowException), - typeof(OutOfMemoryException), - typeof(OperationCanceledException), - typeof(AccessViolationException) - }; +using System; +using System.Linq; +using System.Threading.Tasks; +using Delegates; - /// - /// Try execute client's action - /// - /// Client action - /// StatelessActionExecutionInfo - public static StatelessActionExecutionInfo Try( - this Action clientAction) - { - return new StatelessActionExecutionInfo(clientAction); - } +public static class ExecutionExtensions +{ + private static readonly Type[] ExceptionTypesForSkip = new[] + { + typeof(StackOverflowException), + typeof(OutOfMemoryException), + typeof(OperationCanceledException), + typeof(AccessViolationException) + }; + + public static StatelessActionExecutionInfo Try( + this Action clientAction) + { + return new StatelessActionExecutionInfo(clientAction); + } - /// - /// Try execute client's action - /// - /// Client action - /// State - /// TState type-argument - /// StatelessActionExecutionInfo - public static ActionExecutionInfo Try( - this Action clientAction, - TState state) - { - return new ActionExecutionInfo(state, clientAction); - } + public static ActionExecutionInfo Try( + this Action clientAction, + TState state) + { + return new ActionExecutionInfo(state, clientAction); + } - /// - /// Try execute client's function - /// - /// Client function - /// Function TResult type-argument - /// StatelessFunctionExecutionInfo - public static StatelessFunctionExecutionInfo Try( - this Func clientFunction) - { - return new StatelessFunctionExecutionInfo(clientFunction); - } + public static StatelessFunctionExecutionInfo Try( + this Func clientFunction) + { + return new StatelessFunctionExecutionInfo(clientFunction); + } - /// - /// Try execute client's function - /// - /// Client function - /// State - /// TState type-argument - /// TResult type-argument - /// FunctionExecutionInfo - public static FunctionExecutionInfo Try( - this Func clientFunction, - TState state) - { - return new FunctionExecutionInfo(state, clientFunction); - } + public static FunctionExecutionInfo Try( + this Func clientFunction, + TState state) + { + return new FunctionExecutionInfo(state, clientFunction); + } - /// - /// Try execute client's asynchronous operation - /// - /// Async operation - /// Configure await option - /// AsyncOperationExecutionInfo - public static AsyncOperationExecutionInfo TryAsync( - this Task task, - bool configureAwait = false) - { - return new AsyncOperationExecutionInfo(task, configureAwait); - } + public static AsyncOperationExecutionInfo TryAsync( + this Task task, + bool configureAwait = false) + { + return new AsyncOperationExecutionInfo(task, configureAwait); + } - /// - /// Try execute client's asynchronous operation - /// - /// Async operation - /// Configure await option - /// TResult type-argument - /// AsyncOperationExecutionInfo - public static AsyncOperationExecutionInfo TryAsync( - this Task task, - bool configureAwait = false) - { - return new AsyncOperationExecutionInfo(task, configureAwait); - } + public static AsyncOperationExecutionInfo TryAsync( + this Task task, + bool configureAwait = false) + { + return new AsyncOperationExecutionInfo(task, configureAwait); + } - /// - /// Can be exception caught or not - /// - /// Exception - /// Can be exception caught or not sign - internal static bool CanBeCaught(Exception exception) - { - return !ExceptionTypesForSkip.Contains(exception.GetType()); - } + internal static bool CanBeCaught(Exception exception) + { + return !ExceptionTypesForSkip.Contains(exception.GetType()); } } \ No newline at end of file diff --git a/src/Common/Basics/ExpressionExtensions.cs b/src/Common/Basics/ExpressionExtensions.cs new file mode 100644 index 00000000..23abe9ef --- /dev/null +++ b/src/Common/Basics/ExpressionExtensions.cs @@ -0,0 +1,27 @@ +namespace SpaceEngineers.Core.Basics; + +using System.Linq.Expressions; +using Expressions; + +public static class ExpressionExtensions +{ + public static Expression ReplaceParameter( + this Expression expression, + ParameterExpression parameterExpression) + { + return ReplaceParameterVisitor.Replace(expression, parameterExpression); + } + + public static Expression UnwrapUnaryExpression( + this Expression expression) + { + var expressionTypes = new[] + { + ExpressionType.Quote, + ExpressionType.Convert, + ExpressionType.ConvertChecked + }; + + return UnwrapUnaryExpressionVisitor.Unwrap(expression, expressionTypes); + } +} \ No newline at end of file diff --git a/src/Common/Basics/Expressions/ReplaceParameterVisitor.cs b/src/Common/Basics/Expressions/ReplaceParameterVisitor.cs new file mode 100644 index 00000000..17f03ca8 --- /dev/null +++ b/src/Common/Basics/Expressions/ReplaceParameterVisitor.cs @@ -0,0 +1,23 @@ +namespace SpaceEngineers.Core.Basics.Expressions; + +using System.Linq.Expressions; + +public class ReplaceParameterVisitor : ExpressionVisitor +{ + private readonly ParameterExpression _replacement; + + private ReplaceParameterVisitor(ParameterExpression replacement) + { + _replacement = replacement; + } + + public static Expression Replace(Expression expression, ParameterExpression replacement) + { + return new ReplaceParameterVisitor(replacement).Visit(expression); + } + + protected override Expression VisitParameter(ParameterExpression node) + { + return _replacement; + } +} \ No newline at end of file diff --git a/src/Common/Basics/Expressions/UnwrapUnaryExpressionVisitor.cs b/src/Common/Basics/Expressions/UnwrapUnaryExpressionVisitor.cs new file mode 100644 index 00000000..1f5da11e --- /dev/null +++ b/src/Common/Basics/Expressions/UnwrapUnaryExpressionVisitor.cs @@ -0,0 +1,26 @@ +namespace SpaceEngineers.Core.Basics.Expressions; + +using System.Linq; +using System.Linq.Expressions; + +public class UnwrapUnaryExpressionVisitor : ExpressionVisitor +{ + private readonly ExpressionType[] _expressionTypes; + + private UnwrapUnaryExpressionVisitor(ExpressionType[] expressionTypes) + { + _expressionTypes = expressionTypes; + } + + public static Expression Unwrap(Expression expression, ExpressionType[] expressionTypes) + { + return new UnwrapUnaryExpressionVisitor(expressionTypes).Visit(expression); + } + + protected override Expression VisitUnary(UnaryExpression node) + { + return _expressionTypes.Contains(node.NodeType) + ? node.Operand + : base.VisitUnary(node); + } +} \ No newline at end of file diff --git a/src/Common/Basics/FileSystemExtensions.cs b/src/Common/Basics/FileSystemExtensions.cs index 91e4a8a3..6381c099 100644 --- a/src/Common/Basics/FileSystemExtensions.cs +++ b/src/Common/Basics/FileSystemExtensions.cs @@ -1,202 +1,132 @@ -namespace SpaceEngineers.Core.Basics +namespace SpaceEngineers.Core.Basics; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +public static class FileSystemExtensions { - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - - /// - /// File system extensions - /// - public static class FileSystemExtensions + public static FileInfo RelativeFile(this DirectoryInfo sourceDirectory, string relativePath) { - /// - /// Ges relative FileInfo - /// - /// Source directory - /// Relative file path - /// Relative FileInfo - public static FileInfo RelativeFile(this DirectoryInfo sourceDirectory, string relativePath) - { - var path = Path.Combine(sourceDirectory.FullName, relativePath); - var info = new FileInfo(path); - return info.Exists - ? info - : throw new DirectoryNotFoundException(path); - } + var path = Path.Combine(sourceDirectory.FullName, relativePath); + var info = new FileInfo(path); + return info.Exists + ? info + : throw new DirectoryNotFoundException(path); + } - /// - /// Step into specified directory - /// - /// Source directory - /// Target child directory - /// Child directory (is step was successful) - /// Inner DirectoryInfo - /// Directory doesn't contains specified subdirectories - public static bool TryStepInto(this DirectoryInfo source, string to, out DirectoryInfo? inner) - { - inner = source - .EnumerateDirectories() - .Where(it => string.Equals(it.Name, to, StringComparison.OrdinalIgnoreCase)) - .InformativeSingleOrDefault(Amb); + public static bool TryStepInto(this DirectoryInfo source, string to, out DirectoryInfo? inner) + { + inner = source + .EnumerateDirectories() + .Where(it => string.Equals(it.Name, to, StringComparison.OrdinalIgnoreCase)) + .InformativeSingleOrDefault(Amb); - return inner != null; - } + return inner != null; + } - /// - /// Step into specified directory - /// - /// Source directory - /// Target child directory - /// Additional child directories - /// Inner DirectoryInfo - /// Directory doesn't contains specified subdirectories - public static DirectoryInfo StepInto(this DirectoryInfo source, string to, params string[] additionalTargets) - { - return new[] { to } - .Concat(additionalTargets) - .Aggregate(source, - (acc, next) => acc - .EnumerateDirectories() - .Where(it => string.Equals(it.Name, next, StringComparison.OrdinalIgnoreCase)) - .InformativeSingleOrDefault(Amb) ?? throw new DirectoryNotFoundException(Path.Combine(acc.FullName, next))); - } + public static DirectoryInfo StepInto(this DirectoryInfo source, string to, params string[] additionalTargets) + { + return new[] { to } + .Concat(additionalTargets) + .Aggregate(source, + (acc, next) => acc + .EnumerateDirectories() + .Where(it => string.Equals(it.Name, next, StringComparison.OrdinalIgnoreCase)) + .InformativeSingleOrDefault(Amb) ?? throw new DirectoryNotFoundException(Path.Combine(acc.FullName, next))); + } - /// - /// Try get file from directory with specified name (without extension) - /// - /// Source directory - /// File name without extension - /// File extension (optional) - /// Found file - /// Inner FileInfo - public static bool TryGetFile( - this DirectoryInfo directory, - string fileNameWithoutExtension, - string? extension, - out FileInfo? info) - { - info = directory - .EnumerateFiles() - .Where(file => EqualsFileName(file, fileNameWithoutExtension, extension)) - .InformativeSingleOrDefault(Amb); + public static bool TryGetFile( + this DirectoryInfo directory, + string fileNameWithoutExtension, + string? extension, + out FileInfo? info) + { + info = directory + .EnumerateFiles() + .Where(file => EqualsFileName(file, fileNameWithoutExtension, extension)) + .InformativeSingleOrDefault(Amb); - return info != null; - } + return info != null; + } - /// - /// Get file from directory with specified name (without extension) - /// - /// Source directory - /// File name without extension - /// File extension (optional) - /// Inner FileInfo - /// Directory doesn't contains specified file - public static FileInfo GetFile( - this DirectoryInfo directory, - string fileNameWithoutExtension, - string? extension = null) - { - return directory - .EnumerateFiles() - .Where(file => EqualsFileName(file, fileNameWithoutExtension, extension)) - .InformativeSingleOrDefault(Amb) - ?? throw new FileNotFoundException(Path.Combine(directory.FullName, fileNameWithoutExtension)); - } + public static FileInfo GetFile( + this DirectoryInfo directory, + string fileNameWithoutExtension, + string? extension = null) + { + return directory + .EnumerateFiles() + .Where(file => EqualsFileName(file, fileNameWithoutExtension, extension)) + .InformativeSingleOrDefault(Amb) + ?? throw new FileNotFoundException(Path.Combine(directory.FullName, fileNameWithoutExtension)); + } - /// - /// Converts string directory path to DirectoryInfo - /// - /// String directory path - /// DirectoryInfo - /// Specified directory doesn't exist - public static DirectoryInfo AsDirectoryInfo(this string path) - { - var info = new DirectoryInfo(path); + public static DirectoryInfo AsDirectoryInfo(this string path) + { + var info = new DirectoryInfo(path); - return info.Exists - ? info - : throw new DirectoryNotFoundException(path); - } + return info.Exists + ? info + : throw new DirectoryNotFoundException(path); + } - /// - /// Converts string file path to FileInfo - /// - /// String file path - /// FileInfo - /// Specified file doesn't exist - public static FileInfo AsFileInfo(this string path) - { - var info = new FileInfo(path); + public static FileInfo AsFileInfo(this string path) + { + var info = new FileInfo(path); - return info.Exists - ? info - : throw new FileNotFoundException(path); - } + return info.Exists + ? info + : throw new FileNotFoundException(path); + } - /// - /// Setup FileInfo with specified extension - /// - /// Source FileInfo - /// File extension - /// FileInfo with specified extension - public static FileInfo WithExtension(this FileInfo fileInfo, string extension) - { - return new FileInfo(Path.ChangeExtension(fileInfo.FullName, extension)); - } + public static FileInfo WithExtension(this FileInfo fileInfo, string extension) + { + return new FileInfo(Path.ChangeExtension(fileInfo.FullName, extension)); + } - /// - /// Gets file name without extension - /// - /// FileInfo - /// File name without extension - public static string NameWithoutExtension(this FileInfo fileInfo) - { - return fileInfo.Name.Substring(0, fileInfo.Name.Length - fileInfo.Extension.Length); - } + public static string NameWithoutExtension(this FileInfo fileInfo) + { + return fileInfo.Name.Substring(0, fileInfo.Name.Length - fileInfo.Extension.Length); + } + + public static string NameWithoutExtension(this string fileName) + { + var extension = fileName + .Split(".", StringSplitOptions.RemoveEmptyEntries) + .LastOrDefault(); - /// - /// Gets file name without extension - /// - /// fileName - /// File name without extension - public static string NameWithoutExtension(this string fileName) + if (extension == null) { - var extension = fileName - .Split(".", StringSplitOptions.RemoveEmptyEntries) - .LastOrDefault(); + return fileName; + } - if (extension == null) - { - return fileName; - } + return fileName.Substring(0, fileName.Length - extension.Length - 1); + } - return fileName.Substring(0, fileName.Length - extension.Length - 1); - } + private static bool EqualsFileName(FileInfo file, string fileNameWithoutExtension, string? extension) + { + string left, right; - private static bool EqualsFileName(FileInfo file, string fileNameWithoutExtension, string? extension) + if (extension.IsNullOrEmpty()) { - string left, right; - - if (extension.IsNullOrEmpty()) - { - left = file.NameWithoutExtension(); - right = fileNameWithoutExtension; - } - else - { - left = file.Name; - right = fileNameWithoutExtension + extension; - } - - return string.Equals(left, right, StringComparison.OrdinalIgnoreCase); + left = file.NameWithoutExtension(); + right = fileNameWithoutExtension; } - - private static string Amb(IEnumerable source) + else { - return source - .Select(info => info.FullName) - .ToString(Environment.NewLine); + left = file.Name; + right = fileNameWithoutExtension + extension; } + + return string.Equals(left, right, StringComparison.OrdinalIgnoreCase); + } + + private static string Amb(IEnumerable source) + { + return source + .Select(info => info.FullName) + .ToString(Environment.NewLine); } } \ No newline at end of file diff --git a/src/Common/Basics/FunctionExecutionInfo.cs b/src/Common/Basics/FunctionExecutionInfo.cs deleted file mode 100644 index 4fec22fe..00000000 --- a/src/Common/Basics/FunctionExecutionInfo.cs +++ /dev/null @@ -1,98 +0,0 @@ -namespace SpaceEngineers.Core.Basics -{ - using System; - using System.Collections.Generic; - - /// - /// FunctionExecutionInfo - /// - /// TState Type-argument - /// TResult Type-argument - public class FunctionExecutionInfo - { - private static readonly Action EmptyExceptionHandler = _ => { }; - - private readonly TState _state; - private readonly Func _clientFunction; - private readonly IDictionary> _exceptionHandlers; - - private Action? _finallyAction; - - /// .ctor - /// State - /// Client function - public FunctionExecutionInfo( - TState state, - Func clientFunction) - { - _state = state; - _clientFunction = clientFunction; - _exceptionHandlers = new Dictionary>(); - } - - /// - /// Catch block - /// Catch exception of TException type - /// - /// Exception handler - /// Real exception type-argument - /// FunctionExecutionInfo - public FunctionExecutionInfo Catch(Action? exceptionHandler = null) - { - _exceptionHandlers[typeof(TException)] = exceptionHandler ?? EmptyExceptionHandler; - - return this; - } - - /// - /// Finally block - /// - /// Finally action - /// FunctionExecutionInfo - public FunctionExecutionInfo Finally(Action finallyAction) - { - _finallyAction = finallyAction; - - return this; - } - - /// - /// Invoke client's function - /// - /// Creates result from handled exception - /// TResult - public TResult Invoke(Func exceptionResultFactory) - { - try - { - return _clientFunction.Invoke(_state); - } - catch (Exception ex) when (ExecutionExtensions.CanBeCaught(ex.RealException())) - { - var realException = ex.RealException(); - var handled = false; - - foreach (var pair in _exceptionHandlers) - { - if (pair.Key.IsInstanceOfType(realException)) - { - pair.Value.Invoke(realException); - handled = true; - break; - } - } - - if (!handled) - { - throw realException.Rethrow(); - } - - return exceptionResultFactory.Invoke(realException); - } - finally - { - _finallyAction?.Invoke(); - } - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/Heap/BinaryHeap.cs b/src/Common/Basics/Heap/BinaryHeap.cs new file mode 100644 index 00000000..33c9bfa9 --- /dev/null +++ b/src/Common/Basics/Heap/BinaryHeap.cs @@ -0,0 +1,380 @@ +namespace SpaceEngineers.Core.Basics.Heap; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +public class BinaryHeap : IHeap + where TElement : IEquatable, IComparable, IComparable +{ + private const int Root = 0; + + private readonly EnOrderingDirection _orderingDirection; + + private int _last; + private int _height; + private TElement[] _array; + + public BinaryHeap(EnOrderingDirection orderingDirection) + { + _orderingDirection = orderingDirection; + + _last = -1; + _height = 0; + _array = Array.Empty(); + } + + public BinaryHeap(IEnumerable source, EnOrderingDirection orderingDirection) + : this(orderingDirection) + { + foreach (var element in source) + { + Insert(element); + } + } + + public event EventHandler>? RootNodeChanged; + + private event EventHandler? Changed; + + public int Count => Length(); + + public bool IsEmpty => IsEmptyHeap(); + + public IEnumerator GetEnumerator() + { + return new BinaryHeapEnumerator(this); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public override string ToString() + { + return _array + .Select((element, i) => + { + var height = Height(i); + return (element, height); + }) + .GroupBy(pair => pair.height) + .Select(pair => pair.Select(it => it.element)) + .Select(pair => pair.ToString(" ")) + .ToString(Environment.NewLine); + } + + public TElement[] ExtractArray() + { + var result = new TElement[Length()]; + + for (var i = 0; i < result.Length; i++) + { + result[i] = ExtractFromHeap(); + } + + return result; + } + + public void Insert(TElement element) + { + /* + * 1. Add a new element to the end of the heap + * 2. Heapify-up starting from the last element + */ + + var originalRoot = GetRoot(); + + AddLast(element); + HeapifyUp(_last); + + NotifyChanged(); + NotifyRootNodeChanged(originalRoot); + } + + public TElement Peek() + { + return PeekHeap(); + } + + public bool TryPeek([NotNullWhen(true)] out TElement? element) + { + return Try(PeekHeap, out element); + } + + public TElement Extract() + { + return ExtractFromHeap(); + } + + public bool TryExtract([NotNullWhen(true)] out TElement? element) + { + return Try(ExtractFromHeap, out element); + } + + #region internals + + private bool Try(Func accessor, [NotNullWhen(true)] out TElement? element) + { + if (IsEmptyHeap()) + { + element = default; + return false; + } + + element = accessor(); + return true; + } + + private TElement PeekHeap() + { + return GetRequiredRoot(); + } + + private TElement ExtractFromHeap() + { + /* + * 1. Replace the root of the heap with the last element + * 2. Heapify-down starting from the root element + */ + + var originalRoot = GetRequiredRoot(); + + Swap(Root, _last); + RemoveLast(); + HeapifyDown(Root); + + NotifyChanged(); + NotifyRootNodeChanged(originalRoot); + + return originalRoot; + } + + private bool IsEmptyHeap() + { + return _last < 0; + } + + private int Length() + { + return _last + 1; + } + + private void HeapifyUp(int child) + { + /* + * 1. Compare the added element with its parent + * If they are in the correct order, stop + * If not, swap the element with its parent and return to the previous step + */ + + var parent = Parent(child); + + if (HasHighestPriority(child, parent)) + { + Swap(child, parent); + HeapifyUp(parent); + } + } + + private void HeapifyDown(int parent) + { + /* + * 1. Compare a parent with his children + * If they are in the correct order, stop + * 2. If not, swap the parent with one of his children and return to the previous step + * Swap with the highest priority child + */ + + var theHighestPriority = parent; + var left = Left(theHighestPriority); + var right = Right(theHighestPriority); + + if (left <= _last + && HasHighestPriority(left, theHighestPriority)) + { + theHighestPriority = left; + } + + if (right <= _last + && HasHighestPriority(right, theHighestPriority)) + { + theHighestPriority = right; + } + + if (theHighestPriority == parent) + { + return; + } + + Swap(parent, theHighestPriority); + HeapifyDown(theHighestPriority); + } + + private bool HasHighestPriority(int index, int theHighestPriority) + { + var result = _array[index].CompareTo(_array[theHighestPriority]); + + return _orderingDirection == EnOrderingDirection.Asc + ? result < 0 + : result > 0; + } + + private TElement? GetRoot() + { + return !IsEmptyHeap() + ? _array[Root] + : default; + } + + private TElement GetRequiredRoot() + { + if (IsEmptyHeap()) + { + throw new InvalidOperationException($"{nameof(BinaryHeap)} is empty"); + } + + return _array[Root]; + } + + private void AddLast(TElement element) + { + Expand(); + + _last++; + _array[_last] = element; + } + + private void RemoveLast() + { + _array[_last] = default!; + _last--; + + Contract(); + } + + private void Expand() + { + if (Length() < _array.Length) + { + return; + } + + _array = Resize(_array, ++_height, Length()); + } + + private void Contract() + { + var limitedCapacity = Capacity(_height - 1); + + if (limitedCapacity > _last + && limitedCapacity < _array.Length) + { + _array = Resize(_array, --_height, Length()); + } + } + + private static TElement[] Resize(TElement[] source, int height, int length) + { + var resized = new TElement[Capacity(height)]; + Array.Copy(source, resized, length); + return resized; + } + + private void Swap(int left, int right) + { + if (left == right) + { + return; + } + + (_array[left], _array[right]) = (_array[right], _array[left]); + } + + private static int Left(int index) => (2 * index) + 1; + + private static int Right(int index) => (2 * index) + 2; + + private static int Parent(int index) => (index - 1) / 2; + + private static uint Height(int index) => (index + 1).Log(2); + + private static int Capacity(int height) => 2.Pow((uint)height) - 1; + + private void NotifyChanged() + { + Changed?.Invoke(this, new EventArgs()); + } + + private void NotifyRootNodeChanged(TElement? originalRoot) + { + var currentRoot = GetRoot(); + + RootNodeChanged?.Invoke(this, new RootNodeChangedEventArgs(originalRoot, currentRoot)); + } + + #endregion + + private class BinaryHeapEnumerator : IEnumerator + where T : IEquatable, IComparable, IComparable + { + private readonly BinaryHeap _heap; + private readonly EventHandler _subscription; + + private int _current; + private bool _changed; + + public BinaryHeapEnumerator(BinaryHeap heap) + { + _heap = heap; + _current = -1; + + _subscription = OnChanged; + _heap.Changed += _subscription; + } + + public T Current + { + get + { + ValidateNoChanges(); + return _heap._array[_current]; + } + } + + object IEnumerator.Current => Current; + + public bool MoveNext() + { + ValidateNoChanges(); + return ++_current <= _heap._last; + } + + public void Reset() + { + _current = -1; + } + + public void Dispose() + { + _heap.Changed -= _subscription; + Reset(); + } + + private void OnChanged(object sender, EventArgs e) + { + _changed = true; + } + + private void ValidateNoChanges() + { + if (_changed) + { + throw new InvalidOperationException("Collection was modified during enumeration"); + } + } + } +} \ No newline at end of file diff --git a/src/Common/Basics/Heap/HeapEntry.cs b/src/Common/Basics/Heap/HeapEntry.cs new file mode 100644 index 00000000..5bf42c06 --- /dev/null +++ b/src/Common/Basics/Heap/HeapEntry.cs @@ -0,0 +1,87 @@ +namespace SpaceEngineers.Core.Basics.Heap; + +using System; +using Basics; + +public class HeapEntry : IEquatable>, + ISafelyEquatable>, + ISafelyComparable>, + IComparable>, + IComparable + where TKey : IEquatable, IComparable, IComparable +{ + private readonly TKey _key; + + public HeapEntry(TKey key, TElement element) + { + _key = key; + Element = element; + } + + public TElement Element { get; } + + public static bool operator ==(HeapEntry? left, HeapEntry? right) + { + return Equatable.Equals(left, right); + } + + public static bool operator !=(HeapEntry? left, HeapEntry? right) + { + return !Equatable.Equals(left, right); + } + + public static bool operator <(HeapEntry? left, HeapEntry? right) + { + return Comparable.Less(left, right); + } + + public static bool operator >(HeapEntry? left, HeapEntry? right) + { + return Comparable.Greater(left, right); + } + + public static bool operator <=(HeapEntry? left, HeapEntry? right) + { + return Comparable.LessOrEquals(left, right); + } + + public static bool operator >=(HeapEntry? left, HeapEntry? right) + { + return Comparable.GreaterOrEquals(left, right); + } + + public int SafeCompareTo(HeapEntry other) + { + return _key.CompareTo(other._key); + } + + public int CompareTo(HeapEntry? other) + { + return Comparable.CompareTo(this, other); + } + + public int CompareTo(object? obj) + { + return Comparable.CompareTo(this, obj); + } + + public bool SafeEquals(HeapEntry other) + { + return _key.Equals(other._key); + } + + public bool Equals(HeapEntry? other) + { + return Equatable.Equals(this, other); + } + + public override bool Equals(object? obj) + { + return Equatable.Equals(this, obj); + } + + public override int GetHashCode() + { + return _key.GetHashCode(); + } +} \ No newline at end of file diff --git a/src/Common/Basics/Heap/IHeap.cs b/src/Common/Basics/Heap/IHeap.cs new file mode 100644 index 00000000..a212d99e --- /dev/null +++ b/src/Common/Basics/Heap/IHeap.cs @@ -0,0 +1,27 @@ +namespace SpaceEngineers.Core.Basics.Heap; + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +public interface IHeap : IEnumerable + where TElement : IEquatable, IComparable, IComparable +{ + event EventHandler>? RootNodeChanged; + + int Count { get; } + + bool IsEmpty { get; } + + void Insert(TElement element); + + TElement Peek(); + + bool TryPeek([NotNullWhen(true)] out TElement? element); + + TElement Extract(); + + bool TryExtract([NotNullWhen(true)] out TElement? element); + + TElement[] ExtractArray(); +} \ No newline at end of file diff --git a/src/Common/Basics/Heap/RootNodeChangedEventArgs.cs b/src/Common/Basics/Heap/RootNodeChangedEventArgs.cs new file mode 100644 index 00000000..d56caa15 --- /dev/null +++ b/src/Common/Basics/Heap/RootNodeChangedEventArgs.cs @@ -0,0 +1,14 @@ +namespace SpaceEngineers.Core.Basics.Heap; + +public class RootNodeChangedEventArgs +{ + public RootNodeChangedEventArgs(TElement? originalValue, TElement? currentValue) + { + OriginalValue = originalValue; + CurrentValue = currentValue; + } + + public TElement? OriginalValue { get; } + + public TElement? CurrentValue { get; } +} \ No newline at end of file diff --git a/src/Common/Basics/ICloneable.cs b/src/Common/Basics/ICloneable.cs index 85ff21c0..aa270c65 100644 --- a/src/Common/Basics/ICloneable.cs +++ b/src/Common/Basics/ICloneable.cs @@ -1,15 +1,8 @@ -namespace SpaceEngineers.Core.Basics -{ - using System; +namespace SpaceEngineers.Core.Basics; + +using System; - /// - /// ICloneable - /// - /// T type-argument - public interface ICloneable : ICloneable - { - /// Clone - /// Copy - new T Clone(); - } +public interface ICloneable : ICloneable +{ + new T Clone(); } \ No newline at end of file diff --git a/src/Common/Basics/ISafelyComparable.cs b/src/Common/Basics/ISafelyComparable.cs index 800a191f..c3540905 100644 --- a/src/Common/Basics/ISafelyComparable.cs +++ b/src/Common/Basics/ISafelyComparable.cs @@ -1,16 +1,6 @@ -namespace SpaceEngineers.Core.Basics +namespace SpaceEngineers.Core.Basics; + +public interface ISafelyComparable { - /// - /// ISafelyComparable - /// - /// T type-argument - public interface ISafelyComparable - { - /// - /// Non nullable analog of CompareTo - /// - /// Other - /// Comparison result - int SafeCompareTo(T other); - } + int SafeCompareTo(T other); } \ No newline at end of file diff --git a/src/Common/Basics/ISafelyEquatable.cs b/src/Common/Basics/ISafelyEquatable.cs index f2a23a73..d1fa1edc 100644 --- a/src/Common/Basics/ISafelyEquatable.cs +++ b/src/Common/Basics/ISafelyEquatable.cs @@ -1,16 +1,6 @@ -namespace SpaceEngineers.Core.Basics +namespace SpaceEngineers.Core.Basics; + +public interface ISafelyEquatable { - /// - /// ISafelyEquatable - /// - /// T type-argument - public interface ISafelyEquatable - { - /// - /// Non nullable analog of Equals - /// - /// Other - /// Comparison result - bool SafeEquals(T other); - } + bool SafeEquals(T other); } \ No newline at end of file diff --git a/src/Common/Basics/IntegerExtensions.cs b/src/Common/Basics/IntegerExtensions.cs index 63eb342a..d1fb2c37 100644 --- a/src/Common/Basics/IntegerExtensions.cs +++ b/src/Common/Basics/IntegerExtensions.cs @@ -1,139 +1,92 @@ -namespace SpaceEngineers.Core.Basics +namespace SpaceEngineers.Core.Basics; + +using System; +using System.Collections.Generic; +using System.Linq; + +public static class IntegerExtensions { - using System; - using System.Collections.Generic; - using System.Linq; - - /// - /// Integer extensions - /// - public static class IntegerExtensions + public static string AlphabetIndex(this int index) { - /// - /// Converts index into english alphabet index - /// - /// Index - /// String rank - public static string AlphabetIndex(this int index) - { - var ranks = GetRanks(index, 'z' - 'a' + 1) - .Reverse() - .Select(rank => (char)('a' + rank)) - .ToArray(); + var ranks = GetRanks(index, 'z' - 'a' + 1) + .Reverse() + .Select(rank => (char)('a' + rank)) + .ToArray(); - return new string(ranks); + return new string(ranks); - static IEnumerable GetRanks(int index, int length) - { - var current = index; - - while (current >= length) - { - yield return current % length; - current = (current / length) - 1; - } + static IEnumerable GetRanks(int index, int length) + { + var current = index; - yield return current; + while (current >= length) + { + yield return current % length; + current = (current / length) - 1; } - } - /// - /// Log with custom basis - /// - /// Result - /// Basis - /// Exponent - public static uint Log(this int result, uint basis) - { - return (uint)Math.Log(result, basis); + yield return current; } + } - /// - /// Powers integer with an positive exponent - /// - /// Basis - /// Exponent - /// Result - public static int Pow(this int basis, uint exp) - { - var result = 1; + public static uint Log(this int result, uint basis) + { + return (uint)Math.Log(result, basis); + } - checked + public static int Pow(this int basis, uint exp) + { + var result = 1; + + checked + { + while (exp != 0) { - while (exp != 0) + if ((exp & 1) == 1) { - if ((exp & 1) == 1) - { - result *= basis; - } - - basis *= basis; - exp >>= 1; + result *= basis; } - } - return result; + basis *= basis; + exp >>= 1; + } } - /// - /// Between include bounds - /// - /// Index - /// Range start - /// Range end - /// Index included in range - public static bool BetweenInclude(this int index, int start, int end) - { - return start <= index && index <= end; - } + return result; + } - /// - /// Between include bounds - /// - /// Index - /// Range - /// Index included in range - public static bool BetweenInclude(this int index, Range range) - { - return BetweenInclude(index, range.Start.Value, range.End.Value); - } + public static bool BetweenInclude(this int index, int start, int end) + { + return start <= index && index <= end; + } - /// - /// Between include bounds - /// - /// Index - /// Range start - /// Range end - /// Index included in range - public static bool BetweenInclude(this uint index, uint start, uint end) - { - return start <= index && index <= end; - } + public static bool BetweenInclude(this int index, Range range) + { + return BetweenInclude(index, range.Start.Value, range.End.Value); + } - /// - /// Between include bounds - /// - /// Index - /// Range - /// Index included in range - public static bool BetweenInclude(this uint index, Range range) - { - CheckBoundaries(range.Start.Value, range.End.Value); + public static bool BetweenInclude(this uint index, uint start, uint end) + { + return start <= index && index <= end; + } - return BetweenInclude(index, (uint)range.Start.Value, (uint)range.End.Value); - } + public static bool BetweenInclude(this uint index, Range range) + { + CheckBoundaries(range.Start.Value, range.End.Value); + + return BetweenInclude(index, (uint)range.Start.Value, (uint)range.End.Value); + } - private static void CheckBoundaries(int start, int end) + private static void CheckBoundaries(int start, int end) + { + if (start < 0) { - if (start < 0) - { - throw new ArgumentException($"{start} boundary should be greater than zero", nameof(start)); - } + throw new ArgumentException($"{start} boundary should be greater than zero", nameof(start)); + } - if (end < 0) - { - throw new ArgumentException($"{end} boundary should be greater than zero", nameof(end)); - } + if (end < 0) + { + throw new ArgumentException($"{end} boundary should be greater than zero", nameof(end)); } } } \ No newline at end of file diff --git a/src/Common/Basics/IsExternalInit.cs b/src/Common/Basics/IsExternalInit.cs deleted file mode 100644 index d0ed0bbe..00000000 --- a/src/Common/Basics/IsExternalInit.cs +++ /dev/null @@ -1,20 +0,0 @@ -#pragma warning disable SA1200 -#pragma warning disable CS1591 - -using System.ComponentModel; - -namespace System.Runtime.CompilerServices -{ - /* - * The IsExternalInit type is only included in the net5.0 (and future) target frameworks. - * When compiling against older target frameworks you will need to manually define this type. - */ - - [EditorBrowsable(EditorBrowsableState.Never)] - public static class IsExternalInit - { - } -} - -#pragma warning restore CS1591 -#pragma warning restore SA1200 \ No newline at end of file diff --git a/src/Common/Basics/LogicalExtensions.cs b/src/Common/Basics/LogicalExtensions.cs deleted file mode 100644 index ea9c864c..00000000 --- a/src/Common/Basics/LogicalExtensions.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace SpaceEngineers.Core.Basics -{ - /// - /// LogicalExtensions - /// - public static class LogicalExtensions - { - /// Bit - /// Condition - /// 0 or 1 - public static int Bit(this bool condition) - { - return condition ? 1 : 0; - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/MemberExtensions.cs b/src/Common/Basics/MemberExtensions.cs index c3913056..d66ef49a 100644 --- a/src/Common/Basics/MemberExtensions.cs +++ b/src/Common/Basics/MemberExtensions.cs @@ -1,359 +1,104 @@ -namespace SpaceEngineers.Core.Basics -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Reflection; - using System.Runtime.CompilerServices; - using Exceptions; - - /// - /// Extensions for operations with class members - /// - public static class MemberExtensions - { - private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Public; - - private static readonly Type NullableAttributeType = TypeExtensions.FindType("System.Private.CoreLib System.Runtime.CompilerServices.NullableAttribute"); - - private static readonly Type[] IsExternalInitTypes = - new[] - { - TypeExtensions.FindType("System.Private.CoreLib System.Runtime.CompilerServices.IsExternalInit"), - TypeExtensions.FindType("SpaceEngineers.Core.Basics System.Runtime.CompilerServices.IsExternalInit") - }; - - /// - /// Get specified attribute from type - /// - /// MemberInfo - /// TAttribute type-argument - /// Attribute - public static TAttribute GetRequiredAttribute(this MemberInfo memberInfo) - where TAttribute : Attribute - { - return memberInfo - .GetCustomAttributes() - .InformativeSingleOrDefault(Amb) - ?? throw new AttributeRequiredException(typeof(TAttribute), memberInfo); - - static string Amb(IEnumerable arg) - { - return $"Type has more than one {typeof(TAttribute)}"; - } - } - - /// - /// Get specified attribute from type - /// - /// MemberInfo - /// TAttribute type-argument - /// Attribute - public static TAttribute? GetAttribute(this MemberInfo memberInfo) - where TAttribute : Attribute - { - return memberInfo - .GetCustomAttributes() - .InformativeSingleOrDefault(Amb); - - static string Amb(IEnumerable arg) - { - return $"Type has more than one {typeof(TAttribute)}"; - } - } - - /// - /// Does the specified type has an attribute - /// - /// MemberInfo - /// TAttribute type-argument - /// Attribute existence - public static bool HasAttribute(this MemberInfo memberInfo) - where TAttribute : Attribute - { - return memberInfo.GetCustomAttributes().Any(); - } - - /// - /// Get specified attribute from type - /// - /// MethodInfo - /// TAttribute type-argument - /// Attribute - public static TAttribute GetRequiredAttribute(this MethodInfo methodInfo) - where TAttribute : Attribute - { - return methodInfo - .GetCustomAttributes() - .InformativeSingleOrDefault(Amb) - ?? throw new AttributeRequiredException(typeof(TAttribute), methodInfo); +namespace SpaceEngineers.Core.Basics; - static string Amb(IEnumerable arg) - { - return $"Type has more than one {typeof(TAttribute)}"; - } - } - - /// - /// Get specified attribute from type - /// - /// MethodInfo - /// TAttribute type-argument - /// Attribute - public static TAttribute? GetAttribute(this MethodInfo methodInfo) - where TAttribute : Attribute - { - return methodInfo - .GetCustomAttributes() - .InformativeSingleOrDefault(Amb); +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; - static string Amb(IEnumerable arg) - { - return $"Type has more than one {typeof(TAttribute)}"; - } - } - - /// - /// Does the specified type has an attribute - /// - /// MethodInfo - /// TAttribute type-argument - /// Attribute existence - public static bool HasAttribute(this MethodInfo methodInfo) - where TAttribute : Attribute - { - return methodInfo.GetCustomAttributes().Any(); - } - - /// - /// Extract GenericMethodDefinition or return argument method - /// - /// MethodInfo - /// GenericMethodDefinition or argument method - public static MethodInfo GenericMethodDefinitionOrSelf(this MethodInfo method) - { - return method.IsGenericMethod - ? method.GetGenericMethodDefinition() - : method; - } - - /// - /// Does method have external access (public or internal) - /// - /// MethodBase - /// True if method has external access (public or internal) - public static bool IsAccessible(this MethodBase method) - { - return (method.IsPublic || method.IsAssembly) - && !(method.IsPrivate || method.IsFamilyOrAssembly); - } +public static partial class MemberExtensions +{ + private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Public; - /// - /// Does property getter have external access (public or internal) - /// - /// PropertyInfo - /// True if property getter has external access (public or internal) - public static bool GetIsAccessible(this PropertyInfo property) - { - var getMethod = property.GetGetMethod(true); - return getMethod != null && getMethod.IsAccessible(); - } + private static readonly Type NullableAttributeType = TypeExtensions.FindType("System.Private.CoreLib System.Runtime.CompilerServices.NullableAttribute"); - /// - /// Does property setter have external access (public or internal) - /// - /// PropertyInfo - /// True if property setter has external access (public or internal) - public static bool SetIsAccessible(this PropertyInfo property) - { - var setMethod = property.GetSetMethod(true); - return setMethod != null && setMethod.IsAccessible(); - } + public static bool IsAccessible(this MethodBase method) + { + return (method.IsPublic || method.IsAssembly) + && !(method.IsPrivate || method.IsFamilyOrAssembly); + } - /// - /// Does property represents equality contract generated by compiler - /// - /// PropertyInfo - /// True if property represents equality contract generated by compiler - public static bool IsEqualityContract(this PropertyInfo property) - { - return property.Name.Equals("EqualityContract", StringComparison.OrdinalIgnoreCase) - && property.GetMethod?.GetCustomAttribute(typeof(CompilerGeneratedAttribute)) != null; - } + public static bool IsEqualityContract(this PropertyInfo property) + { + return property.Name.Equals("EqualityContract", StringComparison.OrdinalIgnoreCase) + && property.GetMethod?.GetCustomAttribute(typeof(CompilerGeneratedAttribute)) != null; + } - /// - /// Check that object has property - /// - /// Target object - /// Name of the property - /// Property value - public static bool HasProperty(this object target, string propertyName) - { - return target - .GetType() - .GetProperty(propertyName, Flags) != null; - } + public static bool HasProperty(this object target, string propertyName) + { + return target + .GetType() + .GetProperty(propertyName, Flags) != null; + } - /// - /// Get property value from target object - /// - /// Target object - /// Name of the property - /// Property value - public static object GetPropertyValue(this object target, string propertyName) - { - return target - .GetType() - .GetProperty(propertyName, Flags | BindingFlags.GetProperty) - .GetValue(target); - } + public static object GetPropertyValue(this object target, string propertyName) + { + return target + .GetType() + .GetProperty(propertyName, Flags | BindingFlags.GetProperty) + .GetValue(target); + } - /// - /// Get property value from target object - /// - /// Target object - /// Name of the property - /// TProperty type-argument - /// Property value - public static TProperty GetPropertyValue(this object target, string propertyName) - { - return (TProperty)target.GetPropertyValue(propertyName); - } + public static TProperty GetPropertyValue(this object target, string propertyName) + { + return (TProperty)target.GetPropertyValue(propertyName); + } - /// - /// Check that object has field - /// - /// Target object - /// Name of the field - /// Field value - public static bool HasField(this object target, string fieldName) - { - return HasField(target.GetType(), fieldName); - } + public static bool HasField(this object target, string fieldName) + { + return HasField(target.GetType(), fieldName); + } - /// - /// Check that object has field - /// - /// Target type - /// Name of the field - /// Field value - public static bool HasField(this Type type, string fieldName) - { - return type.GetField(fieldName, Flags) != null; - } + public static bool HasField(this Type type, string fieldName) + { + return type.GetField(fieldName, Flags) != null; + } - /// - /// Get field value from target object - /// - /// Target object - /// Name of the field - /// Field value - public static object GetFieldValue(this object target, string fieldName) - { - return GetFieldValue(target.GetType(), target, fieldName); - } + public static object GetFieldValue(this object target, string fieldName) + { + return GetFieldValue(target.GetType(), target, fieldName); + } - /// - /// Get field value from target object - /// - /// Target object - /// Name of the field - /// TField type-argument - /// Field value - public static TField GetFieldValue(this object target, string fieldName) - { - return (TField)target.GetFieldValue(fieldName); - } + public static TField GetFieldValue(this object target, string fieldName) + { + return (TField)target.GetFieldValue(fieldName); + } - /// - /// Get field value from target object - /// - /// Target type - /// Target object - /// Name of the field - /// Field value - public static object GetFieldValue(this Type type, object target, string fieldName) - { - return type.HasField(fieldName) - ? type.GetField(fieldName, Flags).GetValue(target) - : throw new InvalidOperationException($"Type '{type}' doesn't contain field '{fieldName}'"); - } + public static object GetFieldValue(this Type type, object target, string fieldName) + { + return type.HasField(fieldName) + ? type.GetField(fieldName, Flags).GetValue(target) + : throw new InvalidOperationException($"Type '{type}' doesn't contain field '{fieldName}'"); + } - /// - /// Get field value from target object - /// - /// Target type - /// Target object - /// Name of the field - /// TField type-argument - /// Field value - public static TField GetFieldValue(this Type type, object target, string fieldName) - { - return (TField)type.GetFieldValue(target, fieldName); - } + public static TField GetFieldValue(this Type type, object target, string fieldName) + { + return (TField)type.GetFieldValue(target, fieldName); + } - /// - /// Get dictionary of object property values - /// Key - property name, Value - property value - /// - /// Target object - /// Property dictionary - public static Dictionary ToPropertyDictionary(this object target) - { - return target.GetType() - .GetProperties(Flags | BindingFlags.GetProperty) - .ToDictionary( - info => info.Name, - info => (object?)info.GetValue(target), - StringComparer.OrdinalIgnoreCase); - } + public static bool IsNullable(this PropertyInfo propertyInfo) + { + return IsNullable(propertyInfo, info => info.PropertyType, info => info.GetCustomAttributes()); + } - /// - /// Does property have the initializer (init modifier); - /// - /// PropertyInfo - /// Property have the initializer or not - public static bool HasInitializer(this PropertyInfo propertyInfo) - { - return propertyInfo.SetMethod != default - && propertyInfo.SetMethod.ReturnParameter != null - && propertyInfo.SetMethod.ReturnParameter.GetRequiredCustomModifiers().Any(IsExternalInitTypes.Contains); - } + public static bool IsNullable(this FieldInfo fieldInfo) + { + return IsNullable(fieldInfo, info => info.FieldType, info => info.GetCustomAttributes()); + } - /// - /// Is property nullable - /// - /// PropertyInfo - /// Property is nullable or not - public static bool IsNullable(this PropertyInfo propertyInfo) - { - return IsNullable(propertyInfo, info => info.PropertyType, info => info.GetCustomAttributes()); - } + private static bool IsNullable( + this T memberInfo, + Func typeAccessor, + Func> attributesAccessor) + where T : MemberInfo + { + var isNullableValueType = typeAccessor(memberInfo).IsNullable(); - /// - /// Is field nullable - /// - /// FieldInfo - /// Field is nullable or not - public static bool IsNullable(this FieldInfo fieldInfo) + if (isNullableValueType) { - return IsNullable(fieldInfo, info => info.FieldType, info => info.GetCustomAttributes()); + return true; } - private static bool IsNullable( - this T memberInfo, - Func typeAccessor, - Func> attributesAccessor) - where T : MemberInfo - { - var isNullableValueType = typeAccessor(memberInfo).IsNullable(); - - if (isNullableValueType) - { - return true; - } - - return attributesAccessor(memberInfo).Any(NullableAttributeType.IsInstanceOfType); - } + return attributesAccessor(memberInfo).Any(NullableAttributeType.IsInstanceOfType); } } \ No newline at end of file diff --git a/src/Common/Basics/MethodExecutionInfo.cs b/src/Common/Basics/MethodExecutionInfo.cs deleted file mode 100644 index 0d661eef..00000000 --- a/src/Common/Basics/MethodExecutionInfo.cs +++ /dev/null @@ -1,207 +0,0 @@ -namespace SpaceEngineers.Core.Basics -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Reflection; - using Exceptions; - - /// - /// Execution info of method - /// - public class MethodExecutionInfo - { - private readonly Type _declaringType; - - private readonly string _methodName; - - private readonly ICollection _args = new List(); - - private readonly ICollection _argumentTypes = new List(); - - private readonly ICollection _typeArguments = new List(); - - private object? _target; - - /// .ctor - /// Type that declare the method - /// Name of called method - public MethodExecutionInfo(Type declaringType, string methodName) - { - _declaringType = declaringType; - _methodName = methodName; - } - - /// - /// Set target instance of method call - /// - /// Target instance of method call - /// MethodExecutionInfo - public MethodExecutionInfo ForInstance(object target) - { - _target = target; - - return this; - } - - /// - /// Execute method with argument - /// - /// Argument - /// Argument type - /// MethodExecutionInfo - public MethodExecutionInfo WithArgument(TArgument argument) - { - _args.Add(argument); - _argumentTypes.Add(argument?.GetType() ?? typeof(object)); - - return this; - } - - /// - /// Execute method with argument - /// - /// Argument type - /// Argument - /// MethodExecutionInfo - public MethodExecutionInfo WithArgument(Type argumentType, object? argument) - { - _args.Add(argument); - _argumentTypes.Add(argumentType); - - return this; - } - - /// - /// Execute method with argument - /// - /// Argument - /// Argument type - /// MethodExecutionInfo - public MethodExecutionInfo WithArgument(object? argument) - { - _args.Add(argument); - _argumentTypes.Add(argument?.GetType() ?? typeof(TArgument)); - - return this; - } - - /// - /// Execute method with arguments - /// - /// Arguments - /// MethodExecutionInfo - public MethodExecutionInfo WithArguments(params object[] arguments) - { - foreach (var argument in arguments) - { - _args.Add(argument); - _argumentTypes.Add(argument?.GetType() ?? typeof(object)); - } - - return this; - } - - /// - /// Execute generic method with type argument - /// - /// Type of Type-Argument - /// MethodExecutionInfo - public MethodExecutionInfo WithTypeArgument() - { - _typeArguments.Add(typeof(TTypeArgument)); - - return this; - } - - /// - /// Execute generic method with type argument - /// - /// Type of Type-Argument - /// MethodExecutionInfo - public MethodExecutionInfo WithTypeArgument(Type typeArgument) - { - _typeArguments.Add(typeArgument); - - return this; - } - - /// - /// Execute generic method with type arguments - /// - /// Types of Type-Arguments - /// MethodExecutionInfo - public MethodExecutionInfo WithTypeArguments(params Type[] typeArguments) - { - typeArguments.Each(_typeArguments.Add); - - return this; - } - - /// - /// Invoke configured method - /// - /// TResult type-argument - /// Return value of method - /// Throws if TResult type is mismatched - public TResult Invoke() - { - return Invoke().EnsureType(); - } - - /// - /// Invoke configured method - /// - /// Return value of method - public object? Invoke() - { - // 1 - prepare and check - var isInstanceMethod = _target != null; - - if (isInstanceMethod - && _target.GetType() != _declaringType) - { - throw new TypeMismatchException(_declaringType, _target.GetType()); - } - - // 2 - find - var methodFinder = new MethodFinder( - isInstanceMethod ? _target.GetType() : _declaringType, - _methodName, - GetBindings(isInstanceMethod)) - { - TypeArguments = _typeArguments.ToArray(), - ArgumentTypes = _argumentTypes.ToArray() - }; - - var methodInfo = methodFinder.FindMethod() - ?? throw new NotFoundException($"Method wasn't found: {methodFinder}"); - - // 3 - call - var isGenericMethod = _typeArguments.Any(); - - var constructedMethod = isGenericMethod - ? methodInfo.MakeGenericMethod(_typeArguments.ToArray()) - : methodInfo; - - return ExecutionExtensions - .Try(InvokeMethod, (constructedMethod, _target, _args.ToArray())) - .Catch() - .Invoke(ex => throw ex.Rethrow()); - - static object? InvokeMethod((MethodInfo, object?, object?[]) state) - { - var (methodInfo, target, args) = state; - return methodInfo.Invoke(target, args); - } - } - - private static BindingFlags GetBindings(bool isInstanceMethod) - { - return (isInstanceMethod ? BindingFlags.Instance : BindingFlags.Static) - | BindingFlags.Public - | BindingFlags.NonPublic - | BindingFlags.InvokeMethod; - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/MethodExtensions.cs b/src/Common/Basics/MethodExtensions.cs index 495a712f..d69d7fdc 100644 --- a/src/Common/Basics/MethodExtensions.cs +++ b/src/Common/Basics/MethodExtensions.cs @@ -1,32 +1,25 @@ -namespace SpaceEngineers.Core.Basics +namespace SpaceEngineers.Core.Basics; + +using System; +using System.Reflection; +using Reflection; + +public static class MethodExtensions { - using System; + public static MethodExecutionInfo CallMethod(this Type declaringType, string methodName) + { + return new MethodExecutionInfo(declaringType, methodName); + } - /// - /// System.Type.MethodInfo extensions - /// - public static class MethodExtensions + public static MethodExecutionInfo CallMethod(this object target, string methodName) { - /// - /// Call method by reflection - /// - /// Type that declare the method - /// Method name - /// MethodExecutionInfo - public static MethodExecutionInfo CallMethod(this Type declaringType, string methodName) - { - return new MethodExecutionInfo(declaringType, methodName); - } + return new MethodExecutionInfo(target.GetType(), methodName).ForInstance(target); + } - /// - /// Call method by reflection - /// - /// Target instance of method call - /// Method name - /// MethodExecutionInfo - public static MethodExecutionInfo CallMethod(this object target, string methodName) - { - return new MethodExecutionInfo(target.GetType(), methodName).ForInstance(target); - } + public static MethodInfo GenericMethodDefinitionOrSelf(this MethodInfo method) + { + return method.IsGenericMethod + ? method.GetGenericMethodDefinition() + : method; } } \ No newline at end of file diff --git a/src/Common/Basics/MethodFinder.cs b/src/Common/Basics/MethodFinder.cs deleted file mode 100644 index 854589a1..00000000 --- a/src/Common/Basics/MethodFinder.cs +++ /dev/null @@ -1,127 +0,0 @@ -namespace SpaceEngineers.Core.Basics -{ - using System; - using System.Collections.Concurrent; - using System.Collections.Generic; - using System.Linq; - using System.Reflection; - - /// - /// Method finder - /// - public class MethodFinder - { - private static readonly ConcurrentDictionary Cache - = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - - /// .ctor - /// Type that declare method - /// Method name - /// BindingFlags - public MethodFinder(Type declaringType, - string methodName, - BindingFlags bindingFlags) - { - DeclaringType = declaringType; - MethodName = methodName; - BindingFlags = bindingFlags; - } - - /// Type that declare method - public Type DeclaringType { get; } - - /// Method name - public string MethodName { get; } - - /// BindingFlags - public BindingFlags BindingFlags { get; } - - /// TypeArguments - public IReadOnlyCollection TypeArguments { get; set; } = Array.Empty(); - - /// ArgumentTypes - public IReadOnlyCollection ArgumentTypes { get; set; } = Array.Empty(); - - /// - public override string ToString() - { - var properties = new Dictionary - { - [nameof(DeclaringType)] = DeclaringType.FullName, - [nameof(MethodName)] = MethodName, - [nameof(BindingFlags)] = BindingFlags.ToString("G"), - [nameof(TypeArguments)] = TypeArguments.Select(type => type.FullName).ToString(string.Empty), - [nameof(ArgumentTypes)] = ArgumentTypes.Select(type => type.FullName).ToString(string.Empty) - }; - - return properties.ToString(string.Empty); - } - - /// Find method - /// MethodInfo - public MethodInfo? FindMethod() - { - var key = string.Intern(ToString()); - - return Cache.GetOrAdd(key, _ => Find()); - } - - private MethodInfo? Find() - { - var isGenericMethod = TypeArguments.Any(); - - return isGenericMethod - ? FindGenericMethod() - : FindNonGenericMethod(); - } - - private MethodInfo? FindNonGenericMethod() - { - return DeclaringType.GetMethod(MethodName, BindingFlags, null, ArgumentTypes.ToArray(), null); - } - - private MethodInfo? FindGenericMethod() - { - var methods = DeclaringType - .GetMethods(BindingFlags) - .Where(methodInfo => methodInfo.Name == MethodName - && methodInfo.IsGenericMethod - && ValidateParameters(TypeArguments, methodInfo.GetGenericArguments()) - && ValidateParameters(ArgumentTypes, GetArgumentTypes(methodInfo))) - .ToArray(); - - IReadOnlyCollection GetArgumentTypes(MethodInfo methodInfo) - { - return methodInfo - .MakeGenericMethod(TypeArguments.ToArray()) - .GetParameters() - .Select(z => z.ParameterType) - .ToArray(); - } - - return methods.InformativeSingle(Amb, this); - - static string Amb(MethodFinder methodFinder, IEnumerable source) - { - string Generics(MethodInfo m) => m.GetGenericArguments().Select(g => g.Name).ToString(", "); - string Show(MethodInfo m) => methodFinder.DeclaringType.FullName + "." + m.Name + "[" + Generics(m) + "]"; - return source.Select(Show).ToString(", "); - } - } - - private static bool ValidateParameters(IReadOnlyCollection actual, IReadOnlyCollection expected) - { - if (actual.Count != expected.Count) - { - return false; - } - - return expected.Select((exp, i) => new { Type = exp, i }) - .Join(actual.Select((act, i) => new { Type = act, i }), - exp => exp.i, - act => act.i, - (exp, act) => new { Exp = exp.Type, Act = act.Type }) - .All(pair => pair.Act.FitsForTypeArgument(pair.Exp)); - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/NullableObjectExtensions.cs b/src/Common/Basics/NullableObjectExtensions.cs deleted file mode 100644 index f21fbeb2..00000000 --- a/src/Common/Basics/NullableObjectExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace SpaceEngineers.Core.Basics -{ - using Exceptions; - - /// - /// Extension for work with nullable objects and types - /// - public static class NullableObjectExtensions - { - /// - /// Converts input object to another type - /// - /// input - /// Output type-argument - /// Converted output or exception - /// Throws if TExpected type is mismatched - public static TExpected EnsureType(this object? input) - { - if (input is TExpected expected) - { - return expected; - } - - throw new TypeMismatchException(typeof(TExpected), input.GetType()); - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/ObjectExtensions.DeepCopy.cs b/src/Common/Basics/ObjectExtensions.DeepCopy.cs index 0a35dea0..f81043cb 100644 --- a/src/Common/Basics/ObjectExtensions.DeepCopy.cs +++ b/src/Common/Basics/ObjectExtensions.DeepCopy.cs @@ -1,162 +1,137 @@ -namespace SpaceEngineers.Core.Basics +namespace SpaceEngineers.Core.Basics; + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using EqualityComparers; + +public static partial class ObjectExtensions { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Reflection; - using EqualityComparers; - - /// - /// Object DeepCopy extension methods - /// - public static partial class ObjectExtensions + private static readonly Type RuntimeType = typeof(Type).GetType(); + + private static readonly MethodInfo ShallowCopyMethod = typeof(object).GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance); + + public static T ShallowCopy(this T original) + where T : class { - // ReSharper disable once PossibleMistakenCallToGetType.2 - private static readonly Type RuntimeType = typeof(Type).GetType(); - - private static readonly MethodInfo ShallowCopyMethod = typeof(object).GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance); - - /// - /// Get shallow copy of object - /// Dont copy internal reference links - /// - /// Original object - /// Object type - /// Shallow copy of original object - public static T ShallowCopy(this T original) - where T : class - { - return (T)ShallowCopy((object)original); - } + return (T)ShallowCopy((object)original); + } - /// - /// Get deep copy of object (by reflection) - /// Copy all internal reference links except System.String, System.Type, System.ValueType - /// - /// Original object - /// Object type - /// Deep copy of original object - public static T DeepCopy([DisallowNull] this T original) + public static T DeepCopy([DisallowNull] this T original) + { + return (T?)original.DeepCopyInternal(new Dictionary(new ReferenceEqualityComparer())) + ?? throw new InvalidOperationException("Not nullable input should be copied into not nullable output"); + } + + public static object ShallowCopy(this object original) + { + return ShallowCopyMethod.Invoke(original, null); + } + + private static object? DeepCopyInternal(this object? original, IDictionary visited) + { + if (original == null) { - return (T?)original.DeepCopyInternal(new Dictionary(new ReferenceEqualityComparer())) - ?? throw new InvalidOperationException("Not nullable input should be copied into not nullable output"); + return null; } - /// - /// Get shallow copy of object - /// Dont copy internal reference links - /// - /// Original object - /// Shallow copy of original object - public static object ShallowCopy(this object original) + var typeToReflect = original.GetType(); + + if (visited.TryGetValue(original, out var obj)) { - return ShallowCopyMethod.Invoke(original, null); + return obj; } - private static object? DeepCopyInternal(this object? original, IDictionary visited) + if (IsPrimitive(typeToReflect)) { - if (original == null) - { - return null; - } - - var typeToReflect = original.GetType(); - - if (visited.ContainsKey(original)) - { - return visited[original]; - } + return original; + } - if (IsPrimitive(typeToReflect)) - { - return original; - } + var isCloneable = typeToReflect.IsSubclassOfOpenGeneric(typeof(ICloneable<>)); - var isCloneable = typeToReflect.IsSubclassOfOpenGeneric(typeof(ICloneable<>)); + object clone; - object clone; + if (isCloneable) + { + clone = original + .CallMethod(nameof(ICloneable.Clone)) + .Invoke(); - if (isCloneable) - { - clone = original - .CallMethod(nameof(ICloneable.Clone)) - .Invoke(); + visited.Add(original, clone); + } + else + { + clone = original.ShallowCopy(); - visited.Add(original, clone); - } - else - { - clone = original.ShallowCopy(); + /* + * prevent cyclic reference on yourself + */ + visited.Add(original, clone); - /* - * prevent cyclic reference on yourself - */ - visited.Add(original, clone); + clone.ProcessClone(original, typeToReflect, visited); + } - clone.ProcessClone(original, typeToReflect, visited); - } + return clone; + } - return clone; - } + private static bool IsPrimitive(Type type) + { + return type.IsPrimitive() || type == RuntimeType; + } - private static bool IsPrimitive(Type type) + private static void ProcessClone(this object? clone, object original, Type typeToReflect, IDictionary visited) + { + if (clone == null) { - return type.IsPrimitive() || type == RuntimeType; + return; } - private static void ProcessClone(this object? clone, object original, Type typeToReflect, IDictionary visited) + if (typeToReflect.IsArray()) { - if (clone == null) - { - return; - } - - if (typeToReflect.IsArray()) - { - var arrayClone = (Array)clone; + var arrayClone = (Array)clone; - for (var index = 0; index < arrayClone.Length; ++index) - { - arrayClone.SetValue(arrayClone.GetValue(index).DeepCopyInternal(visited), index); - } - } - else + for (var index = 0; index < arrayClone.Length; ++index) { - original.CopyFields(typeToReflect, clone, visited) - .CopyBaseTypeFields(typeToReflect, clone, visited); + arrayClone.SetValue(arrayClone.GetValue(index).DeepCopyInternal(visited), index); } } - - private static object CopyFields(this object original, - Type typeToReflect, - object clone, - IDictionary visited) + else { - foreach (var fieldInfo in typeToReflect.GetFields(BindingFlags.Instance - | BindingFlags.Public - | BindingFlags.NonPublic - | BindingFlags.DeclaredOnly)) - { - fieldInfo.SetValue(clone, - IsPrimitive(fieldInfo.FieldType) - ? fieldInfo.GetValue(original) - : fieldInfo.GetValue(original).DeepCopyInternal(visited)); - } + original.CopyFields(typeToReflect, clone, visited) + .CopyBaseTypeFields(typeToReflect, clone, visited); + } + } - return original; + private static object CopyFields(this object original, + Type typeToReflect, + object clone, + IDictionary visited) + { + foreach (var fieldInfo in typeToReflect.GetFields(BindingFlags.Instance + | BindingFlags.Public + | BindingFlags.NonPublic + | BindingFlags.DeclaredOnly)) + { + fieldInfo.SetValue(clone, + IsPrimitive(fieldInfo.FieldType) + ? fieldInfo.GetValue(original) + : fieldInfo.GetValue(original).DeepCopyInternal(visited)); } - private static void CopyBaseTypeFields(this object original, - Type typeToReflect, - object clone, - IDictionary visited) + return original; + } + + private static void CopyBaseTypeFields(this object original, + Type typeToReflect, + object clone, + IDictionary visited) + { + if (typeToReflect.BaseType != null) { - if (typeToReflect.BaseType != null) - { - original - .CopyFields(typeToReflect.BaseType, clone, visited) - .CopyBaseTypeFields(typeToReflect.BaseType, clone, visited); - } + original + .CopyFields(typeToReflect.BaseType, clone, visited) + .CopyBaseTypeFields(typeToReflect.BaseType, clone, visited); } } } \ No newline at end of file diff --git a/src/Common/Basics/ObjectExtensions.Nullable.cs b/src/Common/Basics/ObjectExtensions.Nullable.cs new file mode 100644 index 00000000..6f8f1532 --- /dev/null +++ b/src/Common/Basics/ObjectExtensions.Nullable.cs @@ -0,0 +1,16 @@ +namespace SpaceEngineers.Core.Basics; + +using Exceptions; + +public static partial class ObjectExtensions +{ + public static TExpected EnsureType(this object? input) + { + if (input is TExpected expected) + { + return expected; + } + + throw new TypeMismatchException(typeof(TExpected), input.GetType()); + } +} \ No newline at end of file diff --git a/src/Common/Basics/ObjectExtensions.ToString.cs b/src/Common/Basics/ObjectExtensions.ToString.cs index 5a3cf504..188433cb 100644 --- a/src/Common/Basics/ObjectExtensions.ToString.cs +++ b/src/Common/Basics/ObjectExtensions.ToString.cs @@ -1,152 +1,105 @@ -namespace SpaceEngineers.Core.Basics -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Linq; - using System.Reflection; +namespace SpaceEngineers.Core.Basics; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; - /// - /// Object ToString extension methods - /// - public static partial class ObjectExtensions +public static partial class ObjectExtensions +{ + public static string ToString( + this IEnumerable source, + string separator, + Func? projection = null) { - /// - /// Converts enumerable source into string - /// - /// Source - /// Elements separator - /// Projection func - /// TSource type-argument - /// String - public static string ToString( - this IEnumerable source, - string separator, - Func? projection = null) - { - projection ??= static item => item as string ?? item.ToString(); + projection ??= static item => item as string ?? item.ToString(); - return string.Join(separator, source.Select(projection)); - } + return string.Join(separator, source.Select(projection)); + } - /// - /// Converts enumerable source into string - /// - /// Source - /// Elements separator - /// Projection func - /// String - public static string ToString( - this (object first, object second) source, - string separator, - Func? projection = null) - { - return source - .ConstructEnumerable() - .ToString(separator, projection); - } + public static string ToString( + this (object first, object second) source, + string separator, + Func? projection = null) + { + return source + .ConstructEnumerable() + .ToString(separator, projection); + } - /// - /// Converts enumerable source into string - /// - /// Source - /// Elements separator - /// Projection func - /// TSource type-argument - /// String - public static string ToString( - this (TSource first, TSource second) source, - string separator, - Func? projection = null) - { - return source - .ConstructEnumerable() - .ToString(separator, projection); - } + public static string ToString( + this (TSource first, TSource second) source, + string separator, + Func? projection = null) + { + return source + .ConstructEnumerable() + .ToString(separator, projection); + } - /// - /// Converts enumerable source into string - /// - /// Source - /// Elements separator - /// Projection func - /// TSource type-argument - /// String - public static string ToString( - this (TSource first, TSource second, TSource third) source, - string separator, - Func? projection = null) - { - return source - .ConstructEnumerable() - .ToString(separator, projection); - } + public static string ToString( + this (TSource first, TSource second, TSource third) source, + string separator, + Func? projection = null) + { + return source + .ConstructEnumerable() + .ToString(separator, projection); + } - /// - /// Converts enumerable source into string - /// - /// Source - /// Elements separator - /// Projection func - /// String - public static string ToString( - this (object first, object second, object third) source, - string separator, - Func? projection = null) - { - return source - .ConstructEnumerable() - .ToString(separator, projection); - } + public static string ToString( + this (object first, object second, object third) source, + string separator, + Func? projection = null) + { + return source + .ConstructEnumerable() + .ToString(separator, projection); + } - /// - /// Show properties of object - /// - /// Object instance - /// BindingFlags - /// Property values of passed instance - public static string Dump(this object instance, BindingFlags flags) - { - return DumpValue(instance, flags, 0, new HashSet()).ToString(Environment.NewLine); + // TODO: remove or move to test API + public static string Dump(this object instance, BindingFlags flags) + { + return DumpValue(instance, flags, 0, new HashSet()).ToString(Environment.NewLine); - static IEnumerable DumpValue( - object? value, - BindingFlags flags, - int depth, - HashSet visited) + static IEnumerable DumpValue( + object? value, + BindingFlags flags, + int depth, + HashSet visited) + { + if (value != null && value.GetType().IsCollection()) { - if (value != null && value.GetType().IsCollection()) - { - var enumerator = ((IEnumerable)value).GetEnumerator(); + var enumerator = ((IEnumerable)value).GetEnumerator(); - while (enumerator.MoveNext()) + while (enumerator.MoveNext()) + { + foreach (var str in DumpValue(enumerator.Current, flags, depth, visited)) { - foreach (var str in DumpValue(enumerator.Current, flags, depth, visited)) - { - yield return str; - } + yield return str; } } - else if (value != null && !value.GetType().IsPrimitive() && visited.Add(value)) + } + else if (value != null && !value.GetType().IsPrimitive() && visited.Add(value)) + { + var properties = value.GetType().GetProperties(flags); + + foreach (var property in properties) { - var properties = value.GetType().GetProperties(flags); + yield return $"{new string('\t', depth)}{property.Name}"; - foreach (var property in properties) + foreach (var str in DumpValue(property.GetValue(value), flags, depth + 1, visited)) { - yield return $"{new string('\t', depth)}{property.Name}"; - - foreach (var str in DumpValue(property.GetValue(value), flags, depth + 1, visited)) - { - yield return str; - } + yield return str; } - - visited.Remove(value); - } - else - { - yield return $"{new string('\t', depth)}{value?.ToString() ?? "null"}"; } + + visited.Remove(value); + } + else + { + yield return $"{new string('\t', depth)}{value?.ToString() ?? "null"}"; } } } diff --git a/src/Common/Basics/OrderedEnumerableExtensions.cs b/src/Common/Basics/OrderedEnumerableExtensions.cs deleted file mode 100644 index 58e5520a..00000000 --- a/src/Common/Basics/OrderedEnumerableExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace SpaceEngineers.Core.Basics -{ - using System.Collections.Generic; - using System.Linq; - - /// - /// IOrderedEnumerable extensions - /// - public static class OrderedEnumerableExtensions - { - /// - /// Converts IEnumerable to IOrderedEnumerable with order keeping - /// - /// Source stream - /// Item type-argument - /// Ordered stream - public static IOrderedEnumerable AsOrderedEnumerable(this IEnumerable source) - { - return source.OrderBy(_ => 1); - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/PredicateExtensions.cs b/src/Common/Basics/PredicateExtensions.cs index d4c22d27..84e0793c 100644 --- a/src/Common/Basics/PredicateExtensions.cs +++ b/src/Common/Basics/PredicateExtensions.cs @@ -1,148 +1,45 @@ -namespace SpaceEngineers.Core.Basics -{ - using System; - using System.Linq; - using System.Linq.Expressions; - - /// - /// PredicateExtensions - /// - public static class PredicateExtensions - { - /// - /// Not - /// - /// Function - /// T type-argument - /// Negative function - public static Func Not(this Func function) - { - return input => !function.Invoke(input); - } - - /// - /// Not - /// - /// Expression - /// T type-argument - /// Negative expression - public static Expression> Not(this Expression> expression) - { - var parameter = expression.Parameters.Single(); - return Expression.Lambda>(Expression.Not(expression.Body), parameter); - } - - /// - /// And - /// - /// Left expression - /// Right expression - /// T type-argument - /// And expression - public static Expression> And( - this Expression> left, - Expression> right) - { - var parameter = left.Parameters.Single(); - - return Expression.Lambda>( - Expression.AndAlso( - left.Body.ReplaceParameter(parameter), - right.Body.ReplaceParameter(parameter)), - parameter); - } - - /// - /// Or - /// - /// Left expression - /// Right expression - /// T type-argument - /// Or expression - public static Expression> Or( - this Expression> left, - Expression> right) - { - var parameter = left.Parameters.Single(); - - return Expression.Lambda>( - Expression.OrElse( - left.Body.ReplaceParameter(parameter), - right.Body.ReplaceParameter(parameter)), - parameter); - } - - /// - /// Replaces parameter with specified one - /// - /// Source expression - /// Replacement - /// Source expression with replaced parameter - public static Expression ReplaceParameter( - this Expression expression, - ParameterExpression parameterExpression) - { - return ReplaceParameterVisitor.Replace(expression, parameterExpression); - } - - /// - /// Unwraps Quote, Convert, ConvertChecked unary expressions - /// - /// Source expression - /// Source expression without unary expressions - public static Expression UnwrapUnaryExpression( - this Expression expression) - { - var expressionTypes = new[] - { - ExpressionType.Quote, - ExpressionType.Convert, - ExpressionType.ConvertChecked - }; +namespace SpaceEngineers.Core.Basics; - return UnwrapUnaryExpressionVisitor.Unwrap(expression, expressionTypes); - } +using System; +using System.Linq; +using System.Linq.Expressions; - private class ReplaceParameterVisitor : ExpressionVisitor - { - private readonly ParameterExpression _replacement; - - private ReplaceParameterVisitor(ParameterExpression replacement) - { - _replacement = replacement; - } - - public static Expression Replace(Expression expression, ParameterExpression replacement) - { - return new ReplaceParameterVisitor(replacement).Visit(expression); - } +public static class PredicateExtensions +{ + public static Func Not(this Func function) + { + return input => !function.Invoke(input); + } - protected override Expression VisitParameter(ParameterExpression node) - { - return _replacement; - } - } + public static Expression> Not(this Expression> expression) + { + var parameter = expression.Parameters.Single(); + return Expression.Lambda>(Expression.Not(expression.Body), parameter); + } - private class UnwrapUnaryExpressionVisitor : ExpressionVisitor - { - private readonly ExpressionType[] _expressionTypes; + public static Expression> And( + this Expression> left, + Expression> right) + { + var parameter = left.Parameters.Single(); - private UnwrapUnaryExpressionVisitor(ExpressionType[] expressionTypes) - { - _expressionTypes = expressionTypes; - } + return Expression.Lambda>( + Expression.AndAlso( + left.Body.ReplaceParameter(parameter), + right.Body.ReplaceParameter(parameter)), + parameter); + } - public static Expression Unwrap(Expression expression, ExpressionType[] expressionTypes) - { - return new UnwrapUnaryExpressionVisitor(expressionTypes).Visit(expression); - } + public static Expression> Or( + this Expression> left, + Expression> right) + { + var parameter = left.Parameters.Single(); - protected override Expression VisitUnary(UnaryExpression node) - { - return _expressionTypes.Contains(node.NodeType) - ? node.Operand - : base.VisitUnary(node); - } - } + return Expression.Lambda>( + Expression.OrElse( + left.Body.ReplaceParameter(parameter), + right.Body.ReplaceParameter(parameter)), + parameter); } } \ No newline at end of file diff --git a/src/Common/Basics/Primitives/AsyncAutoResetEvent.cs b/src/Common/Basics/Primitives/AsyncAutoResetEvent.cs deleted file mode 100644 index 729643e0..00000000 --- a/src/Common/Basics/Primitives/AsyncAutoResetEvent.cs +++ /dev/null @@ -1,78 +0,0 @@ -namespace SpaceEngineers.Core.Basics.Primitives -{ - using System.Collections.Concurrent; - using System.Threading; - using System.Threading.Tasks; - - /// - /// Async counterpart for AutoResetEvent - /// - public class AsyncAutoResetEvent - { - private static readonly TaskCompletionSource CompletedSource - = CreateCompletedCompletionSource(true); - - private readonly ConcurrentQueue> _waits - = new ConcurrentQueue>(); - - private int _completed; - - /// .cctor - /// AsyncManualResetEvent initial state - signaled or not - public AsyncAutoResetEvent(bool isSet) - { - if (isSet) - { - Interlocked.Increment(ref _completed); - } - } - - /// - /// Wait signaled state asynchronously - /// - /// Cancellation token - /// Waiting operation - public Task WaitAsync(CancellationToken? cancellationToken = null) - { - if (_completed > 0) - { - Interlocked.Decrement(ref _completed); - return CompletedSource.Task; - } - - var tcs = CreateCompletionSource(); - _waits.Enqueue(tcs); - - return cancellationToken != null - ? tcs.Task.WaitAsync(cancellationToken.Value) - : tcs.Task; - } - - /// - /// Set event in signaled state atomically - /// - public void Set() - { - if (_waits.TryDequeue(out var toRelease)) - { - _ = toRelease.TrySetResult(true); - } - else - { - Interlocked.Increment(ref _completed); - } - } - - private static TaskCompletionSource CreateCompletionSource() - { - return new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - } - - private static TaskCompletionSource CreateCompletedCompletionSource(TResult result) - { - var tcs = CreateCompletionSource(); - _ = tcs.TrySetResult(result); - return tcs; - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/Primitives/AsyncCountdownEvent.cs b/src/Common/Basics/Primitives/AsyncCountdownEvent.cs deleted file mode 100644 index 5b73af56..00000000 --- a/src/Common/Basics/Primitives/AsyncCountdownEvent.cs +++ /dev/null @@ -1,109 +0,0 @@ -namespace SpaceEngineers.Core.Basics.Primitives -{ - using System.Threading; - using System.Threading.Tasks; - - /// - /// Async counterpart for CountdownEvent - /// Free interpretation with several differences from original sync event - /// Differences: - /// - Signaled state is state when inner counter has reached zero or initialized with zero - /// - Increment resets event to non signaled state - /// - Decrement sets signaled state if inner counter has reached zero value - /// - public class AsyncCountdownEvent - { - private readonly object _sync; - private TaskCompletionSource _tcs; - private int _count; - - /// .cctor - /// Initial count - public AsyncCountdownEvent(int count) - { - _sync = new object(); - _tcs = CreateCompletionSource(); - _count = count; - - if (_count <= 0) - { - _tcs.TrySetResult(true); - } - } - - /// - /// Wait signaled state asynchronously - /// Signaled state is state when inner counter has reached zero - /// - /// Cancellation token - /// Waiting operation - public Task WaitAsync(CancellationToken? cancellationToken = null) - { - Task waitTask; - - lock (_sync) - { - waitTask = _tcs.Task; - } - - return waitTask.IsCompleted - || cancellationToken == null - ? waitTask - : waitTask.WaitAsync(cancellationToken.Value); - } - - /// - /// Increments inner counter atomically and resets to non signaled state - /// - /// Actual counter state - public int Increment() - { - lock (_sync) - { - if (_tcs.Task.IsCompleted) - { - _tcs = CreateCompletionSource(); - } - - return ++_count; - } - } - - /// - /// Decrements inner counter atomically - /// Set signaled state if inner counter has reached zero value - /// - /// Actual counter state - public int Decrement() - { - lock (_sync) - { - var result = --_count; - - if (result <= 0) - { - _ = _tcs.TrySetResult(true); - } - - return result; - } - } - - /// - /// Reads current counter state - /// - /// Actual counter state - public int Read() - { - lock (_sync) - { - return _count; - } - } - - private static TaskCompletionSource CreateCompletionSource() - { - return new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/Primitives/AsyncDisposable.cs b/src/Common/Basics/Primitives/AsyncDisposable.cs deleted file mode 100644 index 22119d76..00000000 --- a/src/Common/Basics/Primitives/AsyncDisposable.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace SpaceEngineers.Core.Basics.Primitives -{ - using System; - using System.Diagnostics.CodeAnalysis; - using System.Threading.Tasks; - - /// - /// Async disposable - /// - public static class AsyncDisposable - { - /// - /// Empty async disposable - /// - [SuppressMessage("Analysis", "SA1129", Justification = "explicit details")] - public static EmptyAsyncDisposable Empty { get; } = new EmptyAsyncDisposable(); - - /// - /// Creates IAsyncDisposable - /// - /// finallyAction - /// IDisposable - public static AsyncDisposableAction Create(Func finallyAction) - { - return new AsyncDisposableAction(finallyAction.Invoke); - } - - /// - /// Creates IAsyncDisposable - /// - /// state - /// finallyAction - /// TState - /// IDisposable - public static AsyncDisposableAction Create(TState state, Func finallyAction) - { - return new AsyncDisposableAction(state, finallyAction); - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/Primitives/AsyncDisposableAction.cs b/src/Common/Basics/Primitives/AsyncDisposableAction.cs deleted file mode 100644 index 4493a455..00000000 --- a/src/Common/Basics/Primitives/AsyncDisposableAction.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace SpaceEngineers.Core.Basics.Primitives -{ - using System; - using System.Diagnostics.CodeAnalysis; - using System.Threading.Tasks; - - /// - /// AsyncDisposableAction - /// - /// TState type-argument - [SuppressMessage("Analysis", "CA1815", Justification = "unnecessary equality")] - public struct AsyncDisposableAction : IAsyncDisposable - { - private readonly TState _state; - private readonly Func _finallyAction; - - internal AsyncDisposableAction(TState state, Func finallyAction) - { - _state = state; - _finallyAction = finallyAction; - } - - /// - public async ValueTask DisposeAsync() - { - await _finallyAction.Invoke(_state).ConfigureAwait(false); - } - } - - /// - /// AsyncDisposableAction - /// - [SuppressMessage("Analysis", "CA1815", Justification = "unnecessary equality")] - public struct AsyncDisposableAction : IAsyncDisposable - { - private readonly Func _finallyAction; - - internal AsyncDisposableAction(Func finallyAction) - { - _finallyAction = finallyAction; - } - - /// - public async ValueTask DisposeAsync() - { - await _finallyAction.Invoke().ConfigureAwait(false); - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/Primitives/AsyncLazy.cs b/src/Common/Basics/Primitives/AsyncLazy.cs deleted file mode 100644 index 29b685b9..00000000 --- a/src/Common/Basics/Primitives/AsyncLazy.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace SpaceEngineers.Core.Basics.Primitives -{ - using System; - using System.Threading; - using System.Threading.Tasks; - - /// - /// Async lazy structure - /// - /// T type-argument - public class AsyncLazy - { - private readonly AsyncManualResetEvent _manualResetEvent; - private readonly Func> _producer; - - private T? _value; - private int _produced; - - /// .cctor - /// Value producer - public AsyncLazy(Func> producer) - { - _manualResetEvent = new AsyncManualResetEvent(false); - _producer = producer; - - _value = default; - _produced = 0; - } - - /// - /// Gets value in lazy and asynchronous manner - /// - /// Cancellation token - /// Ongoing lazy calculation - public async Task GetValue(CancellationToken? token = null) - { - token ??= CancellationToken.None; - - if (Interlocked.Exchange(ref _produced, 1) == 1) - { - await _manualResetEvent.WaitAsync(token.Value).ConfigureAwait(false); - } - else - { - _value = await _producer(token.Value).ConfigureAwait(false); - _manualResetEvent.Set(); - } - - return _value !; - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/Primitives/AsyncManualResetEvent.cs b/src/Common/Basics/Primitives/AsyncManualResetEvent.cs deleted file mode 100644 index 3b06cb1e..00000000 --- a/src/Common/Basics/Primitives/AsyncManualResetEvent.cs +++ /dev/null @@ -1,80 +0,0 @@ -namespace SpaceEngineers.Core.Basics.Primitives -{ - using System.Threading; - using System.Threading.Tasks; - - /// - /// Async counterpart for AsyncManualResetEvent - /// - public class AsyncManualResetEvent - { - private readonly object _sync; - - private TaskCompletionSource _tcs; - - /// .cctor - /// AsyncManualResetEvent initial state - signaled or not - public AsyncManualResetEvent(bool isSet) - { - _sync = new object(); - - _tcs = CreateCompletionSource(); - - if (isSet) - { - _tcs.TrySetResult(true); - } - } - - /// - /// Wait signaled state asynchronously - /// - /// Cancellation token - /// Waiting operation - public Task WaitAsync(CancellationToken? cancellationToken = null) - { - Task waitTask; - - lock (_sync) - { - waitTask = _tcs.Task; - } - - return waitTask.IsCompleted - || cancellationToken == null - ? waitTask - : waitTask.WaitAsync(cancellationToken.Value); - } - - /// - /// Set event in signaled state atomically - /// - public void Set() - { - lock (_sync) - { - _tcs.TrySetResult(true); - } - } - - /// - /// Set event in non signaled state atomically - /// Do nothing in case of non signaled state - /// - public void Reset() - { - lock (_sync) - { - if (_tcs.Task.IsCompleted) - { - _tcs = CreateCompletionSource(); - } - } - } - - private static TaskCompletionSource CreateCompletionSource() - { - return new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/Primitives/AsyncUnitOfWork.cs b/src/Common/Basics/Primitives/AsyncUnitOfWork.cs deleted file mode 100644 index e11a2b4c..00000000 --- a/src/Common/Basics/Primitives/AsyncUnitOfWork.cs +++ /dev/null @@ -1,132 +0,0 @@ -namespace SpaceEngineers.Core.Basics.Primitives -{ - using System; - using System.Threading; - using System.Threading.Tasks; - using Enumerations; - - /// - public abstract class AsyncUnitOfWork : IAsyncUnitOfWork - { - private readonly Exclusive _exclusive = new Exclusive(); - - /// - public async Task ExecuteInTransaction( - TContext context, - Func producer, - bool saveChanges, - CancellationToken token) - { - using (await _exclusive.Run(token).ConfigureAwait(false)) - { - var (behavior, startError) = await StartTransactionUnsafe(context, token) - .TryAsync() - .Catch() - .Invoke(StartExceptionResult, token) - .ConfigureAwait(false); - - if (startError != null) - { - throw startError.Rethrow(); - } - - if (behavior is EnUnitOfWorkBehavior.DoNotRun) - { - return; - } - - Exception? executionError = default; - - if (behavior is not EnUnitOfWorkBehavior.SkipProducer) - { - executionError = await ExecuteProducerUnsafe(context, producer, token) - .TryAsync() - .Catch() - .Invoke(ExceptionResult, token) - .ConfigureAwait(false); - } - - var finishError = await FinishTransactionUnsafe(context, saveChanges, executionError, token) - .TryAsync() - .Catch() - .Invoke(ExceptionResult, token) - .ConfigureAwait(false); - - var error = executionError ?? finishError; - - if (error != null) - { - throw error.Rethrow(); - } - } - } - - /// - /// Runs on start operations - /// - /// Context - /// Cancellation token - /// Ongoing operations - protected virtual Task Start(TContext context, CancellationToken token) - { - return Task.FromResult(EnUnitOfWorkBehavior.Regular); - } - - /// - /// Rollback logical transaction - /// - /// Context - /// Producers exception (optional) - /// Cancellation token - /// Ongoing rollback operation - protected virtual Task Rollback(TContext context, Exception? exception, CancellationToken token) - { - return Task.CompletedTask; - } - - /// - /// Commit logical transaction - /// - /// Context - /// Cancellation token - /// Ongoing commit operation - protected virtual Task Commit(TContext context, CancellationToken token) - { - return Task.CompletedTask; - } - - private async Task<(EnUnitOfWorkBehavior, Exception?)> StartTransactionUnsafe(TContext context, CancellationToken token) - { - var behavior = await Start(context, token).ConfigureAwait(false); - return (behavior, null); - } - - private static async Task ExecuteProducerUnsafe(TContext context, Func producer, CancellationToken token) - { - await producer.Invoke(context, token).ConfigureAwait(false); - - return null; - } - - private async Task FinishTransactionUnsafe(TContext context, bool saveChanges, Exception? exception, CancellationToken token) - { - var finishOperation = saveChanges && exception == null - ? Commit(context, token) - : Rollback(context, exception, token); - - await finishOperation.ConfigureAwait(false); - - return null; - } - - private static (EnUnitOfWorkBehavior, Exception?) StartExceptionResult(Exception exception) - { - return (EnUnitOfWorkBehavior.DoNotRun, (Exception?)exception); - } - - private static Exception? ExceptionResult(Exception exception) - { - return exception; - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/Primitives/BinaryHeap.cs b/src/Common/Basics/Primitives/BinaryHeap.cs deleted file mode 100644 index bd4b3622..00000000 --- a/src/Common/Basics/Primitives/BinaryHeap.cs +++ /dev/null @@ -1,403 +0,0 @@ -namespace SpaceEngineers.Core.Basics.Primitives -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Linq; - using Enumerations; - - /// - /// Binary-heap - /// - /// TElement type-argument - public class BinaryHeap : IHeap - where TElement : IEquatable, IComparable, IComparable - { - private const int Root = 0; - - private readonly EnOrderingDirection _orderingDirection; - - private int _last; - private int _height; - private TElement[] _array; - - /// .cctor - /// EnOrderingDirection - public BinaryHeap(EnOrderingDirection orderingDirection) - { - _orderingDirection = orderingDirection; - - _last = -1; - _height = 0; - _array = Array.Empty(); - } - - /// .cctor - /// Source stream - /// EnOrderingDirection - public BinaryHeap(IEnumerable source, EnOrderingDirection orderingDirection) - : this(orderingDirection) - { - foreach (var element in source) - { - Insert(element); - } - } - - /// - public event EventHandler>? RootNodeChanged; - - private event EventHandler? Changed; - - /// - public int Count => Length(); - - /// - public bool IsEmpty => IsEmptyHeap(); - - /// - public IEnumerator GetEnumerator() - { - return new BinaryHeapEnumerator(this); - } - - /// - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - /// - public override string ToString() - { - return _array - .Select((element, i) => - { - var height = Height(i); - return (element, height); - }) - .GroupBy(pair => pair.height) - .Select(pair => pair.Select(it => it.element)) - .Select(pair => pair.ToString(" ")) - .ToString(Environment.NewLine); - } - - /// - public TElement[] ExtractArray() - { - var result = new TElement[Length()]; - - for (var i = 0; i < result.Length; i++) - { - result[i] = ExtractFromHeap(); - } - - return result; - } - - /// - public void Insert(TElement element) - { - /* - * 1. Add a new element to the end of the heap - * 2. Heapify-up starting from the last element - */ - - var originalRoot = GetRoot(); - - AddLast(element); - HeapifyUp(_last); - - NotifyChanged(); - NotifyRootNodeChanged(originalRoot); - } - - /// - public TElement Peek() - { - return PeekHeap(); - } - - /// - public bool TryPeek([NotNullWhen(true)] out TElement? element) - { - return Try(PeekHeap, out element); - } - - /// - public TElement Extract() - { - return ExtractFromHeap(); - } - - /// - public bool TryExtract([NotNullWhen(true)] out TElement? element) - { - return Try(ExtractFromHeap, out element); - } - - #region internals - - private bool Try(Func accessor, [NotNullWhen(true)] out TElement? element) - { - if (IsEmptyHeap()) - { - element = default; - return false; - } - - element = accessor(); - return true; - } - - private TElement PeekHeap() - { - return GetRequiredRoot(); - } - - private TElement ExtractFromHeap() - { - /* - * 1. Replace the root of the heap with the last element - * 2. Heapify-down starting from the root element - */ - - var originalRoot = GetRequiredRoot(); - - Swap(Root, _last); - RemoveLast(); - HeapifyDown(Root); - - NotifyChanged(); - NotifyRootNodeChanged(originalRoot); - - return originalRoot; - } - - private bool IsEmptyHeap() - { - return _last < 0; - } - - private int Length() - { - return _last + 1; - } - - private void HeapifyUp(int child) - { - /* - * 1. Compare the added element with its parent - * If they are in the correct order, stop - * If not, swap the element with its parent and return to the previous step - */ - - var parent = Parent(child); - - if (HasHighestPriority(child, parent)) - { - Swap(child, parent); - HeapifyUp(parent); - } - } - - private void HeapifyDown(int parent) - { - /* - * 1. Compare a parent with his children - * If they are in the correct order, stop - * 2. If not, swap the parent with one of his children and return to the previous step - * Swap with the highest priority child - */ - - var theHighestPriority = parent; - var left = Left(theHighestPriority); - var right = Right(theHighestPriority); - - if (left <= _last - && HasHighestPriority(left, theHighestPriority)) - { - theHighestPriority = left; - } - - if (right <= _last - && HasHighestPriority(right, theHighestPriority)) - { - theHighestPriority = right; - } - - if (theHighestPriority == parent) - { - return; - } - - Swap(parent, theHighestPriority); - HeapifyDown(theHighestPriority); - } - - private bool HasHighestPriority(int index, int theHighestPriority) - { - var result = _array[index].CompareTo(_array[theHighestPriority]); - - return _orderingDirection == EnOrderingDirection.Asc - ? result < 0 - : result > 0; - } - - private TElement? GetRoot() - { - return !IsEmptyHeap() - ? _array[Root] - : default; - } - - private TElement GetRequiredRoot() - { - if (IsEmptyHeap()) - { - throw new InvalidOperationException($"{nameof(BinaryHeap)} is empty"); - } - - return _array[Root]; - } - - private void AddLast(TElement element) - { - Expand(); - - _last++; - _array[_last] = element; - } - - private void RemoveLast() - { - _array[_last] = default!; - _last--; - - Contract(); - } - - private void Expand() - { - if (Length() < _array.Length) - { - return; - } - - _array = Resize(_array, ++_height, Length()); - } - - private void Contract() - { - var limitedCapacity = Capacity(_height - 1); - - if (limitedCapacity > _last - && limitedCapacity < _array.Length) - { - _array = Resize(_array, --_height, Length()); - } - } - - private static TElement[] Resize(TElement[] source, int height, int length) - { - var resized = new TElement[Capacity(height)]; - Array.Copy(source, resized, length); - return resized; - } - - private void Swap(int left, int right) - { - if (left == right) - { - return; - } - - (_array[left], _array[right]) = (_array[right], _array[left]); - } - - private static int Left(int index) => (2 * index) + 1; - - private static int Right(int index) => (2 * index) + 2; - - private static int Parent(int index) => (index - 1) / 2; - - private static uint Height(int index) => (index + 1).Log(2); - - private static int Capacity(int height) => 2.Pow((uint)height) - 1; - - private void NotifyChanged() - { - Changed?.Invoke(this, new EventArgs()); - } - - private void NotifyRootNodeChanged(TElement? originalRoot) - { - var currentRoot = GetRoot(); - - RootNodeChanged?.Invoke(this, new RootNodeChangedEventArgs(originalRoot, currentRoot)); - } - - #endregion - - private class BinaryHeapEnumerator : IEnumerator - where T : IEquatable, IComparable, IComparable - { - private readonly BinaryHeap _heap; - private readonly EventHandler _subscription; - - private int _current; - private bool _changed; - - public BinaryHeapEnumerator(BinaryHeap heap) - { - _heap = heap; - _current = -1; - - _subscription = OnChanged; - _heap.Changed += _subscription; - } - - public T Current - { - get - { - ValidateNoChanges(); - return _heap._array[_current]; - } - } - - object IEnumerator.Current => Current; - - public bool MoveNext() - { - ValidateNoChanges(); - return ++_current <= _heap._last; - } - - public void Reset() - { - _current = -1; - } - - public void Dispose() - { - _heap.Changed -= _subscription; - Reset(); - } - - private void OnChanged(object sender, EventArgs e) - { - _changed = true; - } - - private void ValidateNoChanges() - { - if (_changed) - { - throw new InvalidOperationException("Collection was modified during enumeration"); - } - } - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/Primitives/CompositeDisposable.cs b/src/Common/Basics/Primitives/CompositeDisposable.cs deleted file mode 100644 index 6a45d6fc..00000000 --- a/src/Common/Basics/Primitives/CompositeDisposable.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace SpaceEngineers.Core.Basics.Primitives -{ - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - - /// - /// CompositeDisposable - /// - [SuppressMessage("Analysis", "CA1815", Justification = "unnecessary equality")] - public struct CompositeDisposable : IDisposable - { - private readonly Stack _disposables = new Stack(); - - internal CompositeDisposable(params IDisposable[] disposables) - { - foreach (var disposable in disposables) - { - _disposables.Push(disposable); - } - } - - /// Push - /// IDisposable - public void Push(IDisposable disposable) - { - _disposables.Push(disposable); - } - - /// - public void Dispose() - { - _disposables.Each(d => d.Dispose()); - _disposables.Clear(); - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/Primitives/DeferredQueue.cs b/src/Common/Basics/Primitives/DeferredQueue.cs deleted file mode 100644 index 90be76ac..00000000 --- a/src/Common/Basics/Primitives/DeferredQueue.cs +++ /dev/null @@ -1,260 +0,0 @@ -namespace SpaceEngineers.Core.Basics.Primitives -{ - using System; - using System.Diagnostics.CodeAnalysis; - using System.Threading; - using System.Threading.Tasks; - - /// - /// DeferredQueue - /// - /// TElement type-argument - public class DeferredQueue : IQueue, - IAsyncQueue - where TElement : IEquatable, - IComparable, - IComparable - { - private readonly Exclusive _exclusive = new Exclusive(); - - private readonly TimeSpan _high = TimeSpan.FromMilliseconds(42); - private readonly TimeSpan _low = TimeSpan.FromMilliseconds(1); - - private readonly IHeap> _heap; - private readonly PriorityQueue _priorityQueue; - private readonly Func _prioritySelector; - - private Task? _delay; - private CancellationTokenSource? _cts; - - /// .cctor - /// Heap implementation - /// Priority selector - public DeferredQueue( - IHeap> heap, - Func prioritySelector) - { - _heap = heap; - _prioritySelector = prioritySelector; - _priorityQueue = new PriorityQueue(heap, prioritySelector); - } - - #region IQueue - - /// - public int Count - { - get - { - lock (_priorityQueue) - { - return _priorityQueue.Count; - } - } - } - - /// - public bool IsEmpty - { - get - { - lock (_priorityQueue) - { - return _priorityQueue.IsEmpty; - } - } - } - - /// - public void Enqueue(TElement element) - { - lock (_priorityQueue) - { - _priorityQueue.Enqueue(element); - } - } - - /// - public TElement Dequeue() - { - throw new NotSupportedException(nameof(Dequeue)); - } - - /// - public bool TryDequeue([NotNullWhen(true)] out TElement? element) - { - throw new NotSupportedException(nameof(TryDequeue)); - } - - /// - public TElement Peek() - { - throw new NotSupportedException(nameof(Peek)); - } - - /// - public bool TryPeek([NotNullWhen(true)] out TElement? element) - { - throw new NotSupportedException(nameof(TryPeek)); - } - - #endregion - - #region IAsyncQueue - - /// - public Task Enqueue(TElement element, CancellationToken token) - { - Enqueue(element); - return Task.CompletedTask; - } - - /// - public async Task Run(Func callback, CancellationToken token) - { - using (await _exclusive.Run(token).ConfigureAwait(false)) - using (Disposable.Create( - new EventHandler>>(CancelScheduleOnRootNodeChanged), - subscription => _heap.RootNodeChanged += subscription, - subscription => _heap.RootNodeChanged -= subscription)) - { - while (!token.IsCancellationRequested) - { - (_delay, _cts) = Schedule(token); - - try - { - await _delay.ConfigureAwait(false); - } - catch (OperationCanceledException) - { - continue; - } - - var args = DequeueSync(); - var planned = _prioritySelector(args); - - try - { - await WaitForBreadcrumbs(planned, token).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - } - - await callback(args, token).ConfigureAwait(false); - } - } - - await CancelSchedule().ConfigureAwait(false); - token.ThrowIfCancellationRequested(); - - async void CancelScheduleOnRootNodeChanged(object sender, RootNodeChangedEventArgs> args) - { - await CancelSchedule().ConfigureAwait(false); - } - } - - #endregion - - private TElement DequeueSync() - { - lock (_priorityQueue) - { - return _priorityQueue.Dequeue(); - } - } - - private (Task, CancellationTokenSource?) Schedule(CancellationToken token) - { - lock (_priorityQueue) - { - _ = _priorityQueue.TryPeek(out var element); - - return element == null - ? InfiniteDelay(token) - : ElementDelay(element, token); - } - } - - private static (Task, CancellationTokenSource?) InfiniteDelay(CancellationToken token) - { - var cts = CancellationTokenSource.CreateLinkedTokenSource(token); - var delay = Task.Delay(Timeout.InfiniteTimeSpan, cts.Token); - - return (delay, cts); - } - - private (Task, CancellationTokenSource?) ElementDelay(TElement element, CancellationToken token) - { - Task delay; - CancellationTokenSource? cts; - - var now = DateTime.UtcNow; - var planned = _prioritySelector(element).ToUniversalTime(); - - if (planned <= now) - { - delay = Task.CompletedTask; - cts = null; - } - else - { - cts = CancellationTokenSource.CreateLinkedTokenSource(token); - delay = Task.Delay(planned - now, cts.Token); - } - - return (delay, cts); - } - - private async Task CancelSchedule() - { - var cts = Interlocked.Exchange(ref _cts, null); - - if (cts == null - || typeof(CancellationTokenSource).GetFieldValue(cts, "_disposed")) - { - return; - } - - using (cts) - { - cts.Cancel(); - - try - { - await _delay.ConfigureAwait(false); - } - catch (OperationCanceledException) - { - } - } - } - - private async Task WaitForBreadcrumbs(DateTime planned, CancellationToken token) - { - var now = DateTime.UtcNow; - - if (planned <= now) - { - return; - } - - var delta = planned - now; - - if (delta < _low) - { - return; - } - - if (_low <= delta && delta <= _high) - { - await Task.Delay(delta, token).ConfigureAwait(false); - await WaitForBreadcrumbs(planned, token).ConfigureAwait(false); - return; - } - - throw new InvalidOperationException($"Operation was started earlier than planned in {delta.TotalMilliseconds} ms: planned: {planned:O}; now: {now:O};"); - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/Primitives/Disposable.cs b/src/Common/Basics/Primitives/Disposable.cs deleted file mode 100644 index 67f6fb6a..00000000 --- a/src/Common/Basics/Primitives/Disposable.cs +++ /dev/null @@ -1,75 +0,0 @@ -namespace SpaceEngineers.Core.Basics.Primitives -{ - using System; - using System.Diagnostics.CodeAnalysis; - - /// - /// Disposable - /// - public static class Disposable - { - /// - /// Gets empty disposable object - /// - [SuppressMessage("Analysis", "SA1129", Justification = "explicit details")] - public static EmptyDisposable Empty { get; } = new EmptyDisposable(); - - /// - /// Creates disposable object - /// - /// state - /// openScopeAction - /// finallyAction - /// TState - /// IDisposable - public static DisposableAction Create(TState state, Action openScopeAction, Action finallyAction) - { - openScopeAction(state); - return new DisposableAction(state, finallyAction); - } - - /// - /// Creates disposable object - /// - /// state - /// finallyAction - /// TState - /// IDisposable - public static DisposableAction Create(TState state, Action finallyAction) - { - return new DisposableAction(state, finallyAction); - } - - /// - /// Creates disposable object - /// - /// openScopeAction - /// finallyAction - /// IDisposable - public static DisposableAction Create(Action openScopeAction, Action finallyAction) - { - openScopeAction(); - return new DisposableAction(finallyAction); - } - - /// - /// Creates disposable object - /// - /// finallyAction - /// IDisposable - public static DisposableAction Create(Action finallyAction) - { - return new DisposableAction(finallyAction); - } - - /// - /// Custom composite disposable (disposes like stack - LIFO) - /// - /// disposables - /// ICompositeDisposable (disposes like stack - LIFO) - public static CompositeDisposable CreateComposite(params IDisposable[] disposables) - { - return new CompositeDisposable(disposables); - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/Primitives/DisposableAction.cs b/src/Common/Basics/Primitives/DisposableAction.cs deleted file mode 100644 index cbaf2b93..00000000 --- a/src/Common/Basics/Primitives/DisposableAction.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace SpaceEngineers.Core.Basics.Primitives -{ - using System; - using System.Diagnostics.CodeAnalysis; - - /// - /// DisposableAction - /// - /// TState type-argument - [SuppressMessage("Analysis", "CA1815", Justification = "unnecessary equality")] - public struct DisposableAction : IDisposable - { - private readonly TState _state; - private readonly Action _finallyAction; - - internal DisposableAction(TState state, Action finallyAction) - { - _state = state; - _finallyAction = finallyAction; - } - - /// - public void Dispose() - { - _finallyAction.Invoke(_state); - } - } - - /// - /// DisposableAction - /// - [SuppressMessage("Analysis", "CA1815", Justification = "unnecessary equality")] - public struct DisposableAction : IDisposable - { - private readonly Action _finallyAction; - - internal DisposableAction(Action finallyAction) - { - _finallyAction = finallyAction; - } - - /// - public void Dispose() - { - _finallyAction.Invoke(); - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/Primitives/EmptyAsyncDisposable.cs b/src/Common/Basics/Primitives/EmptyAsyncDisposable.cs deleted file mode 100644 index 711ed285..00000000 --- a/src/Common/Basics/Primitives/EmptyAsyncDisposable.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace SpaceEngineers.Core.Basics.Primitives -{ - using System; - using System.Diagnostics.CodeAnalysis; - using System.Threading.Tasks; - - /// - /// EmptyAsyncDisposable - /// - [SuppressMessage("Analysis", "CA1815", Justification = "unnecessary equality")] - public struct EmptyAsyncDisposable : IAsyncDisposable - { - /// - public async ValueTask DisposeAsync() - { - await Task.CompletedTask.ConfigureAwait(false); - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/Primitives/EmptyDisposable.cs b/src/Common/Basics/Primitives/EmptyDisposable.cs deleted file mode 100644 index 3826769d..00000000 --- a/src/Common/Basics/Primitives/EmptyDisposable.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace SpaceEngineers.Core.Basics.Primitives -{ - using System; - using System.Diagnostics.CodeAnalysis; - - /// - /// EmptyDisposable - /// - [SuppressMessage("Analysis", "CA1815", Justification = "unnecessary equality")] - public struct EmptyDisposable : IDisposable - { - /// - public void Dispose() - { - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/Primitives/HeapEntry.cs b/src/Common/Basics/Primitives/HeapEntry.cs deleted file mode 100644 index 363e14b8..00000000 --- a/src/Common/Basics/Primitives/HeapEntry.cs +++ /dev/null @@ -1,143 +0,0 @@ -namespace SpaceEngineers.Core.Basics.Primitives -{ - using System; - - /// - /// Heap entry - /// - /// TElement type-argument - /// TKey type-argument - public class HeapEntry : IEquatable>, - ISafelyEquatable>, - ISafelyComparable>, - IComparable>, - IComparable - where TKey : IEquatable, IComparable, IComparable - { - private readonly TKey _key; - - /// - /// .cctor - /// - /// Element key - /// Element - public HeapEntry(TKey key, TElement element) - { - _key = key; - Element = element; - } - - /// - /// Element - /// - public TElement Element { get; } - - /// - /// Equality == - /// - /// Left - /// Right - /// Operation result - public static bool operator ==(HeapEntry? left, HeapEntry? right) - { - return Equatable.Equals(left, right); - } - - /// - /// Equality != - /// - /// Left - /// Right - /// Operation result - public static bool operator !=(HeapEntry? left, HeapEntry? right) - { - return !Equatable.Equals(left, right); - } - - /// - /// Less operator - /// - /// Left - /// Right - /// Operation result - public static bool operator <(HeapEntry? left, HeapEntry? right) - { - return Comparable.Less(left, right); - } - - /// - /// Greater operator - /// - /// Left - /// Right - /// Operation result - public static bool operator >(HeapEntry? left, HeapEntry? right) - { - return Comparable.Greater(left, right); - } - - /// - /// Less or equals operator - /// - /// Left - /// Right - /// Operation result - public static bool operator <=(HeapEntry? left, HeapEntry? right) - { - return Comparable.LessOrEquals(left, right); - } - - /// - /// Greater or equals operator - /// - /// Left - /// Right - /// Operation result - public static bool operator >=(HeapEntry? left, HeapEntry? right) - { - return Comparable.GreaterOrEquals(left, right); - } - - /// - public int SafeCompareTo(HeapEntry other) - { - return _key.CompareTo(other._key); - } - - /// - public int CompareTo(HeapEntry? other) - { - return Comparable.CompareTo(this, other); - } - - /// - public int CompareTo(object? obj) - { - return Comparable.CompareTo(this, obj); - } - - /// - public bool SafeEquals(HeapEntry other) - { - return _key.Equals(other._key); - } - - /// - public bool Equals(HeapEntry? other) - { - return Equatable.Equals(this, other); - } - - /// - public override bool Equals(object? obj) - { - return Equatable.Equals(this, obj); - } - - /// - public override int GetHashCode() - { - return _key.GetHashCode(); - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/Primitives/IAsyncQueue.cs b/src/Common/Basics/Primitives/IAsyncQueue.cs deleted file mode 100644 index 1bb72947..00000000 --- a/src/Common/Basics/Primitives/IAsyncQueue.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace SpaceEngineers.Core.Basics.Primitives -{ - using System; - using System.Threading; - using System.Threading.Tasks; - - /// - /// IAsyncQueue abstraction - /// - /// TElement type-argument - public interface IAsyncQueue - { - /// Enqueue - /// Element - /// Cancellation token - /// Ongoing operation - Task Enqueue(TElement element, CancellationToken token); - - /// - /// Starts elements processing - /// - /// Callback function for next element - /// Cancellation token - /// Ongoing operation - Task Run(Func callback, CancellationToken token); - } -} \ No newline at end of file diff --git a/src/Common/Basics/Primitives/IAsyncUnitOfWork.cs b/src/Common/Basics/Primitives/IAsyncUnitOfWork.cs deleted file mode 100644 index b878f2ac..00000000 --- a/src/Common/Basics/Primitives/IAsyncUnitOfWork.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace SpaceEngineers.Core.Basics.Primitives -{ - using System; - using System.Threading; - using System.Threading.Tasks; - - /// - /// Represents logical transaction that tracks different kinds of resources and maintains consistency - /// - /// TContext type-argument - public interface IAsyncUnitOfWork - { - /// - /// Executes producer within logical transaction - /// - /// Context data container - /// Producer - /// Save changes. By default changes are going to be rolled back - /// Cancellation token - /// Ongoing start operation - Task ExecuteInTransaction( - TContext context, - Func producer, - bool saveChanges, - CancellationToken token); - } -} \ No newline at end of file diff --git a/src/Common/Basics/Primitives/IHeap.cs b/src/Common/Basics/Primitives/IHeap.cs deleted file mode 100644 index 781b1b89..00000000 --- a/src/Common/Basics/Primitives/IHeap.cs +++ /dev/null @@ -1,67 +0,0 @@ -namespace SpaceEngineers.Core.Basics.Primitives -{ - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - - /// - /// IHeap - /// - /// TElement type-argument - public interface IHeap : IEnumerable - where TElement : IEquatable, IComparable, IComparable - { - /// - /// Root node changed - /// - event EventHandler>? RootNodeChanged; - - /// - /// Gets the number of elements contained in the heap - /// - int Count { get; } - - /// - /// Gets a value that indicates whether the heap is empty - /// - bool IsEmpty { get; } - - /// - /// Adds an element to the heap - /// - /// Element - void Insert(TElement element); - - /// - /// Gets the highest-priority element but doesn't modify the heap - /// - /// The highest-priority element - TElement Peek(); - - /// - /// Gets the highest-priority element but doesn't modify the heap if it isn't empty - /// - /// The highest-priority element or default value - /// Successful operation or not - bool TryPeek([NotNullWhen(true)] out TElement? element); - - /// - /// Gets the highest-priority element and removes it from the heap - /// - /// The highest-priority element - TElement Extract(); - - /// - /// Gets the highest-priority element and removes it from the heap if it isn't empty - /// - /// The highest-priority element or default value - /// Successful operation or not - bool TryExtract([NotNullWhen(true)] out TElement? element); - - /// - /// Extract sorted array - /// - /// Array with priority ordering - TElement[] ExtractArray(); - } -} \ No newline at end of file diff --git a/src/Common/Basics/Primitives/IQueue.cs b/src/Common/Basics/Primitives/IQueue.cs deleted file mode 100644 index 10459c99..00000000 --- a/src/Common/Basics/Primitives/IQueue.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace SpaceEngineers.Core.Basics.Primitives -{ - using System; - using System.Diagnostics.CodeAnalysis; - - /// - /// IQueue abstraction - /// - /// TElement type-argument - public interface IQueue - { - /// - /// Gets the number of elements contained in the queue - /// - int Count { get; } - - /// - /// Gets a value that indicates whether the queue is empty - /// - bool IsEmpty { get; } - - /// - /// Adds an element to the queue - /// - /// Element - void Enqueue(TElement element); - - /// - /// Returns the first element and removes it from the queue - /// - /// The first element - /// Queue is empty - TElement Dequeue(); - - /// - /// Returns the first element and removes it from the queue if queue is not empty - /// - /// The first element of default value - /// The first element - /// Queue is empty - bool TryDequeue([NotNullWhen(true)] out TElement? element); - - /// - /// Returns the first element but doesn't modify the queue - /// - /// The first element - /// Queue is empty - TElement Peek(); - - /// - /// Returns the first element but doesn't modify the queue if queue is not empty - /// - /// The first element of default value - /// The first element - /// Queue is empty - bool TryPeek([NotNullWhen(true)] out TElement? element); - } -} \ No newline at end of file diff --git a/src/Common/Basics/Primitives/MessageQueue.cs b/src/Common/Basics/Primitives/MessageQueue.cs deleted file mode 100644 index ab4400b1..00000000 --- a/src/Common/Basics/Primitives/MessageQueue.cs +++ /dev/null @@ -1,118 +0,0 @@ -namespace SpaceEngineers.Core.Basics.Primitives -{ - using System; - using System.Collections.Concurrent; - using System.Diagnostics.CodeAnalysis; - using System.Threading; - using System.Threading.Tasks; - - /// - /// MessageQueue - /// - /// TElement type-argument - public class MessageQueue : IQueue, - IAsyncQueue - { - private readonly Exclusive _exclusive = new Exclusive(); - - private readonly AsyncAutoResetEvent _autoResetEvent; - private readonly ConcurrentQueue _queue; - - /// .cctor - public MessageQueue() - { - _autoResetEvent = new AsyncAutoResetEvent(false); - _queue = new ConcurrentQueue(); - } - - #region IQueue - - /// - public int Count => _queue.Count; - - /// - public bool IsEmpty => _queue.IsEmpty; - - /// - public void Enqueue(TElement element) - { - _queue.Enqueue(element); - _autoResetEvent.Set(); - } - - /// - public TElement Dequeue() - { - throw new NotSupportedException(nameof(Dequeue)); - } - - /// - public bool TryDequeue([NotNullWhen(true)] out TElement? element) - { - throw new NotSupportedException(nameof(TryDequeue)); - } - - /// - public TElement Peek() - { - throw new NotSupportedException(nameof(Peek)); - } - - /// - public bool TryPeek([NotNullWhen(true)] out TElement? element) - { - throw new NotSupportedException(nameof(TryPeek)); - } - - #endregion - - #region IAsyncQueue - - /// - public Task Enqueue(TElement element, CancellationToken token) - { - Enqueue(element); - return Task.CompletedTask; - } - - /// - public async Task Run(Func callback, CancellationToken token) - { - using (await _exclusive.Run(token).ConfigureAwait(false)) - { - while (!token.IsCancellationRequested) - { - var element = await Dequeue(token).ConfigureAwait(false); - - if (element != null) - { - await callback(element, token).ConfigureAwait(false); - } - } - } - } - - #endregion - - /// - /// Returns first element and removes it from the MessageQueue. - /// Waits while the MessageQueue is empty. - /// Returns default value when cancellation was requested. - /// - /// Cancellation token - /// First element in the MessageQueue - private async Task Dequeue(CancellationToken token) - { - await _autoResetEvent.WaitAsync(token).ConfigureAwait(false); - - if (token.IsCancellationRequested) - { - return default; - } - - return _queue.TryDequeue(out var element) - ? element - : throw new InvalidOperationException($"{nameof(MessageQueue)} was corrupted"); - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/Primitives/PriorityQueue.cs b/src/Common/Basics/Primitives/PriorityQueue.cs deleted file mode 100644 index 83f6d6bd..00000000 --- a/src/Common/Basics/Primitives/PriorityQueue.cs +++ /dev/null @@ -1,77 +0,0 @@ -namespace SpaceEngineers.Core.Basics.Primitives -{ - using System; - using System.Diagnostics.CodeAnalysis; - - /// - /// PriorityQueue - /// - /// TElement type-argument - /// TKey type-argument - public class PriorityQueue : IQueue - where TKey : IEquatable, IComparable, IComparable - { - private readonly IHeap> _heap; - private readonly Func _prioritySelector; - - /// .cctor - /// Heap implementation - /// Priority selector - public PriorityQueue(IHeap> heap, Func prioritySelector) - { - _heap = heap; - _prioritySelector = prioritySelector; - } - - /// - public int Count => _heap.Count; - - /// - public bool IsEmpty => _heap.IsEmpty; - - /// - public void Enqueue(TElement element) - { - var entry = new HeapEntry(_prioritySelector(element), element); - _heap.Insert(entry); - } - - /// - public TElement Dequeue() - { - return _heap.Extract().Element; - } - - /// - public bool TryDequeue([NotNullWhen(true)] out TElement? element) - { - if (_heap.TryExtract(out var entry)) - { - element = entry.Element!; - return true; - } - - element = default; - return false; - } - - /// - public TElement Peek() - { - return _heap.Peek().Element; - } - - /// - public bool TryPeek([NotNullWhen(true)] out TElement? element) - { - if (_heap.TryPeek(out var entry)) - { - element = entry.Element!; - return true; - } - - element = default; - return false; - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/Primitives/RootNodeChangedEventArgs.cs b/src/Common/Basics/Primitives/RootNodeChangedEventArgs.cs deleted file mode 100644 index b08ce9b3..00000000 --- a/src/Common/Basics/Primitives/RootNodeChangedEventArgs.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace SpaceEngineers.Core.Basics.Primitives -{ - /// - /// RootNodeChangedEventArgs - /// - /// TElement type-argument - public class RootNodeChangedEventArgs - { - /// .cctor - /// Original value - /// Current value - public RootNodeChangedEventArgs(TElement? originalValue, TElement? currentValue) - { - OriginalValue = originalValue; - CurrentValue = currentValue; - } - - /// - /// Original value - /// - public TElement? OriginalValue { get; } - - /// - /// Current value - /// - public TElement? CurrentValue { get; } - } -} \ No newline at end of file diff --git a/src/Common/Basics/Primitives/TaskCancellationCompletionSource.cs b/src/Common/Basics/Primitives/TaskCancellationCompletionSource.cs deleted file mode 100644 index 1d444b0b..00000000 --- a/src/Common/Basics/Primitives/TaskCancellationCompletionSource.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace SpaceEngineers.Core.Basics.Primitives -{ - using System; - using System.Threading; - using System.Threading.Tasks; - - /// - /// TaskCancellationCompletionSource - /// - /// TResult type-argument - public class TaskCancellationCompletionSource : TaskCompletionSource, IDisposable - { - private readonly IDisposable? _registration; - - /// .cctor - /// Cancellation token - public TaskCancellationCompletionSource(CancellationToken token) - : base(TaskCreationOptions.RunContinuationsAsynchronously) - { - if (!token.CanBeCanceled) - { - throw new InvalidOperationException("Cancellation token can't be in cancelled state"); - } - - if (token.IsCancellationRequested) - { - _ = TrySetCanceled(); - return; - } - - _registration = token.Register(() => TrySetCanceled(token), useSynchronizationContext: false); - } - - /// - public void Dispose() - { - _registration?.Dispose(); - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/Queue/DeferredQueue.cs b/src/Common/Basics/Queue/DeferredQueue.cs new file mode 100644 index 00000000..46596ffc --- /dev/null +++ b/src/Common/Basics/Queue/DeferredQueue.cs @@ -0,0 +1,244 @@ +namespace SpaceEngineers.Core.Basics.Queue; + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using Disposables; +using Heap; +using SynchronizationPrimitives; + +public class DeferredQueue : IQueue, + IAsyncQueue + where TElement : IEquatable, IComparable, IComparable +{ + private readonly Exclusive _exclusive = new Exclusive(); + + private readonly TimeSpan _high = TimeSpan.FromMilliseconds(42); + private readonly TimeSpan _low = TimeSpan.FromMilliseconds(1); + + private readonly IHeap> _heap; + private readonly PriorityQueue _priorityQueue; + private readonly Func _prioritySelector; + + private Task? _delay; + private CancellationTokenSource? _cts; + + public DeferredQueue( + IHeap> heap, + Func prioritySelector) + { + _heap = heap; + _prioritySelector = prioritySelector; + _priorityQueue = new PriorityQueue(heap, prioritySelector); + } + + #region IQueue + + public int Count + { + get + { + lock (_priorityQueue) + { + return _priorityQueue.Count; + } + } + } + + public bool IsEmpty + { + get + { + lock (_priorityQueue) + { + return _priorityQueue.IsEmpty; + } + } + } + + public void Enqueue(TElement element) + { + lock (_priorityQueue) + { + _priorityQueue.Enqueue(element); + } + } + + public TElement Dequeue() + { + throw new NotSupportedException(nameof(Dequeue)); + } + + public bool TryDequeue([NotNullWhen(true)] out TElement? element) + { + throw new NotSupportedException(nameof(TryDequeue)); + } + + public TElement Peek() + { + throw new NotSupportedException(nameof(Peek)); + } + + public bool TryPeek([NotNullWhen(true)] out TElement? element) + { + throw new NotSupportedException(nameof(TryPeek)); + } + + #endregion + + #region IAsyncQueue + + public Task Enqueue(TElement element, CancellationToken token) + { + Enqueue(element); + return Task.CompletedTask; + } + + public async Task Run(Func callback, CancellationToken token) + { + using (await _exclusive.Run(token).ConfigureAwait(false)) + using (Disposable.Create( + new EventHandler>>(CancelScheduleOnRootNodeChanged), + subscription => _heap.RootNodeChanged += subscription, + subscription => _heap.RootNodeChanged -= subscription)) + { + while (!token.IsCancellationRequested) + { + (_delay, _cts) = Schedule(token); + + try + { + await _delay.ConfigureAwait(false); + } + catch (OperationCanceledException) + { + continue; + } + + var args = DequeueSync(); + var planned = _prioritySelector(args); + + try + { + await WaitForBreadcrumbs(planned, token).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + } + + await callback(args, token).ConfigureAwait(false); + } + } + + await CancelSchedule().ConfigureAwait(false); + token.ThrowIfCancellationRequested(); + + async void CancelScheduleOnRootNodeChanged(object sender, RootNodeChangedEventArgs> args) + { + await CancelSchedule().ConfigureAwait(false); + } + } + + #endregion + + private TElement DequeueSync() + { + lock (_priorityQueue) + { + return _priorityQueue.Dequeue(); + } + } + + private (Task, CancellationTokenSource?) Schedule(CancellationToken token) + { + lock (_priorityQueue) + { + _ = _priorityQueue.TryPeek(out var element); + + return element == null + ? InfiniteDelay(token) + : ElementDelay(element, token); + } + } + + private static (Task, CancellationTokenSource?) InfiniteDelay(CancellationToken token) + { + var cts = CancellationTokenSource.CreateLinkedTokenSource(token); + var delay = Task.Delay(Timeout.InfiniteTimeSpan, cts.Token); + + return (delay, cts); + } + + private (Task, CancellationTokenSource?) ElementDelay(TElement element, CancellationToken token) + { + Task delay; + CancellationTokenSource? cts; + + var now = DateTime.UtcNow; + var planned = _prioritySelector(element).ToUniversalTime(); + + if (planned <= now) + { + delay = Task.CompletedTask; + cts = null; + } + else + { + cts = CancellationTokenSource.CreateLinkedTokenSource(token); + delay = Task.Delay(planned - now, cts.Token); + } + + return (delay, cts); + } + + private async Task CancelSchedule() + { + var cts = Interlocked.Exchange(ref _cts, null); + + if (cts == null + || typeof(CancellationTokenSource).GetFieldValue(cts, "_disposed")) + { + return; + } + + using (cts) + { + cts.Cancel(); + + try + { + await _delay.ConfigureAwait(false); + } + catch (OperationCanceledException) + { + } + } + } + + private async Task WaitForBreadcrumbs(DateTime planned, CancellationToken token) + { + var now = DateTime.UtcNow; + + if (planned <= now) + { + return; + } + + var delta = planned - now; + + if (delta < _low) + { + return; + } + + if (_low <= delta && delta <= _high) + { + await Task.Delay(delta, token).ConfigureAwait(false); + await WaitForBreadcrumbs(planned, token).ConfigureAwait(false); + return; + } + + throw new InvalidOperationException($"Operation was started earlier than planned in {delta.TotalMilliseconds} ms: planned: {planned:O}; now: {now:O};"); + } +} \ No newline at end of file diff --git a/src/Common/Basics/Queue/IAsyncQueue.cs b/src/Common/Basics/Queue/IAsyncQueue.cs new file mode 100644 index 00000000..11807ccc --- /dev/null +++ b/src/Common/Basics/Queue/IAsyncQueue.cs @@ -0,0 +1,12 @@ +namespace SpaceEngineers.Core.Basics.Queue; + +using System; +using System.Threading; +using System.Threading.Tasks; + +public interface IAsyncQueue +{ + Task Enqueue(TElement element, CancellationToken token); + + Task Run(Func callback, CancellationToken token); +} \ No newline at end of file diff --git a/src/Common/Basics/Queue/IQueue.cs b/src/Common/Basics/Queue/IQueue.cs new file mode 100644 index 00000000..58551e82 --- /dev/null +++ b/src/Common/Basics/Queue/IQueue.cs @@ -0,0 +1,20 @@ +namespace SpaceEngineers.Core.Basics.Queue; + +using System.Diagnostics.CodeAnalysis; + +public interface IQueue +{ + int Count { get; } + + bool IsEmpty { get; } + + void Enqueue(TElement element); + + TElement Dequeue(); + + bool TryDequeue([NotNullWhen(true)] out TElement? element); + + TElement Peek(); + + bool TryPeek([NotNullWhen(true)] out TElement? element); +} \ No newline at end of file diff --git a/src/Common/Basics/Queue/MessageQueue.cs b/src/Common/Basics/Queue/MessageQueue.cs new file mode 100644 index 00000000..0cf5d8fd --- /dev/null +++ b/src/Common/Basics/Queue/MessageQueue.cs @@ -0,0 +1,102 @@ +namespace SpaceEngineers.Core.Basics.Queue; + +using System; +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using SynchronizationPrimitives; + +public class MessageQueue : IQueue, + IAsyncQueue +{ + private readonly Exclusive _exclusive = new Exclusive(); + + private readonly AsyncAutoResetEvent _autoResetEvent; + private readonly ConcurrentQueue _queue; + + public MessageQueue() + { + _autoResetEvent = new AsyncAutoResetEvent(false); + _queue = new ConcurrentQueue(); + } + + #region IQueue + + public int Count => _queue.Count; + + public bool IsEmpty => _queue.IsEmpty; + + public void Enqueue(TElement element) + { + _queue.Enqueue(element); + _autoResetEvent.Set(); + } + + public TElement Dequeue() + { + throw new NotSupportedException(nameof(Dequeue)); + } + + public bool TryDequeue([NotNullWhen(true)] out TElement? element) + { + throw new NotSupportedException(nameof(TryDequeue)); + } + + public TElement Peek() + { + throw new NotSupportedException(nameof(Peek)); + } + + public bool TryPeek([NotNullWhen(true)] out TElement? element) + { + throw new NotSupportedException(nameof(TryPeek)); + } + + #endregion + + #region IAsyncQueue + + public Task Enqueue(TElement element, CancellationToken token) + { + Enqueue(element); + return Task.CompletedTask; + } + + public async Task Run(Func callback, CancellationToken token) + { + using (await _exclusive.Run(token).ConfigureAwait(false)) + { + while (!token.IsCancellationRequested) + { + var element = await Dequeue(token).ConfigureAwait(false); + + if (element != null) + { + await callback(element, token).ConfigureAwait(false); + } + } + } + } + + #endregion + + private async Task Dequeue(CancellationToken token) + { + /* + * Waits while the MessageQueue is empty. + * Returns default value when cancellation was requested. + */ + + await _autoResetEvent.WaitAsync(token).ConfigureAwait(false); + + if (token.IsCancellationRequested) + { + return default; + } + + return _queue.TryDequeue(out var element) + ? element + : throw new InvalidOperationException($"{nameof(MessageQueue)} was corrupted"); + } +} \ No newline at end of file diff --git a/src/Common/Basics/Queue/PriorityQueue.cs b/src/Common/Basics/Queue/PriorityQueue.cs new file mode 100644 index 00000000..7fe06ea7 --- /dev/null +++ b/src/Common/Basics/Queue/PriorityQueue.cs @@ -0,0 +1,62 @@ +namespace SpaceEngineers.Core.Basics.Queue; + +using System; +using System.Diagnostics.CodeAnalysis; +using Heap; + +public class PriorityQueue : IQueue + where TKey : IEquatable, IComparable, IComparable +{ + private readonly IHeap> _heap; + private readonly Func _prioritySelector; + + public PriorityQueue(IHeap> heap, Func prioritySelector) + { + _heap = heap; + _prioritySelector = prioritySelector; + } + + public int Count => _heap.Count; + + public bool IsEmpty => _heap.IsEmpty; + + public void Enqueue(TElement element) + { + var entry = new HeapEntry(_prioritySelector(element), element); + _heap.Insert(entry); + } + + public TElement Dequeue() + { + return _heap.Extract().Element; + } + + public bool TryDequeue([NotNullWhen(true)] out TElement? element) + { + if (_heap.TryExtract(out var entry)) + { + element = entry.Element!; + return true; + } + + element = default; + return false; + } + + public TElement Peek() + { + return _heap.Peek().Element; + } + + public bool TryPeek([NotNullWhen(true)] out TElement? element) + { + if (_heap.TryPeek(out var entry)) + { + element = entry.Element!; + return true; + } + + element = default; + return false; + } +} \ No newline at end of file diff --git a/src/Common/Basics/QueueExtensions.cs b/src/Common/Basics/QueueExtensions.cs new file mode 100644 index 00000000..ac77128c --- /dev/null +++ b/src/Common/Basics/QueueExtensions.cs @@ -0,0 +1,13 @@ +namespace SpaceEngineers.Core.Basics; + +using System.Collections.Generic; + +public static class QueueExtensions +{ + public static Queue EnqueueMany(this Queue queue, IReadOnlyCollection source) + { + source.Each(queue.Enqueue); + + return queue; + } +} \ No newline at end of file diff --git a/src/Common/Basics/Reflection/MethodExecutionInfo.cs b/src/Common/Basics/Reflection/MethodExecutionInfo.cs new file mode 100644 index 00000000..6235f282 --- /dev/null +++ b/src/Common/Basics/Reflection/MethodExecutionInfo.cs @@ -0,0 +1,147 @@ +namespace SpaceEngineers.Core.Basics.Reflection; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Exceptions; + +public class MethodExecutionInfo +{ + private readonly Type _declaringType; + + private readonly string _methodName; + + private readonly ICollection _args = new List(); + + private readonly ICollection _argumentTypes = new List(); + + private readonly ICollection _typeArguments = new List(); + + private object? _target; + + public MethodExecutionInfo(Type declaringType, string methodName) + { + _declaringType = declaringType; + _methodName = methodName; + } + + public MethodExecutionInfo ForInstance(object target) + { + _target = target; + + return this; + } + + public MethodExecutionInfo WithArgument(TArgument argument) + { + _args.Add(argument); + _argumentTypes.Add(argument?.GetType() ?? typeof(object)); + + return this; + } + + public MethodExecutionInfo WithArgument(Type argumentType, object? argument) + { + _args.Add(argument); + _argumentTypes.Add(argumentType); + + return this; + } + + public MethodExecutionInfo WithArgument(object? argument) + { + _args.Add(argument); + _argumentTypes.Add(argument?.GetType() ?? typeof(TArgument)); + + return this; + } + + public MethodExecutionInfo WithArguments(params object[] arguments) + { + foreach (var argument in arguments) + { + _args.Add(argument); + _argumentTypes.Add(argument?.GetType() ?? typeof(object)); + } + + return this; + } + + public MethodExecutionInfo WithTypeArgument() + { + _typeArguments.Add(typeof(TTypeArgument)); + + return this; + } + + public MethodExecutionInfo WithTypeArgument(Type typeArgument) + { + _typeArguments.Add(typeArgument); + + return this; + } + + public MethodExecutionInfo WithTypeArguments(params Type[] typeArguments) + { + typeArguments.Each(_typeArguments.Add); + + return this; + } + + public TResult Invoke() + { + return Invoke().EnsureType(); + } + + public object? Invoke() + { + // 1 - prepare and check + var isInstanceMethod = _target != null; + + if (isInstanceMethod + && _target.GetType() != _declaringType) + { + throw new TypeMismatchException(_declaringType, _target.GetType()); + } + + // 2 - find + var methodFinder = new MethodFinder( + isInstanceMethod ? _target.GetType() : _declaringType, + _methodName, + GetBindings(isInstanceMethod)) + { + TypeArguments = _typeArguments.ToArray(), + ArgumentTypes = _argumentTypes.ToArray() + }; + + var methodInfo = methodFinder.FindMethod() + ?? throw new NotFoundException($"Method wasn't found: {methodFinder}"); + + // 3 - call + var isGenericMethod = _typeArguments.Any(); + + var constructedMethod = isGenericMethod + ? methodInfo.MakeGenericMethod(_typeArguments.ToArray()) + : methodInfo; + + return ExecutionExtensions + .Try(InvokeMethod, (constructedMethod, _target, _args.ToArray())) + .Catch() + .Invoke(ex => throw ex.Rethrow()); + + static object? InvokeMethod((MethodInfo, object?, object?[]) state) + { + var (methodInfo, target, args) = state; + return methodInfo.Invoke(target, args); + } + } + + private static BindingFlags GetBindings(bool isInstanceMethod) + { + return (isInstanceMethod ? BindingFlags.Instance : BindingFlags.Static) + | BindingFlags.Public + | BindingFlags.NonPublic + | BindingFlags.InvokeMethod; + } +} \ No newline at end of file diff --git a/src/Common/Basics/Reflection/MethodFinder.cs b/src/Common/Basics/Reflection/MethodFinder.cs new file mode 100644 index 00000000..1d5f8761 --- /dev/null +++ b/src/Common/Basics/Reflection/MethodFinder.cs @@ -0,0 +1,116 @@ +namespace SpaceEngineers.Core.Basics.Reflection; + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +public class MethodFinder +{ + private static readonly ConcurrentDictionary Cache + = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + public MethodFinder(Type declaringType, + string methodName, + BindingFlags bindingFlags) + { + DeclaringType = declaringType; + MethodName = methodName; + BindingFlags = bindingFlags; + } + + public Type DeclaringType { get; } + + public string MethodName { get; } + + public BindingFlags BindingFlags { get; } + + public IReadOnlyCollection TypeArguments { get; set; } = Array.Empty(); + + public IReadOnlyCollection ArgumentTypes { get; set; } = Array.Empty(); + + public override string ToString() + { + var properties = new Dictionary + { + [nameof(DeclaringType)] = DeclaringType.FullName, + [nameof(MethodName)] = MethodName, + [nameof(BindingFlags)] = BindingFlags.ToString("G"), + [nameof(TypeArguments)] = TypeArguments.Select(type => type.FullName).ToString(string.Empty), + [nameof(ArgumentTypes)] = ArgumentTypes.Select(type => type.FullName).ToString(string.Empty) + }; + + return properties.ToString(string.Empty); + } + + public MethodInfo? FindMethod() + { + var key = string.Intern(ToString()); + + return Cache.GetOrAdd(key, static (_, methodFinder) => Find(methodFinder), this); + } + + private static MethodInfo? Find(MethodFinder methodFinder) + { + var isGenericMethod = methodFinder.TypeArguments.Any(); + + return isGenericMethod + ? FindGenericMethod(methodFinder) + : FindNonGenericMethod(methodFinder); + } + + private static MethodInfo? FindNonGenericMethod(MethodFinder methodFinder) + { + return methodFinder.DeclaringType.GetMethod( + methodFinder.MethodName, + methodFinder.BindingFlags, + null, + methodFinder.ArgumentTypes.ToArray(), + null); + } + + private static MethodInfo? FindGenericMethod(MethodFinder methodFinder) + { + var methods = methodFinder.DeclaringType + .GetMethods(methodFinder.BindingFlags) + .Where(methodInfo => methodInfo.Name == methodFinder.MethodName + && methodInfo.IsGenericMethod + && ValidateParameters(methodFinder.TypeArguments, methodInfo.GetGenericArguments()) + && ValidateParameters(methodFinder.ArgumentTypes, GetArgumentTypes(methodInfo))) + .ToArray(); + + IReadOnlyCollection GetArgumentTypes(MethodInfo methodInfo) + { + return methodInfo + .MakeGenericMethod(methodFinder.TypeArguments.ToArray()) + .GetParameters() + .Select(z => z.ParameterType) + .ToArray(); + } + + return methods.InformativeSingle(Amb, methodFinder); + + static string Amb(MethodFinder methodFinder, IEnumerable source) + { + string Generics(MethodInfo m) => m.GetGenericArguments().Select(g => g.Name).ToString(", "); + string Show(MethodInfo m) => methodFinder.DeclaringType.FullName + "." + m.Name + "[" + Generics(m) + "]"; + return source.Select(Show).ToString(", "); + } + } + + private static bool ValidateParameters(IReadOnlyCollection actual, IReadOnlyCollection expected) + { + if (actual.Count != expected.Count) + { + return false; + } + + return expected.Select((exp, i) => new { Type = exp, i }) + .Join(actual.Select((act, i) => new { Type = act, i }), + exp => exp.i, + act => act.i, + (exp, act) => new { Exp = exp.Type, Act = act.Type }) + .All(pair => pair.Act.FitsForTypeArgument(pair.Exp)); + } +} \ No newline at end of file diff --git a/src/Common/Basics/SolutionExtensions.cs b/src/Common/Basics/SolutionExtensions.cs index 0f427887..8a44fd4f 100644 --- a/src/Common/Basics/SolutionExtensions.cs +++ b/src/Common/Basics/SolutionExtensions.cs @@ -1,120 +1,86 @@ -namespace SpaceEngineers.Core.Basics +namespace SpaceEngineers.Core.Basics; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; + +public static class SolutionExtensions { - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Xml.Linq; - - /// - /// Solution extensions - /// - public static class SolutionExtensions + public static FileInfo SolutionFile() { - /// - /// Find nearest solution file (*.sln) - /// Has valid behavior only in test environment where we have full solution structure on disk - /// - /// Solution directory path - /// If solution directory not found or depth in 42 nested directories exceeded - public static FileInfo SolutionFile() - { - return FindFile("*.sln"); - } + return FindFile("*.sln"); + } - /// - /// Find nearest project file (*.csproj) - /// Has valid behavior only in test environment where we have full solution structure on disk - /// - /// Project directory path - /// If project directory wasn't found or depth in 42 nested directories exceeded - public static FileInfo ProjectFile() - { - return FindFile("*.csproj"); - } + public static FileInfo ProjectFile() + { + return FindFile("*.csproj"); + } + + public static FileInfo[] ProjectFiles(this DirectoryInfo solutionDirectory) + { + return solutionDirectory.GetFiles("*.csproj", SearchOption.AllDirectories); + } + + public static FileInfo[] SourceFiles(this DirectoryInfo projectDirectory) + { + return projectDirectory.GetFiles("*.cs", SearchOption.AllDirectories); + } + + public static string AssemblyName(this FileInfo csproj) + { + var projectDocument = XDocument.Load(csproj.FullName, LoadOptions.None); - /// - /// Returns all projects files (*.csproj) in specified solution directory - /// Has valid behavior only in test environment where we have full solution structure on disk - /// - /// Solution directory (*.sln) - /// Projects files paths in specified solution directory - public static FileInfo[] ProjectFiles(this DirectoryInfo solutionDirectory) + if (projectDocument.Root == null + || projectDocument.Root.Name != "Project") { - return solutionDirectory.GetFiles("*.csproj", SearchOption.AllDirectories); + throw new InvalidOperationException("Project file must contains project node as root"); } - /// - /// Returns all source files (*.cs) in specified project directory - /// Has valid behavior only in test environment where we have full solution structure on disk - /// - /// Project directory (*.csproj) - /// Projects files paths in specified solution directory - public static FileInfo[] SourceFiles(this DirectoryInfo projectDirectory) + return ExtractAssemblyNames(projectDocument.Root).InformativeSingle(Amb).Value; + + IEnumerable ExtractAssemblyNames(XElement element) { - return projectDirectory.GetFiles("*.cs", SearchOption.AllDirectories); + return element + .Flatten(e => e.Elements()) + .Where(e => e.Name == "AssemblyName"); } - /// - /// Returns assembly name of specified project file (.*csproj) - /// - /// Project file (.*csproj) - /// Assembly name of specified project - /// Throws if project file has invalid structure - public static string AssemblyName(this FileInfo csproj) + static string Amb(IEnumerable source) { - var projectDocument = XDocument.Load(csproj.FullName, LoadOptions.None); - - if (projectDocument.Root == null - || projectDocument.Root.Name != "Project") - { - throw new InvalidOperationException("Project file must contains project node as root"); - } - - return ExtractAssemblyNames(projectDocument.Root).InformativeSingle(Amb).Value; - - IEnumerable ExtractAssemblyNames(XElement element) - { - return element - .Flatten(e => e.Elements()) - .Where(e => e.Name == "AssemblyName"); - } - - static string Amb(IEnumerable source) - { - return source - .Select(e => e.Value) - .ToString(Environment.NewLine); - } + return source + .Select(e => e.Value) + .ToString(Environment.NewLine); } + } - private static FileInfo FindFile(string pattern) - { - var baseDirectory = AppDomain.CurrentDomain.BaseDirectory.AsDirectoryInfo(); + private static FileInfo FindFile(string pattern) + { + var baseDirectory = AppDomain.CurrentDomain.BaseDirectory.AsDirectoryInfo(); - var directory = baseDirectory; + var directory = baseDirectory; - for (var i = 0; - !FileExist(directory, out _) && i < 42; - ++i) - { - directory = directory.Parent; - } + for (var i = 0; + !FileExist(directory, out _) && i < 42; + ++i) + { + directory = directory.Parent; + } - if (!FileExist(directory, out var file)) - { - throw new DirectoryNotFoundException($"Directory with {pattern} wasn't found"); - } + if (!FileExist(directory, out var file)) + { + throw new DirectoryNotFoundException($"Directory with {pattern} wasn't found"); + } - return file ?? throw new InvalidOperationException("File must exists"); + return file ?? throw new InvalidOperationException("File must exists"); - bool FileExist(DirectoryInfo directoryInfo, out FileInfo? fileInfo) - { - fileInfo = directoryInfo.GetFiles(pattern, SearchOption.TopDirectoryOnly) - .FirstOrDefault(); + bool FileExist(DirectoryInfo directoryInfo, out FileInfo? fileInfo) + { + fileInfo = directoryInfo.GetFiles(pattern, SearchOption.TopDirectoryOnly) + .FirstOrDefault(); - return fileInfo != null; - } + return fileInfo != null; } } } \ No newline at end of file diff --git a/src/Common/Basics/StatelessActionExecutionInfo.cs b/src/Common/Basics/StatelessActionExecutionInfo.cs deleted file mode 100644 index 26d863fe..00000000 --- a/src/Common/Basics/StatelessActionExecutionInfo.cs +++ /dev/null @@ -1,87 +0,0 @@ -namespace SpaceEngineers.Core.Basics -{ - using System; - using System.Collections.Generic; - - /// - /// StatelessActionExecutionInfo - /// - public class StatelessActionExecutionInfo - { - private static readonly Action EmptyExceptionHandler = _ => { }; - - private readonly Action _clientAction; - private readonly IDictionary> _exceptionHandlers; - - private Action? _finallyAction; - - /// .ctor - /// Client action - public StatelessActionExecutionInfo(Action clientAction) - { - _clientAction = clientAction; - _exceptionHandlers = new Dictionary>(); - } - - /// - /// Catch block - /// Catch exception of TException type - /// - /// Exception handler - /// Real exception type-argument - /// StatelessActionExecutionInfo - public StatelessActionExecutionInfo Catch(Action? exceptionHandler = null) - { - _exceptionHandlers[typeof(TException)] = exceptionHandler ?? EmptyExceptionHandler; - - return this; - } - - /// - /// Finally block - /// - /// Finally action - /// StatelessActionExecutionInfo - public StatelessActionExecutionInfo Finally(Action finallyAction) - { - _finallyAction = finallyAction; - - return this; - } - - /// - /// Invoke client's action - /// - public void Invoke() - { - try - { - _clientAction.Invoke(); - } - catch (Exception ex) when (ExecutionExtensions.CanBeCaught(ex.RealException())) - { - var realException = ex.RealException(); - var handled = false; - - foreach (var pair in _exceptionHandlers) - { - if (pair.Key.IsInstanceOfType(realException)) - { - pair.Value.Invoke(realException); - handled = true; - break; - } - } - - if (!handled) - { - throw realException.Rethrow(); - } - } - finally - { - _finallyAction?.Invoke(); - } - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/StatelessFunctionExecutionInfo.cs b/src/Common/Basics/StatelessFunctionExecutionInfo.cs deleted file mode 100644 index 40c78433..00000000 --- a/src/Common/Basics/StatelessFunctionExecutionInfo.cs +++ /dev/null @@ -1,92 +0,0 @@ -namespace SpaceEngineers.Core.Basics -{ - using System; - using System.Collections.Generic; - - /// - /// StatelessFunctionExecutionInfo - /// - /// Function TResult Type-argument - public class StatelessFunctionExecutionInfo - { - private static readonly Action EmptyExceptionHandler = _ => { }; - - private readonly Func _clientFunction; - private readonly IDictionary> _exceptionHandlers; - - private Action? _finallyAction; - - /// .ctor - /// Client function - public StatelessFunctionExecutionInfo(Func clientFunction) - { - _clientFunction = clientFunction; - _exceptionHandlers = new Dictionary>(); - } - - /// - /// Catch block - /// Catch exception of TException type - /// - /// Exception handler - /// Real exception type-argument - /// StatelessFunctionExecutionInfo - public StatelessFunctionExecutionInfo Catch(Action? exceptionHandler = null) - { - _exceptionHandlers[typeof(TException)] = exceptionHandler ?? EmptyExceptionHandler; - - return this; - } - - /// - /// Finally block - /// - /// Finally action - /// StatelessFunctionExecutionInfo - public StatelessFunctionExecutionInfo Finally(Action finallyAction) - { - _finallyAction = finallyAction; - - return this; - } - - /// - /// Invoke client's function - /// - /// Creates result from handled exception - /// TResult - public TResult Invoke(Func exceptionResultFactory) - { - try - { - return _clientFunction.Invoke(); - } - catch (Exception ex) when (ExecutionExtensions.CanBeCaught(ex.RealException())) - { - var realException = ex.RealException(); - var handled = false; - - foreach (var pair in _exceptionHandlers) - { - if (pair.Key.IsInstanceOfType(realException)) - { - pair.Value.Invoke(realException); - handled = true; - break; - } - } - - if (!handled) - { - throw realException.Rethrow(); - } - - return exceptionResultFactory.Invoke(realException); - } - finally - { - _finallyAction?.Invoke(); - } - } - } -} \ No newline at end of file diff --git a/src/Common/Basics/StreamExtensions.cs b/src/Common/Basics/StreamExtensions.cs index 096f974e..5cb06d79 100644 --- a/src/Common/Basics/StreamExtensions.cs +++ b/src/Common/Basics/StreamExtensions.cs @@ -1,153 +1,98 @@ -namespace SpaceEngineers.Core.Basics +namespace SpaceEngineers.Core.Basics; + +using System; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +public static class StreamExtensions { - using System; - using System.IO; - using System.Text; - using System.Threading; - using System.Threading.Tasks; - - /// - /// Extensions for operations with System.IO.Stream class - /// - public static class StreamExtensions + public static MemoryStream AsMemoryStream(this ReadOnlySpan bytes) { - /// - /// Converts bytes to memory steam - /// - /// Bytes - /// MemoryStream - public static MemoryStream AsMemoryStream(this ReadOnlySpan bytes) - { - var memoryStream = new MemoryStream(bytes.Length); + var memoryStream = new MemoryStream(bytes.Length); - memoryStream.Write(bytes); - memoryStream.Position = 0; + memoryStream.Write(bytes); + memoryStream.Position = 0; - return memoryStream; - } + return memoryStream; + } - /// - /// Converts MemoryStream to bytes - /// - /// MemoryStream - /// Bytes - public static ReadOnlyMemory AsBytes(this MemoryStream memoryStream) - { - return memoryStream.TryGetBuffer(out var arraySegment) - ? arraySegment - : throw new InvalidOperationException("Unable to extract buffer from the memoryStream"); - } + public static ReadOnlyMemory AsBytes(this MemoryStream memoryStream) + { + return memoryStream.TryGetBuffer(out var arraySegment) + ? arraySegment + : throw new InvalidOperationException("Unable to extract buffer from the memoryStream"); + } - /// - /// Converts stream to bytes - /// - /// Stream - /// Bytes - public static ReadOnlyMemory AsBytes(this Stream stream) + public static ReadOnlyMemory AsBytes(this Stream stream) + { + using (var memoryStream = new MemoryStream()) { - using (var memoryStream = new MemoryStream()) - { - stream.CopyTo(memoryStream); + stream.CopyTo(memoryStream); - return memoryStream.AsBytes(); - } + return memoryStream.AsBytes(); } + } - /// - /// Converts stream to bytes - /// - /// Stream - /// Cancellation token - /// Bytes - public static async Task> AsBytes(this Stream stream, CancellationToken token) + public static async Task> AsBytes(this Stream stream, CancellationToken token) + { + using (var memoryStream = new MemoryStream()) { - using (var memoryStream = new MemoryStream()) - { - await stream - .CopyToAsync(memoryStream, token) - .ConfigureAwait(false); - - return memoryStream.AsBytes(); - } + await stream + .CopyToAsync(memoryStream, token) + .ConfigureAwait(false); + + return memoryStream.AsBytes(); } + } - /// - /// Converts stream to encoded string - /// - /// Stream - /// Encoding - /// String - public static string AsString( - this Stream stream, - Encoding encoding) - { - var bytes = stream.AsBytes(); + public static string AsString(this Stream stream, Encoding encoding) + { + var bytes = stream.AsBytes(); - return encoding.GetString(bytes.Span); - } + return encoding.GetString(bytes.Span); + } - /// - /// Converts stream to encoded string - /// - /// Stream - /// Encoding - /// Cancellation token - /// String - public static async Task AsString( - this Stream stream, - Encoding encoding, - CancellationToken token) - { - var bytes = await stream - .AsBytes(token) - .ConfigureAwait(false); + public static async Task AsString(this Stream stream, Encoding encoding, CancellationToken token) + { + var bytes = await stream + .AsBytes(token) + .ConfigureAwait(false); - return encoding.GetString(bytes.Span); - } + return encoding.GetString(bytes.Span); + } - /// - /// Flushes stream and writes bytes - /// - /// Stream - /// Bytes - public static void Overwrite( - this Stream stream, - ReadOnlySpan bytes) - { - stream.Flush(); + public static void Overwrite( + this Stream stream, + ReadOnlySpan bytes) + { + stream.Flush(); - stream.Position = 0; - stream.SetLength(bytes.Length); + stream.Position = 0; + stream.SetLength(bytes.Length); - stream.Write(bytes); + stream.Write(bytes); - stream.Position = 0; - } + stream.Position = 0; + } - /// - /// Flushes stream and writes bytes - /// - /// Stream - /// Bytes - /// Cancellation token - /// Ongoing operation - public static async Task Overwrite( - this Stream stream, - ReadOnlyMemory bytes, - CancellationToken token) - { - await stream - .FlushAsync(token) - .ConfigureAwait(false); + public static async Task Overwrite( + this Stream stream, + ReadOnlyMemory bytes, + CancellationToken token) + { + await stream + .FlushAsync(token) + .ConfigureAwait(false); - stream.Position = 0; - stream.SetLength(bytes.Length); + stream.Position = 0; + stream.SetLength(bytes.Length); - await stream - .WriteAsync(bytes, token) - .ConfigureAwait(false); + await stream + .WriteAsync(bytes, token) + .ConfigureAwait(false); - stream.Position = 0; - } + stream.Position = 0; } } \ No newline at end of file diff --git a/src/Common/Basics/StringExtensions.cs b/src/Common/Basics/StringExtensions.cs index 1126df56..06398a86 100644 --- a/src/Common/Basics/StringExtensions.cs +++ b/src/Common/Basics/StringExtensions.cs @@ -1,77 +1,45 @@ -namespace SpaceEngineers.Core.Basics -{ - using System; - using System.Diagnostics.CodeAnalysis; - using System.Globalization; +namespace SpaceEngineers.Core.Basics; + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; - /// - /// System.String extensions - /// - public static class StringExtensions +public static class StringExtensions +{ + public static string StartFromCapitalLetter(this string source) { - /// - /// Starts source string from capital letter - /// - /// Source string - /// Source string started from capital letter - public static string StartFromCapitalLetter(this string source) + if (source.IsNullOrEmpty()) { - if (source.IsNullOrEmpty()) - { - return source; - } - - return string.Create( - source.Length, - source, - static (buffer, source) => - { - buffer[0] = char.ToUpper(source[0], CultureInfo.InvariantCulture); - source.AsSpan(1).ToLowerInvariant(buffer.Slice(1)); - }); + return source; } - /// - /// IsNullOrEmpty - /// - /// Source string - /// IsNullOrEmpty attribute - public static bool IsNullOrEmpty([NotNullWhen(false)] this string? source) - { - return string.IsNullOrEmpty(source); - } + return string.Create( + source.Length, + source, + static (buffer, source) => + { + buffer[0] = char.ToUpper(source[0], CultureInfo.InvariantCulture); + source.AsSpan(1).ToLowerInvariant(buffer.Slice(1)); + }); + } - /// - /// IsNullOrWhiteSpace - /// - /// Source string - /// IsNullOrWhiteSpace attribute - public static bool IsNullOrWhiteSpace([NotNullWhen(false)] this string? source) - { - return string.IsNullOrWhiteSpace(source); - } + public static bool IsNullOrEmpty([NotNullWhen(false)] this string? source) + { + return string.IsNullOrEmpty(source); + } - /// - /// Formats string with invariant culture - /// - /// Format string - /// Args - /// Format result - public static string Format(this string format, params object[] args) - { - return string.Format(CultureInfo.InvariantCulture, format, args); - } + public static bool IsNullOrWhiteSpace([NotNullWhen(false)] this string? source) + { + return string.IsNullOrWhiteSpace(source); + } - /// - /// Formats string - /// - /// Format string - /// Format provider - /// Args - /// Format result - public static string Format(this string format, IFormatProvider formatProvider, params object[] args) - { - return string.Format(formatProvider, format, args); - } + public static string Format(this string format, params object[] args) + { + return string.Format(CultureInfo.InvariantCulture, format, args); + } + + public static string Format(this string format, IFormatProvider formatProvider, params object[] args) + { + return string.Format(formatProvider, format, args); } } \ No newline at end of file diff --git a/src/Common/Basics/SynchronizationPrimitives/AsyncAutoResetEvent.cs b/src/Common/Basics/SynchronizationPrimitives/AsyncAutoResetEvent.cs new file mode 100644 index 00000000..66e51362 --- /dev/null +++ b/src/Common/Basics/SynchronizationPrimitives/AsyncAutoResetEvent.cs @@ -0,0 +1,64 @@ +namespace SpaceEngineers.Core.Basics.SynchronizationPrimitives; + +using System.Collections.Concurrent; +using System.Threading; +using System.Threading.Tasks; + +public class AsyncAutoResetEvent +{ + private static readonly TaskCompletionSource CompletedSource + = CreateCompletedCompletionSource(true); + + private readonly ConcurrentQueue> _waits + = new ConcurrentQueue>(); + + private int _completed; + + public AsyncAutoResetEvent(bool isSet) + { + if (isSet) + { + Interlocked.Increment(ref _completed); + } + } + + public Task WaitAsync(CancellationToken? cancellationToken = null) + { + if (_completed > 0) + { + Interlocked.Decrement(ref _completed); + return CompletedSource.Task; + } + + var tcs = CreateCompletionSource(); + _waits.Enqueue(tcs); + + return cancellationToken != null + ? tcs.Task.WaitAsync(cancellationToken.Value) + : tcs.Task; + } + + public void Set() + { + if (_waits.TryDequeue(out var toRelease)) + { + _ = toRelease.TrySetResult(true); + } + else + { + Interlocked.Increment(ref _completed); + } + } + + private static TaskCompletionSource CreateCompletionSource() + { + return new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + } + + private static TaskCompletionSource CreateCompletedCompletionSource(TResult result) + { + var tcs = CreateCompletionSource(); + _ = tcs.TrySetResult(result); + return tcs; + } +} \ No newline at end of file diff --git a/src/Common/Basics/SynchronizationPrimitives/AsyncCountdownEvent.cs b/src/Common/Basics/SynchronizationPrimitives/AsyncCountdownEvent.cs new file mode 100644 index 00000000..a1100f56 --- /dev/null +++ b/src/Common/Basics/SynchronizationPrimitives/AsyncCountdownEvent.cs @@ -0,0 +1,86 @@ +namespace SpaceEngineers.Core.Basics.SynchronizationPrimitives; + +using System.Threading; +using System.Threading.Tasks; + +/// +/// Free interpretation of CountdownEvent with several differences from original sync event +/// Differences: +/// - Signaled state is state when inner counter has reached zero or initialized with zero +/// - Increment resets event to non signaled state +/// - Decrement sets signaled state if inner counter has reached zero value +/// +public class AsyncCountdownEvent +{ + private readonly object _sync; + private TaskCompletionSource _tcs; + private int _count; + + public AsyncCountdownEvent(int count) + { + _sync = new object(); + _tcs = CreateCompletionSource(); + _count = count; + + if (_count <= 0) + { + _tcs.TrySetResult(true); + } + } + + public Task WaitAsync(CancellationToken? cancellationToken = null) + { + Task waitTask; + + lock (_sync) + { + waitTask = _tcs.Task; + } + + return waitTask.IsCompleted + || cancellationToken == null + ? waitTask + : waitTask.WaitAsync(cancellationToken.Value); + } + + public int Increment() + { + lock (_sync) + { + if (_tcs.Task.IsCompleted) + { + _tcs = CreateCompletionSource(); + } + + return ++_count; + } + } + + public int Decrement() + { + lock (_sync) + { + var result = --_count; + + if (result <= 0) + { + _ = _tcs.TrySetResult(true); + } + + return result; + } + } + + public int Read() + { + lock (_sync) + { + return _count; + } + } + + private static TaskCompletionSource CreateCompletionSource() + { + return new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + } +} \ No newline at end of file diff --git a/src/Common/Basics/SynchronizationPrimitives/AsyncManualResetEvent.cs b/src/Common/Basics/SynchronizationPrimitives/AsyncManualResetEvent.cs new file mode 100644 index 00000000..bbf0f0d6 --- /dev/null +++ b/src/Common/Basics/SynchronizationPrimitives/AsyncManualResetEvent.cs @@ -0,0 +1,62 @@ +namespace SpaceEngineers.Core.Basics.SynchronizationPrimitives; + +using System.Threading; +using System.Threading.Tasks; + +public class AsyncManualResetEvent +{ + private readonly object _sync; + + private TaskCompletionSource _tcs; + + public AsyncManualResetEvent(bool isSet) + { + _sync = new object(); + + _tcs = CreateCompletionSource(); + + if (isSet) + { + _tcs.TrySetResult(true); + } + } + + public Task WaitAsync(CancellationToken? cancellationToken = null) + { + Task waitTask; + + lock (_sync) + { + waitTask = _tcs.Task; + } + + return waitTask.IsCompleted + || cancellationToken == null + ? waitTask + : waitTask.WaitAsync(cancellationToken.Value); + } + + public void Set() + { + lock (_sync) + { + _tcs.TrySetResult(true); + } + } + + public void Reset() + { + lock (_sync) + { + if (_tcs.Task.IsCompleted) + { + _tcs = CreateCompletionSource(); + } + } + } + + private static TaskCompletionSource CreateCompletionSource() + { + return new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + } +} \ No newline at end of file diff --git a/src/Common/Basics/SynchronizationPrimitives/Exclusive.cs b/src/Common/Basics/SynchronizationPrimitives/Exclusive.cs new file mode 100644 index 00000000..c87da6c3 --- /dev/null +++ b/src/Common/Basics/SynchronizationPrimitives/Exclusive.cs @@ -0,0 +1,41 @@ +namespace SpaceEngineers.Core.Basics.SynchronizationPrimitives; + +using System; +using System.Threading; +using System.Threading.Tasks; +using Disposables; + +public class Exclusive +{ + private readonly AsyncAutoResetEvent _sync; + + private bool _isTaken; + + public Exclusive() + { + _sync = new AsyncAutoResetEvent(true); + _isTaken = false; + } + + public async Task Run(CancellationToken token) + { + if (_isTaken) + { + throw new InvalidOperationException("Exclusive operation has already been started"); + } + + await _sync + .WaitAsync(token) + .ConfigureAwait(false); + + _isTaken = true; + + return Disposable.Create(this, Finally); + + static void Finally(Exclusive exclusive) + { + exclusive._sync.Set(); + exclusive._isTaken = false; + } + } +} \ No newline at end of file diff --git a/src/Common/Basics/SynchronizationPrimitivesExtensions.cs b/src/Common/Basics/SynchronizationPrimitivesExtensions.cs index f467d342..74b019cb 100644 --- a/src/Common/Basics/SynchronizationPrimitivesExtensions.cs +++ b/src/Common/Basics/SynchronizationPrimitivesExtensions.cs @@ -1,132 +1,94 @@ -namespace SpaceEngineers.Core.Basics +namespace SpaceEngineers.Core.Basics; + +using System; +using System.Threading; +using Disposables; + +public static class SynchronizationPrimitivesExtensions { - using System; - using System.Threading; - using Primitives; - - /// - /// Synchronization primitives extensions - /// - public static class SynchronizationPrimitivesExtensions + public static IDisposable WithinReadLock(this ReaderWriterLockSlim sync) { - /// - /// Opens read-lock scope - /// - /// ReaderWriterLockSlim - /// Opened scope and its cancellation - public static IDisposable WithinReadLock(this ReaderWriterLockSlim sync) + if (sync.IsReadLockHeld) { - if (sync.IsReadLockHeld) - { - return Disposable.Empty; - } + return Disposable.Empty; + } - sync.EnterReadLock(); + sync.EnterReadLock(); - return Disposable.Create(sync.ExitReadLock); - } + return Disposable.Create(sync.ExitReadLock); + } - /// - /// Invoke action within read-lock - /// - /// ReaderWriterLockSlim - /// Action - public static void WithinReadLock(this ReaderWriterLockSlim sync, Action action) + public static void WithinReadLock(this ReaderWriterLockSlim sync, Action action) + { + if (sync.IsReadLockHeld) { - if (sync.IsReadLockHeld) - { - action.Invoke(); - return; - } - - sync.EnterReadLock(); - - try - { - action.Invoke(); - } - finally - { - sync.ExitReadLock(); - } + action.Invoke(); + return; } - /// - /// Invoke function within read-lock - /// - /// ReaderWriterLockSlim - /// Function - /// Return value type-argument - /// Function result - public static T WithinReadLock(this ReaderWriterLockSlim sync, Func func) + sync.EnterReadLock(); + + try { - if (sync.IsReadLockHeld) - { - return func.Invoke(); - } - - sync.EnterReadLock(); - - try - { - return func.Invoke(); - } - finally - { - sync.ExitReadLock(); - } + action.Invoke(); } + finally + { + sync.ExitReadLock(); + } + } - /// - /// Opens write-lock scope - /// - /// ReaderWriterLockSlim - /// Opened scope and its cancellation - public static IDisposable WithinWriteLock(this ReaderWriterLockSlim sync) + public static T WithinReadLock(this ReaderWriterLockSlim sync, Func func) + { + if (sync.IsReadLockHeld) { - sync.EnterWriteLock(); + return func.Invoke(); + } - return Disposable.Create(sync.ExitWriteLock); + sync.EnterReadLock(); + + try + { + return func.Invoke(); + } + finally + { + sync.ExitReadLock(); } + } + + public static IDisposable WithinWriteLock(this ReaderWriterLockSlim sync) + { + sync.EnterWriteLock(); + + return Disposable.Create(sync.ExitWriteLock); + } + + public static void WithinWriteLock(this ReaderWriterLockSlim sync, Action action) + { + sync.EnterWriteLock(); - /// - /// Invoke action within write-lock - /// - /// ReaderWriterLockSlim - /// Action - public static void WithinWriteLock(this ReaderWriterLockSlim sync, Action action) + try { - sync.EnterWriteLock(); - - try - { - action.Invoke(); - } - finally - { - sync.ExitWriteLock(); - } + action.Invoke(); } + finally + { + sync.ExitWriteLock(); + } + } - /// - /// Invoke function within write-lock - /// - /// ReaderWriterLockSlim - /// Function - /// Return value type-argument - /// Function result - public static T WithinWriteLock(this ReaderWriterLockSlim sync, Func func) + public static T WithinWriteLock(this ReaderWriterLockSlim sync, Func func) + { + sync.EnterWriteLock(); + + try + { + return func.Invoke(); + } + finally { - sync.EnterWriteLock(); - - try - { - return func.Invoke(); - } - finally - { - sync.ExitWriteLock(); - } + sync.ExitWriteLock(); } } } \ No newline at end of file diff --git a/src/Common/Basics/TaskCancellationCompletionSource.cs b/src/Common/Basics/TaskCancellationCompletionSource.cs new file mode 100644 index 00000000..dd095898 --- /dev/null +++ b/src/Common/Basics/TaskCancellationCompletionSource.cs @@ -0,0 +1,32 @@ +namespace SpaceEngineers.Core.Basics; + +using System; +using System.Threading; +using System.Threading.Tasks; + +public class TaskCancellationCompletionSource : TaskCompletionSource, IDisposable +{ + private readonly IDisposable? _registration; + + public TaskCancellationCompletionSource(CancellationToken token) + : base(TaskCreationOptions.RunContinuationsAsynchronously) + { + if (!token.CanBeCanceled) + { + throw new InvalidOperationException("Cancellation token can't be in cancelled state"); + } + + if (token.IsCancellationRequested) + { + _ = TrySetCanceled(); + return; + } + + _registration = token.Register(() => TrySetCanceled(token), useSynchronizationContext: false); + } + + public void Dispose() + { + _registration?.Dispose(); + } +} \ No newline at end of file diff --git a/src/Common/Basics/TaskExtensions.cs b/src/Common/Basics/TaskExtensions.cs index 4b7f99ac..93fe78eb 100644 --- a/src/Common/Basics/TaskExtensions.cs +++ b/src/Common/Basics/TaskExtensions.cs @@ -1,35 +1,24 @@ -namespace SpaceEngineers.Core.Basics -{ - using System.Threading; - using System.Threading.Tasks; - using Primitives; +namespace SpaceEngineers.Core.Basics; + +using System.Threading; +using System.Threading.Tasks; - /// - /// Task class extensions - /// - public static class TaskExtensions +public static class TaskExtensions +{ + public static async Task WaitAsync(this Task task, CancellationToken token) { - /// - /// Wait task asynchronously with cancellation - /// - /// Task - /// Optional cancellation token - /// Task wrapped in cancellation callback - public static async Task WaitAsync(this Task task, CancellationToken token) + if (!token.CanBeCanceled) { - if (!token.CanBeCanceled) - { - await task.ConfigureAwait(false); - return; - } + await task.ConfigureAwait(false); + return; + } - using (var tcs = new TaskCancellationCompletionSource(token)) - { - await Task - .WhenAny(task, tcs.Task) - .Unwrap() - .ConfigureAwait(false); - } + using (var tcs = new TaskCancellationCompletionSource(token)) + { + await Task + .WhenAny(task, tcs.Task) + .Unwrap() + .ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/Common/Basics/TypeExtensions.cs b/src/Common/Basics/TypeExtensions.cs index 4e338654..5c3e6711 100644 --- a/src/Common/Basics/TypeExtensions.cs +++ b/src/Common/Basics/TypeExtensions.cs @@ -1,842 +1,492 @@ -namespace SpaceEngineers.Core.Basics +namespace SpaceEngineers.Core.Basics; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; + +public static class TypeExtensions { - using System; - using System.Collections; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.IO; - using System.Linq; - using System.Reflection; - using System.Runtime.CompilerServices; - using Exceptions; - - /// - /// System.Type extensions methods - /// - public static class TypeExtensions - { - /// - /// All types from current app domain - /// - /// All types - public static IEnumerable AllTypes() - { - return AssembliesExtensions - .AllAssembliesFromCurrentDomain() - .Where(assembly => !assembly.IsDynamic) - .SelectMany(GetTypes); - - static IEnumerable GetTypes(Assembly assembly) - { - return ExecutionExtensions - .Try>(assembly.GetTypes) - .Catch() - .Catch() - .Invoke(_ => Enumerable.Empty()); - } - } - - /// - /// Finds type by type full name - /// - /// TypeNode - /// System.Type - public static Type FindType(this TypeNode typeNode) - { - return typeNode; - } - - /// - /// Finds type by type full name - /// - /// TypeNode - /// System.Type - /// True if type was found - [SuppressMessage("Analysis", "CA1031", Justification = "desired behavior")] - public static bool TryFindType(TypeNode typeNode, [NotNullWhen(true)] out Type? type) - { - try - { - type = FindType(typeNode); - return true; - } - catch (Exception) - { - type = null; - return false; - } - } + public static IEnumerable AllTypes() + { + return AssemblyExtensions + .AllAssembliesFromCurrentDomain() + .Where(assembly => !assembly.IsDynamic) + .SelectMany(GetTypes); - /// - /// Is object an instance of specified type - /// - /// Object - /// Type - /// Object is an instance of specified type or not - public static bool IsInstanceOfType(this object? obj, Type type) + static IEnumerable GetTypes(Assembly assembly) { - return type.IsInstanceOfType(obj); + return ExecutionExtensions + .Try>(assembly.GetTypes) + .Catch() + .Catch() + .Invoke(_ => Enumerable.Empty()); } + } - /// - /// Returns an attribute that type is anonymous or not - /// - /// Type - /// Whether type is anonymous or not - public static bool IsCompilerGenerated(this Type type) - { - return type.HasAttribute(); - } + public static Type FindType(this TypeNode typeNode) + { + return typeNode; + } - /// - /// Gets default value of the specified system type - /// - /// Type - /// Default value - public static object? DefaultValue(this Type type) + public static bool TryFindType(TypeNode typeNode, [NotNullWhen(true)] out Type? type) + { + try { - return type.IsValueType - ? Activator.CreateInstance(type) - : null; + type = FindType(typeNode); + return true; } - - /// - /// Does type represent collection or not - /// - /// Type - /// Result of check - public static bool IsCollection(this Type type) + catch (Exception) { - return typeof(IEnumerable).IsAssignableFrom(type) - && !typeof(IQueryable).IsAssignableFrom(type) - && type != typeof(string); + type = null; + return false; } + } - /// - /// Does type represent array or not - /// - /// Type - /// Result of check - public static bool IsArray(this Type type) - { - return type.IsArray || type == typeof(Array); - } + public static bool IsInstanceOfType(this object? obj, Type type) + { + return type.IsInstanceOfType(obj); + } - /// - /// Is type primitive or not - /// - Boolean, - /// - Byte - /// - SByte - /// - Int16 - /// - UInt16 - /// - Int32 - /// - UInt32 - /// - Int64 - /// - UInt64 - /// - IntPtr - /// - UIntPtr - /// - Char - /// - Double - /// - Single - /// - Decimal - /// - Enum - /// - Guid - /// - DateTime - /// - TimeSpan - /// - string - /// - System.Type - /// - /// Type - /// Is type can be interpreted as primitive - public static bool IsPrimitive(this Type type) - { - return IsPrimitiveType(type.ExtractGenericArgumentAtOrSelf(typeof(Nullable<>))); + public static bool IsCompilerGenerated(this Type type) + { + return type.HasAttribute(); + } - static bool IsPrimitiveType(Type t) - { - return t.IsPrimitive - || t.IsEnum - || t == typeof(Guid) - || t == typeof(DateTime) - || t == typeof(TimeSpan) - || t == typeof(decimal) - || t == typeof(string) - || t == typeof(Type); - } - } + public static object? DefaultValue(this Type type) + { + return type.IsValueType + ? Activator.CreateInstance(type) + : null; + } - /// - /// Is type numeric or not - /// - Int16 - /// - UInt16 - /// - Int32 - /// - UInt32 - /// - Int64 - /// - UInt64 - /// - Double - /// - Single - /// - Decimal - /// - /// Type - /// Result of check - public static bool IsNumeric(this Type type) - { - return IsNumericType(type.ExtractGenericArgumentAtOrSelf(typeof(Nullable<>))); + public static bool IsCollection(this Type type) + { + return typeof(IEnumerable).IsAssignableFrom(type) + && !typeof(IQueryable).IsAssignableFrom(type) + && type != typeof(string); + } - static bool IsNumericType(Type t) - { - return t == typeof(short) - || t == typeof(ushort) - || t == typeof(int) - || t == typeof(uint) - || t == typeof(long) - || t == typeof(ulong) - || t == typeof(float) - || t == typeof(double) - || t == typeof(decimal); - } - } + public static bool IsArray(this Type type) + { + return type.IsArray || type == typeof(Array); + } - /// - /// Does type implement Nullable - /// - /// Type for check - /// Result of check - public static bool IsNullable(this Type type) - { - return type.IsGenericType - && type.GetGenericTypeDefinition() == typeof(Nullable<>); + public static bool IsPrimitive(this Type type) + { + /* + * Is type primitive or not + * - Boolean + * - Byte + * - SByte + * - Int16 + * - UInt16 + * - Int32 + * - UInt32 + * - Int64 + * - UInt64 + * - IntPtr + * - UIntPtr + * - Char + * - Double + * - Single + * - Decimal + * - Enum + * - Guid + * - DateTime + * - TimeSpan + * - string + * - System.Type + */ + + return IsPrimitiveType(type.ExtractGenericArgumentAtOrSelf(typeof(Nullable<>))); + + static bool IsPrimitiveType(Type t) + { + return t.IsPrimitive + || t.IsEnum + || t == typeof(Guid) + || t == typeof(DateTime) + || t == typeof(TimeSpan) + || t == typeof(decimal) + || t == typeof(string) + || t == typeof(Type); } + } - /// - /// Does type represent enum flags - /// - /// Type for check - /// Result of check - public static bool IsEnumFlags(this Type type) - { - return type.IsEnum - && type.IsDefined(typeof(FlagsAttribute), false); + public static bool IsNumeric(this Type type) + { + /* + * Is type numeric or not + * - Int16 + * - UInt16 + * - Int32 + * - UInt32 + * - Int64 + * - UInt64 + * - Double + * - Single + * - Decimal + */ + + return IsNumericType(type.ExtractGenericArgumentAtOrSelf(typeof(Nullable<>))); + + static bool IsNumericType(Type t) + { + return t == typeof(short) + || t == typeof(ushort) + || t == typeof(int) + || t == typeof(uint) + || t == typeof(long) + || t == typeof(ulong) + || t == typeof(float) + || t == typeof(double) + || t == typeof(decimal); } + } - /// - /// Does type represents reference type - /// - /// Type for check - /// Result of check - public static bool IsReference(this Type type) - { - return !type.IsValueType; - } + public static bool IsNullable(this Type type) + { + return type.IsGenericType + && type.GetGenericTypeDefinition() == typeof(Nullable<>); + } - /// - /// Does type represent a record - /// - /// Type - /// Result of check - public static bool IsRecord(this Type type) - { - if (type.GetMethod("$") == null) - { - return false; - } + public static bool IsEnumFlags(this Type type) + { + return type.IsEnum + && type.IsDefined(typeof(FlagsAttribute), false); + } - return type - .GetPropertyValue("DeclaredProperties") - .SingleOrDefault(MemberExtensions.IsEqualityContract) != null; - } + public static bool IsReference(this Type type) + { + return !type.IsValueType; + } - /// - /// True for non-generic or constructed generic types - /// - /// Type - /// Is constructed or simple type - public static bool IsConstructedOrNonGenericType(this Type type) + public static bool IsRecord(this Type type) + { + if (type.GetMethod("$") == null) { - return !type.IsGenericType || type.IsConstructedGenericType; + return false; } - /// - /// Is type generic and partially closed - /// - /// Type - /// Type is generic and partially closed or not - public static bool IsPartiallyClosed(this Type type) - { - return type.IsGenericType - && type.ContainsGenericParameters - && type.GetGenericTypeDefinition() != type; - } + return type + .GetPropertyValue("DeclaredProperties") + .SingleOrDefault(MemberExtensions.IsEqualityContract) != null; + } - /// - /// Is type concrete - /// - /// Type - /// Type is concrete - public static bool IsConcreteType(this Type type) - { - return !type.IsAbstract - && !type.IsArray() - && type != typeof(object) - && !typeof(Delegate).IsAssignableFrom(type); - } + public static bool IsConstructedOrNonGenericType(this Type type) + { + return !type.IsGenericType || type.IsConstructedGenericType; + } - /// - /// Is constructor decorates specified type - /// - /// Constructor - /// Decorator dependency type - /// Constructor decorates specified type - public static bool IsDecorator(this ConstructorInfo cctor, Type dependency) - { - return cctor - .SelfDependencies(dependency) - .Any(type => ContainsDecorateeParameters(cctor, type)); + public static bool IsPartiallyClosed(this Type type) + { + return type.IsGenericType + && type.ContainsGenericParameters + && type.GetGenericTypeDefinition() != type; + } - static bool ContainsDecorateeParameters(ConstructorInfo cctor, Type dependency) - { - return cctor - .GetParameters() - .Any(parameter => IsDecorateeParameter(parameter, dependency)); - } + public static bool IsConcreteType(this Type type) + { + return !type.IsAbstract + && !type.IsArray() + && type != typeof(object) + && !typeof(Delegate).IsAssignableFrom(type); + } - static bool IsDecorateeParameter(ParameterInfo parameter, Type dependency) - { - return parameter.ParameterType == dependency; - } - } + public static bool IsDecorator(this ConstructorInfo cctor, Type dependency) + { + return cctor + .SelfDependencies(dependency) + .Any(type => ContainsDecorateeParameters(cctor, type)); - /// - /// Is constructor composes collections of specified type - /// - /// Constructor - /// Composite dependency type - /// Constructor composes collections of specified type - public static bool IsComposite(this ConstructorInfo cctor, Type dependency) + static bool ContainsDecorateeParameters(ConstructorInfo cctor, Type dependency) { return cctor - .SelfDependencies(dependency) - .Any(type => ContainsCompositeParameters(cctor, type)); - - static bool ContainsCompositeParameters(ConstructorInfo cctor, Type dependency) - { - return cctor - .GetParameters() - .Any(parameter => IsCompositeParameter(parameter, dependency)); - } - - static bool IsCompositeParameter(ParameterInfo parameter, Type dependency) - { - return parameter - .ParameterType - .ExtractGenericArgumentAt(typeof(IEnumerable<>)) == dependency; - } + .GetParameters() + .Any(parameter => IsDecorateeParameter(parameter, dependency)); } - /// - /// Does type derived from open-generic base type - /// - /// Type for check - /// Open generic ancestor - /// Result of check - public static bool IsSubclassOfOpenGeneric(this Type type, Type openGenericAncestor) + static bool IsDecorateeParameter(ParameterInfo parameter, Type dependency) { - if (!openGenericAncestor.IsGenericTypeDefinition) - { - return false; - } - - return TypeInfoStorage.Get(type).GenericTypeDefinitions.Contains(openGenericAncestor) - || TypeInfoStorage.Get(type).GenericInterfaceDefinitions.Contains(openGenericAncestor); + return parameter.ParameterType == dependency; } + } - /// - /// Does type contains interface declaration - /// - /// Type for check - /// Type-candidate for interface declaration - /// Result of check - public static bool IsContainsInterfaceDeclaration(this Type type, Type @interface) - { - if (TypeInfoStorage.Get(type).DeclaredInterfaces.Contains(@interface)) - { - return true; - } - - // generic - var genericTypeDefinition = type.GenericTypeDefinitionOrSelf(); - var genericInterfaceDefinition = @interface.GenericTypeDefinitionOrSelf(); - - return TypeInfoStorage.Get(genericTypeDefinition) - .DeclaredInterfaces - .Select(z => z.GUID) - .Contains(genericInterfaceDefinition.GUID) - && type.GetGenericArguments().SequenceEqual(@interface.GetGenericArguments()); - } + public static bool IsComposite(this ConstructorInfo cctor, Type dependency) + { + return cctor + .SelfDependencies(dependency) + .Any(type => ContainsCompositeParameters(cctor, type)); - /// - /// Get specified attribute from type - /// - /// Type - /// TAttribute type-argument - /// Attribute - /// Throws if source is empty - /// Throws if source contains more than one element - public static TAttribute GetRequiredAttribute(this Type type) - where TAttribute : Attribute + static bool ContainsCompositeParameters(ConstructorInfo cctor, Type dependency) { - return TypeInfoStorage - .Get(type) - .Attributes - .OfType() - .InformativeSingleOrDefault(Amb) - ?? throw new AttributeRequiredException(typeof(TAttribute), type); - - static string Amb(IEnumerable arg) - { - return $"Type has more than one {typeof(TAttribute)}"; - } + return cctor + .GetParameters() + .Any(parameter => IsCompositeParameter(parameter, dependency)); } - /// - /// Get specified attributes from type - /// - /// Type - /// TAttribute type-argument - /// Attribute - public static IEnumerable GetAttributes(this Type type) - where TAttribute : Attribute + static bool IsCompositeParameter(ParameterInfo parameter, Type dependency) { - return TypeInfoStorage - .Get(type) - .Attributes - .OfType(); + return parameter + .ParameterType + .ExtractGenericArgumentAt(typeof(IEnumerable<>)) == dependency; } + } - /// - /// Get specified attribute from type - /// - /// Type - /// TAttribute type-argument - /// Attribute - /// Throws if source is empty - /// Throws if source contains more than one element - public static TAttribute? GetAttribute(this Type type) - where TAttribute : Attribute + public static bool IsSubclassOfOpenGeneric(this Type type, Type openGenericAncestor) + { + if (!openGenericAncestor.IsGenericTypeDefinition) { - return TypeInfoStorage - .Get(type) - .Attributes - .OfType() - .InformativeSingleOrDefault(Amb); - - static string Amb(IEnumerable arg) - { - return $"Type has more than one {typeof(TAttribute)}"; - } + return false; } - /// - /// Does the specified type has an attribute - /// - /// Type - /// TAttribute type-argument - /// Attribute existence - public static bool HasAttribute(this Type type) - where TAttribute : Attribute + return TypeInfoStorage.Get(type).GenericTypeDefinitions.Contains(openGenericAncestor) + || TypeInfoStorage.Get(type).GenericInterfaceDefinitions.Contains(openGenericAncestor); + } + + public static bool IsContainsInterfaceDeclaration(this Type type, Type @interface) + { + if (TypeInfoStorage.Get(type).DeclaredInterfaces.Contains(@interface)) { - return TypeInfoStorage - .Get(type) - .Attributes - .OfType() - .Any(); + return true; } - /// - /// Does the specified type has an attribute - /// - /// Type - /// Attribute type - /// Attribute existence - public static bool HasAttribute(this Type type, Type attributeType) + // generic + var genericTypeDefinition = type.GenericTypeDefinitionOrSelf(); + var genericInterfaceDefinition = @interface.GenericTypeDefinitionOrSelf(); + + return TypeInfoStorage.Get(genericTypeDefinition) + .DeclaredInterfaces + .Select(z => z.GUID) + .Contains(genericInterfaceDefinition.GUID) + && type.GetGenericArguments().SequenceEqual(@interface.GetGenericArguments()); + } + + public static IEnumerable ExtractGenericArgumentsAt(this Type source, Type openGeneric, int typeArgumentAt = 0) + { + if (!openGeneric.IsGenericTypeDefinition) { - return TypeInfoStorage - .Get(type) - .Attributes - .Any(attributeType.IsInstanceOfType); + throw new ArgumentException("Should be GenericTypeDefinition", nameof(openGeneric)); } - /// Order types collection by BeforeAttribute and AfterAttribute - /// Unordered types collection - /// Source type has cycle dependency - /// Ordered by BeforeAttribute and AfterAttribute collection - public static IOrderedEnumerable OrderByDependencies(this IEnumerable source) + if (typeArgumentAt < 0 || typeArgumentAt >= openGeneric.GetGenericArguments().Length) { - return source.OrderByDependencies(t => t.GenericTypeDefinitionOrSelf()); + throw new ArgumentException("Should be in bounds of generic arguments count", nameof(typeArgumentAt)); } - /// Order collection by BeforeAttribute and AfterAttribute - /// Unordered source collection - /// Type accessor - /// Source items type-argument - /// Source type has cycle dependency - /// Ordered by BeforeAttribute and AfterAttribute collection - public static IOrderedEnumerable OrderByDependencies(this IEnumerable source, Func accessor) - { - int SortFunc(T item) - { - var type = accessor(item); - var dependencies = GetDependenciesByAttribute(type).ToList(); + return !IsSubclassOfOpenGeneric(source, openGeneric) + ? Enumerable.Empty() + : source + .IncludedTypes() + .Where(type => type.GenericTypeDefinitionOrSelf() == openGeneric) + .Select(type => type.GetGenericArguments()[typeArgumentAt]) + .Distinct(); + } - var depth = 0; + public static Type ExtractGenericArgumentAt(this Type source, Type openGeneric, int typeArgumentAt = 0) + { + return source.ExtractGenericArgumentsAt(openGeneric, typeArgumentAt).Single(); + } - while (dependencies.Any()) - { - if (dependencies.Contains(type)) - { - throw new InvalidOperationException($"{type} has cycle dependency"); - } + public static Type ExtractGenericArgumentAtOrSelf(this Type source, Type openGeneric, int typeArgumentAt = 0) + { + return openGeneric == typeof(Nullable<>) + ? Nullable.GetUnderlyingType(source) ?? source + : source.ExtractGenericArgumentsAt(openGeneric, typeArgumentAt).SingleOrDefault() ?? source; + } - ++depth; + public static IEnumerable ExtractAllGenericArguments(this Type source, Type openGeneric) + { + if (!openGeneric.IsGenericTypeDefinition) + { + throw new ArgumentException("Should be GenericTypeDefinition", nameof(openGeneric)); + } - dependencies = dependencies.SelectMany(GetDependenciesByAttribute).ToList(); - } + return !IsSubclassOfOpenGeneric(source, openGeneric) + ? Enumerable.Empty() + : source + .IncludedTypes() + .Where(type => type.GenericTypeDefinitionOrSelf() == openGeneric) + .Select(type => type.GetGenericArguments().ToArray()); + } - return depth; - } + public static Type[] ExtractGenericArguments(this Type source, Type openGeneric) + { + return source.ExtractAllGenericArguments(openGeneric).Single(); + } - return source.OrderBy(SortFunc); + public static Type ApplyGenericArguments(this Type openGeneric, Type source) + { + if (openGeneric.IsConstructedOrNonGenericType()) + { + return openGeneric; } - /// Order collection by BeforeAttribute and AfterAttribute - /// Unordered source collection - /// Key accessor - /// Dependencies accessor - /// TSource type-argument - /// TDependency type-argument - /// Source type has cycle dependency - /// Ordered by BeforeAttribute and AfterAttribute collection - public static IOrderedEnumerable OrderByDependencies( - this IEnumerable source, - Func getKey, - Func> getDependencies) + if (openGeneric.IsGenericType + && openGeneric.IsGenericTypeDefinition + && source.IsConstructedOrNonGenericType()) { - return source.OrderBy(SortFunc); + var genericArguments = source + .ExtractAllGenericArguments(openGeneric) + .InformativeSingle(Amb(openGeneric, source)); - int SortFunc(TSource item) - { - var key = getKey(item); - var dependencies = getDependencies(item).ToList(); - var dependenciesKeys = dependencies.Select(getKey).ToList(); - - var depth = 0; - - while (dependenciesKeys.Any()) - { - if (dependenciesKeys.Contains(key)) - { - throw new InvalidOperationException($"{key} has cycle dependency"); - } - - ++depth; - - dependencies = dependencies.SelectMany(getDependencies).ToList(); - dependenciesKeys = dependencies.Select(getKey).ToList(); - } - - return depth; - } + return openGeneric.MakeGenericType(genericArguments); } - /// - /// Get type dependencies by BeforeAttribute and AfterAttribute - /// - /// Type - /// Type dependencies - public static IEnumerable GetDependenciesByAttribute(this Type type) - { - return TypeInfoStorage.Get(type).Dependencies; - } + throw new InvalidOperationException($"Type {openGeneric.FullName} can't be closed from {source.FullName}"); - /// - /// Extracts type-arguments from derived class of open-generic type at specified index - /// Distinct, might be several implementations with different type-arguments - /// - /// Source type for extraction - /// Open-generic which derived - /// Index of type-argument - /// Collection of type arguments at specified index. - public static IEnumerable ExtractGenericArgumentsAt(this Type source, Type openGeneric, int typeArgumentAt = 0) + static Func, string> Amb(Type openGeneric, Type source) { - if (!openGeneric.IsGenericTypeDefinition) - { - throw new ArgumentException("Should be GenericTypeDefinition", nameof(openGeneric)); - } - - if (typeArgumentAt < 0 || typeArgumentAt >= openGeneric.GetGenericArguments().Length) + return args => { - throw new ArgumentException("Should be in bounds of generic arguments count", nameof(typeArgumentAt)); - } + var details = args + .Select(genericArguments => genericArguments + .Select(arg => arg.Name) + .ToString(", ")) + .ToString("; "); - return !IsSubclassOfOpenGeneric(source, openGeneric) - ? Enumerable.Empty() - : source - .IncludedTypes() - .Where(type => type.GenericTypeDefinitionOrSelf() == openGeneric) - .Select(type => type.GetGenericArguments()[typeArgumentAt]) - .Distinct(); + return $"Type {source.FullName} has different implementations of {openGeneric.FullName}: {details}"; + }; } + } - /// - /// Extracts required type-argument from derived class of open-generic type at specified index - /// - /// Source type - /// Open generic wrapper - /// Type argument position - /// Unwrapped type - public static Type ExtractGenericArgumentAt(this Type source, Type openGeneric, int typeArgumentAt = 0) - { - return source.ExtractGenericArgumentsAt(openGeneric, typeArgumentAt).Single(); - } + public static IEnumerable BaseTypes(this Type source) + { + /* + * Base types from source type + * - base types + * - interfaces + */ + + return TypeInfoStorage.Get(source).BaseTypes + .Concat(source.GetInterfaces()); + } - /// - /// Extracts type-argument from derived class of open-generic type at specified index if possible - /// - /// Source type - /// Open generic wrapper - /// Type argument position - /// Unwrapped type - public static Type ExtractGenericArgumentAtOrSelf(this Type source, Type openGeneric, int typeArgumentAt = 0) - { - return openGeneric == typeof(Nullable<>) - ? Nullable.GetUnderlyingType(source) ?? source - : source.ExtractGenericArgumentsAt(openGeneric, typeArgumentAt).SingleOrDefault() ?? source; - } + public static IEnumerable IncludedTypes(this Type source) + { + /* + * Types included in source type + * - source + * - base types + * - interfaces + */ + + return new[] { source }.Concat(source.BaseTypes()); + } + + public static IReadOnlyCollection DerivedTypes(this Type source) + { + return TypeInfoStorage.Get(source).DerivedTypes; + } - /// - /// Extracts all type-arguments from derived class of open-generic type - /// - /// Source type for extraction - /// Open-generic which derived - /// Collection of type arguments at specified index. - public static IEnumerable ExtractAllGenericArguments(this Type source, Type openGeneric) + public static Type GenericTypeDefinitionOrSelf(this Type type) + { + return type.IsGenericType + ? type.GetGenericTypeDefinition() + : type; + } + + public static bool FitsForTypeArgument(this Type typeForCheck, Type typeArgument) + { + if (!typeArgument.IsGenericParameter) { - if (!openGeneric.IsGenericTypeDefinition) + if (!typeArgument.IsGenericType) { - throw new ArgumentException("Should be GenericTypeDefinition", nameof(openGeneric)); + return typeArgument.IsAssignableFrom(typeForCheck); } - return !IsSubclassOfOpenGeneric(source, openGeneric) - ? Enumerable.Empty() - : source - .IncludedTypes() - .Where(type => type.GenericTypeDefinitionOrSelf() == openGeneric) - .Select(type => type.GetGenericArguments().ToArray()); - } + var genericParameters = typeArgument.GetGenericArguments(); - /// - /// Extracts required combination of type-arguments from derived class of open-generic type - /// - /// Source type - /// Open generic wrapper - /// Unwrapped type - public static Type[] ExtractGenericArguments(this Type source, Type openGeneric) - { - return source.ExtractAllGenericArguments(openGeneric).Single(); + return typeForCheck + .ExtractAllGenericArguments(typeArgument.GetGenericTypeDefinition()) + .Any(set => set + .Zip(genericParameters, (arg, param) => (arg, param)) + .All(pair => pair.arg.FitsForTypeArgument(pair.param))); } - /// - /// Applies generic arguments having based on specified source type - /// - /// Open-generic - /// Source with type-arguments - /// Constructed generic type - public static Type ApplyGenericArguments(this Type openGeneric, Type source) - { - if (openGeneric.IsConstructedOrNonGenericType()) - { - return openGeneric; - } + var byConstraints = typeArgument + .GetGenericParameterConstraints() + .All(CheckConstraint); - if (openGeneric.IsGenericType - && openGeneric.IsGenericTypeDefinition - && source.IsConstructedOrNonGenericType()) - { - var genericArguments = source - .ExtractAllGenericArguments(openGeneric) - .InformativeSingle(Amb(openGeneric, source)); - - return openGeneric.MakeGenericType(genericArguments); - } + var filters = GetFiltersByTypeParameterAttributes(typeArgument.GenericParameterAttributes); + var byGenericParameterAttributes = filters.All(filter => filter(typeForCheck)); - throw new InvalidOperationException($"Type {openGeneric.FullName} can't be closed from {source.FullName}"); + return byConstraints && byGenericParameterAttributes; - static Func, string> Amb(Type openGeneric, Type source) + bool CheckConstraint(Type constraint) + { + if (!constraint.IsGenericType) { - return args => - { - var details = args - .Select(genericArguments => genericArguments - .Select(arg => arg.Name) - .ToString(", ")) - .ToString("; "); - - return $"Type {source.FullName} has different implementations of {openGeneric.FullName}: {details}"; - }; + return constraint.IsAssignableFrom(typeForCheck); } - } - /// - /// Base types from source type - /// - base types - /// - interfaces - /// - /// Source type - /// Included types - public static IEnumerable BaseTypes(this Type source) - { - return TypeInfoStorage.Get(source).BaseTypes - .Concat(source.GetInterfaces()); - } + var constraintGenericTypeDefinition = constraint.GetGenericTypeDefinition(); - /// - /// Types included in source type - /// - source - /// - base types - /// - interfaces - /// - /// Source type - /// Included types - public static IEnumerable IncludedTypes(this Type source) - { - return new[] { source }.Concat(source.BaseTypes()); - } + return IsSubclassOfOpenGeneric(typeForCheck, constraintGenericTypeDefinition) + && HasSuitableTypeArguments(); - /// - /// Types derived from source type - /// - /// Source type - /// Derived types - public static IReadOnlyCollection DerivedTypes(this Type source) - { - return TypeInfoStorage.Get(source).DerivedTypes; + bool HasSuitableTypeArguments() + { + var typeArgumentsForCheck = ExtractAllGenericArguments(typeForCheck, constraintGenericTypeDefinition).ToArray(); + + return constraint + .GetGenericArguments() + .Select((constraintGenericArgument, position) => (constraintGenericArgument, position)) + .All(info => typeArgumentsForCheck + .Any(typeArgumentForCheck => info.constraintGenericArgument == typeArgument + ? typeForCheck == typeArgumentForCheck[info.position] // T : IService + : FitsForTypeArgument(typeArgumentForCheck[info.position], info.constraintGenericArgument))); + } } - /// - /// Extract GenericTypeDefinition or return argument type - /// - /// Type - /// GenericTypeDefinition or argument type - public static Type GenericTypeDefinitionOrSelf(this Type type) + static ICollection> GetFiltersByTypeParameterAttributes(GenericParameterAttributes genericParameterAttributes) { - return type.IsGenericType - ? type.GetGenericTypeDefinition() - : type; - } + var filters = new List>(); - /// - /// Does type fits for type argument - /// - /// Actual type for check - /// Type argument - /// True - fits / False doesn't fits - public static bool FitsForTypeArgument(this Type typeForCheck, Type typeArgument) - { - if (!typeArgument.IsGenericParameter) + if (genericParameterAttributes.HasFlag(GenericParameterAttributes.ReferenceTypeConstraint)) { - if (!typeArgument.IsGenericType) - { - return typeArgument.IsAssignableFrom(typeForCheck); - } - - var genericParameters = typeArgument.GetGenericArguments(); - - return typeForCheck - .ExtractAllGenericArguments(typeArgument.GetGenericTypeDefinition()) - .Any(set => set - .Zip(genericParameters, (arg, param) => (arg, param)) - .All(pair => pair.arg.FitsForTypeArgument(pair.param))); + filters.Add(type => type.IsClass || type.IsInterface); } - var byConstraints = typeArgument - .GetGenericParameterConstraints() - .All(CheckConstraint); - - var filters = GetFiltersByTypeParameterAttributes(typeArgument.GenericParameterAttributes); - var byGenericParameterAttributes = filters.All(filter => filter(typeForCheck)); - - return byConstraints && byGenericParameterAttributes; - - bool CheckConstraint(Type constraint) + if (genericParameterAttributes.HasFlag(GenericParameterAttributes.NotNullableValueTypeConstraint)) { - if (!constraint.IsGenericType) - { - return constraint.IsAssignableFrom(typeForCheck); - } - - var constraintGenericTypeDefinition = constraint.GetGenericTypeDefinition(); - - return IsSubclassOfOpenGeneric(typeForCheck, constraintGenericTypeDefinition) - && HasSuitableTypeArguments(); - - bool HasSuitableTypeArguments() - { - var typeArgumentsForCheck = ExtractAllGenericArguments(typeForCheck, constraintGenericTypeDefinition).ToArray(); - - return constraint - .GetGenericArguments() - .Select((constraintGenericArgument, position) => (constraintGenericArgument, position)) - .All(info => typeArgumentsForCheck - .Any(typeArgumentForCheck => info.constraintGenericArgument == typeArgument - ? typeForCheck == typeArgumentForCheck[info.position] // T : IService - : FitsForTypeArgument(typeArgumentForCheck[info.position], info.constraintGenericArgument))); - } + filters.Add(type => type.IsValueType && !type.IsNullable()); } - static ICollection> GetFiltersByTypeParameterAttributes(GenericParameterAttributes genericParameterAttributes) + if (genericParameterAttributes.HasFlag(GenericParameterAttributes.DefaultConstructorConstraint)) { - var filters = new List>(); - - if (genericParameterAttributes.HasFlag(GenericParameterAttributes.ReferenceTypeConstraint)) - { - filters.Add(type => type.IsClass || type.IsInterface); - } - - if (genericParameterAttributes.HasFlag(GenericParameterAttributes.NotNullableValueTypeConstraint)) + filters.Add(type => { - filters.Add(type => type.IsValueType && !type.IsNullable()); - } - - if (genericParameterAttributes.HasFlag(GenericParameterAttributes.DefaultConstructorConstraint)) - { - filters.Add(type => + if (type.IsValueType) { - if (type.IsValueType) - { - return true; - } - - var ctor = type.GetConstructor(Array.Empty()); + return true; + } - return ctor != null; - }); - } + var ctor = type.GetConstructor(Array.Empty()); - if (!filters.Any()) - { - filters.Add(type => true); - } + return ctor != null; + }); + } - return filters; + if (!filters.Any()) + { + filters.Add(type => true); } - } - private static IEnumerable SelfDependencies(this ConstructorInfo cctor, Type dependency) - { - return cctor - .DeclaringType - .BaseTypes() - .Where(type => type.GenericTypeDefinitionOrSelf() == dependency.GenericTypeDefinitionOrSelf()); + return filters; } } + + private static IEnumerable SelfDependencies(this ConstructorInfo cctor, Type dependency) + { + return cctor + .DeclaringType + .BaseTypes() + .Where(type => type.GenericTypeDefinitionOrSelf() == dependency.GenericTypeDefinitionOrSelf()); + } } \ No newline at end of file diff --git a/src/Common/Basics/TypeInfo.cs b/src/Common/Basics/TypeInfo.cs index 984e9bb0..8edb2adc 100644 --- a/src/Common/Basics/TypeInfo.cs +++ b/src/Common/Basics/TypeInfo.cs @@ -1,126 +1,121 @@ -namespace SpaceEngineers.Core.Basics +namespace SpaceEngineers.Core.Basics; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; + +[DebuggerDisplay("{OriginalType}")] +internal class TypeInfo { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Linq; - using System.Threading; - - [DebuggerDisplay("{OriginalType}")] - internal class TypeInfo + private readonly Lazy> _baseTypes; + private readonly Lazy> _derivedTypes; + private readonly Lazy> _genericTypeDefinitions; + private readonly Lazy> _declaredInterfaces; + private readonly Lazy> _genericInterfaceDefinitions; + private readonly Lazy> _attributes; + + internal TypeInfo(Type type) { - private readonly Lazy> _dependencies; - private readonly Lazy> _baseTypes; - private readonly Lazy> _derivedTypes; - private readonly Lazy> _genericTypeDefinitions; - private readonly Lazy> _declaredInterfaces; - private readonly Lazy> _genericInterfaceDefinitions; - private readonly Lazy> _attributes; - - internal TypeInfo(Type type) - { - OriginalType = type; - - _dependencies = new Lazy>(() => TypeInfoStorage.ExtractDependencies(type).ToList(), LazyThreadSafetyMode.ExecutionAndPublication); - _baseTypes = new Lazy>(() => ExtractBaseTypes(type).ToList(), LazyThreadSafetyMode.ExecutionAndPublication); - _derivedTypes = new Lazy>(() => ExtractDerivedTypes(type).ToList(), LazyThreadSafetyMode.ExecutionAndPublication); - _genericTypeDefinitions = new Lazy>(() => ExtractGenericTypeDefinitions(type).ToList(), LazyThreadSafetyMode.ExecutionAndPublication); - _declaredInterfaces = new Lazy>(() => ExtractDeclaredInterfaces(type).ToList(), LazyThreadSafetyMode.ExecutionAndPublication); - _genericInterfaceDefinitions = new Lazy>(() => ExtractGenericInterfaceDefinitions(type).ToList(), LazyThreadSafetyMode.ExecutionAndPublication); - _attributes = new Lazy>(() => ExtractAttributes(type).ToList(), LazyThreadSafetyMode.ExecutionAndPublication); - } + OriginalType = type; + + _baseTypes = new Lazy>(() => ExtractBaseTypes(type).ToList(), LazyThreadSafetyMode.ExecutionAndPublication); + _derivedTypes = new Lazy>(() => ExtractDerivedTypes(type).ToList(), LazyThreadSafetyMode.ExecutionAndPublication); + _genericTypeDefinitions = new Lazy>(() => ExtractGenericTypeDefinitions(type).ToList(), LazyThreadSafetyMode.ExecutionAndPublication); + _declaredInterfaces = new Lazy>(() => ExtractDeclaredInterfaces(type).ToList(), LazyThreadSafetyMode.ExecutionAndPublication); + _genericInterfaceDefinitions = new Lazy>(() => ExtractGenericInterfaceDefinitions(type).ToList(), LazyThreadSafetyMode.ExecutionAndPublication); + _attributes = new Lazy>(() => ExtractAttributes(type).ToList(), LazyThreadSafetyMode.ExecutionAndPublication); + } - public Type OriginalType { get; } + public Type OriginalType { get; } - public IReadOnlyCollection Dependencies => _dependencies.Value; + public IReadOnlyCollection BaseTypes => _baseTypes.Value; - public IReadOnlyCollection BaseTypes => _baseTypes.Value; + public IReadOnlyCollection DerivedTypes => _derivedTypes.Value; - public IReadOnlyCollection DerivedTypes => _derivedTypes.Value; + public IReadOnlyCollection GenericTypeDefinitions => _genericTypeDefinitions.Value; - public IReadOnlyCollection GenericTypeDefinitions => _genericTypeDefinitions.Value; + public IReadOnlyCollection DeclaredInterfaces => _declaredInterfaces.Value; - public IReadOnlyCollection DeclaredInterfaces => _declaredInterfaces.Value; + public IReadOnlyCollection GenericInterfaceDefinitions => _genericInterfaceDefinitions.Value; - public IReadOnlyCollection GenericInterfaceDefinitions => _genericInterfaceDefinitions.Value; + public IReadOnlyCollection Attributes => _attributes.Value; - public IReadOnlyCollection Attributes => _attributes.Value; + private static IEnumerable ExtractBaseTypes(Type type) + { + var currentType = type; - private static IEnumerable ExtractBaseTypes(Type type) + while (currentType.BaseType != null) { - var currentType = type; - - while (currentType.BaseType != null) - { - yield return currentType.BaseType; + yield return currentType.BaseType; - currentType = currentType.BaseType; - } + currentType = currentType.BaseType; } + } - private static IEnumerable ExtractDerivedTypes(Type type) - { - return TypeExtensions - .AllTypes() - .Where(type.IsAssignableFrom); - } + private static IEnumerable ExtractDerivedTypes(Type type) + { + return TypeExtensions + .AllTypes() + .Where(type.IsAssignableFrom); + } - private static IEnumerable ExtractGenericTypeDefinitions(Type type) - { - var currentType = type; + private static IEnumerable ExtractGenericTypeDefinitions(Type type) + { + var currentType = type; - while (currentType != null) + while (currentType != null) + { + if (currentType.IsGenericType) { - if (currentType.IsGenericType) - { - yield return currentType.GetGenericTypeDefinition(); - } - - currentType = currentType.BaseType; + yield return currentType.GetGenericTypeDefinition(); } - } - private static IEnumerable ExtractDeclaredInterfaces(Type type) - { - var allTypeInterfaces = type.GetInterfaces(); - var nestedInterfaces = allTypeInterfaces.SelectMany(i => i.GetInterfaces()); + currentType = currentType.BaseType; + } + } - var declaredInterfaces = allTypeInterfaces.Except(nestedInterfaces); + private static IEnumerable ExtractDeclaredInterfaces(Type type) + { + var allTypeInterfaces = type.GetInterfaces(); + var nestedInterfaces = allTypeInterfaces.SelectMany(i => i.GetInterfaces()); - var currentType = type; - while (currentType.BaseType != null) - { - declaredInterfaces = declaredInterfaces.Except(currentType.BaseType.GetInterfaces()); + var declaredInterfaces = allTypeInterfaces.Except(nestedInterfaces); - currentType = currentType.BaseType; - } + var currentType = type; + while (currentType.BaseType != null) + { + declaredInterfaces = declaredInterfaces.Except(currentType.BaseType.GetInterfaces()); - foreach (var i in declaredInterfaces) - { - yield return i; - } + currentType = currentType.BaseType; } - private static IEnumerable ExtractGenericInterfaceDefinitions(Type type) + foreach (var i in declaredInterfaces) { - var source = type.GetInterfaces().AsEnumerable(); + yield return i; + } + } - if (type.IsInterface) - { - source = new[] { type }.Concat(source); - } + private static IEnumerable ExtractGenericInterfaceDefinitions(Type type) + { + var source = type.GetInterfaces().AsEnumerable(); - foreach (var i in source.Where(i => i.IsGenericType)) - { - yield return i.GetGenericTypeDefinition(); - } + if (type.IsInterface) + { + source = new[] { type }.Concat(source); } - private static IEnumerable ExtractAttributes(Type type) + foreach (var i in source.Where(i => i.IsGenericType)) { - return type - .GetCustomAttributes(true) - .OfType(); + yield return i.GetGenericTypeDefinition(); } } + + private static IEnumerable ExtractAttributes(Type type) + { + return type + .GetCustomAttributes(true) + .OfType(); + } } \ No newline at end of file diff --git a/src/Common/Basics/TypeInfoStorage.cs b/src/Common/Basics/TypeInfoStorage.cs index 94d13f5a..8dc79afb 100644 --- a/src/Common/Basics/TypeInfoStorage.cs +++ b/src/Common/Basics/TypeInfoStorage.cs @@ -1,88 +1,54 @@ -namespace SpaceEngineers.Core.Basics -{ - using System; - using System.Collections.Concurrent; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Linq; - using System.Reflection; - using System.Threading; - using Attributes; +namespace SpaceEngineers.Core.Basics; - internal sealed class TypeInfoStorage - { - private static readonly ConcurrentDictionary Cache - = new ConcurrentDictionary(); +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; - private static readonly Lazy>> TypesCache - = new Lazy>>(InitializeTypesCache, LazyThreadSafetyMode.ExecutionAndPublication); +internal sealed class TypeInfoStorage +{ + private static readonly ConcurrentDictionary Cache + = new ConcurrentDictionary(); - private static readonly Lazy>> BeforeAfterAttributesMap - = new Lazy>>(InitializeBeforeAfterAttributesMap, LazyThreadSafetyMode.ExecutionAndPublication); + private static readonly Lazy>> TypesCache + = new Lazy>>(InitializeTypesCache, LazyThreadSafetyMode.ExecutionAndPublication); - internal static bool TryGet(string assemblyName, string typeFullName, [NotNullWhen(true)] out Type? type) + internal static bool TryGet(string assemblyName, string typeFullName, [NotNullWhen(true)] out Type? type) + { + if (TypesCache.Value.TryGetValue(assemblyName, out var types) + && types.TryGetValue(typeFullName, out type)) { - if (TypesCache.Value.TryGetValue(assemblyName, out var types) - && types.TryGetValue(typeFullName, out type)) - { - return true; - } - - type = default; - return false; + return true; } - internal static TypeInfo Get(Type type) => Cache.GetOrAdd(GetKey(type), static (_, t) => new TypeInfo(t), type); - - internal static IEnumerable ExtractDependencies(Type type) - { - var byAfterAttribute = type - .GetCustomAttribute() - ?.Types ?? Enumerable.Empty(); - - var byBeforeAttribute = BeforeAfterAttributesMap.Value.TryGetValue(type, out var value) - ? value - : Enumerable.Empty(); + type = default; + return false; + } - return byAfterAttribute.Concat(byBeforeAttribute); - } + internal static TypeInfo Get(Type type) => Cache.GetOrAdd(GetKey(type), static (_, t) => new TypeInfo(t), type); - private static string GetKey(Type type) + private static string GetKey(Type type) + { + if (type.IsGenericParameter) { - if (type.IsGenericParameter) - { - throw new InvalidOperationException("Type cache doesn't support generic parameters"); - } - - return !type.IsGenericTypeDefinition && type.ContainsGenericParameters - ? type.ToString() - : type.FullName ?? throw new InvalidOperationException($"Type cache doesn't support types without {nameof(Type.FullName)}: {type}"); + throw new InvalidOperationException("Type cache doesn't support generic parameters"); } - private static IReadOnlyDictionary> InitializeTypesCache() - { - return AssembliesExtensions - .AllAssembliesFromCurrentDomain() - .ToDictionary( - assembly => assembly.GetName().Name, - assembly => (IReadOnlyDictionary)assembly - .GetTypes() - .ToDictionary(type => type.FullName)); - } + return !type.IsGenericTypeDefinition && type.ContainsGenericParameters + ? type.ToString() + : type.FullName ?? throw new InvalidOperationException($"Type cache doesn't support types without {nameof(Type.FullName)}: {type}"); + } - private static IReadOnlyDictionary> InitializeBeforeAfterAttributesMap() - { - return TypeExtensions - .AllTypes() - .Select(type => - { - var attribute = type.GetCustomAttribute(); - return (type, attribute); - }) - .Where(pair => pair.attribute != null) - .SelectMany(pair => pair.attribute.Types.Select(before => (before, pair.type))) - .GroupBy(pair => pair.before, pair => pair.type) - .ToDictionary(grp => grp.Key, grp => grp.ToList() as IReadOnlyCollection); - } + private static IReadOnlyDictionary> InitializeTypesCache() + { + return AssemblyExtensions + .AllAssembliesFromCurrentDomain() + .ToDictionary( + assembly => assembly.GetName().Name, + assembly => (IReadOnlyDictionary)assembly + .GetTypes() + .ToDictionary(type => type.FullName)); } } \ No newline at end of file diff --git a/src/Common/Basics/TypeNode.cs b/src/Common/Basics/TypeNode.cs index 85514916..e732e615 100644 --- a/src/Common/Basics/TypeNode.cs +++ b/src/Common/Basics/TypeNode.cs @@ -1,265 +1,197 @@ -namespace SpaceEngineers.Core.Basics +namespace SpaceEngineers.Core.Basics; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security; +using System.Text; + +public class TypeNode : IEquatable, + ISafelyEquatable { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Security; - using System.Text; - - /// - /// TypeNode - /// - public class TypeNode : IEquatable, - ISafelyEquatable + private TypeNode(Type type) { - private TypeNode(Type type) - { - Assembly = type.Assembly.GetName().Name; + Assembly = type.Assembly.GetName().Name; - Type = type.GenericTypeDefinitionOrSelf().FullName; + Type = type.GenericTypeDefinitionOrSelf().FullName; - GenericArguments = type.IsGenericType - ? type.GetGenericArguments() - .Where(genericArgument => !genericArgument.IsGenericParameter) - .Select(genericArgument => new TypeNode(genericArgument)) - .ToList() - : new List(); + GenericArguments = type.IsGenericType + ? type.GetGenericArguments() + .Where(genericArgument => !genericArgument.IsGenericParameter) + .Select(genericArgument => new TypeNode(genericArgument)) + .ToList() + : new List(); - IsArray = type.IsArray(); - } + IsArray = type.IsArray(); + } - private TypeNode(string assembly, string type, IReadOnlyCollection genericArguments) - { - Assembly = assembly; - Type = type; - GenericArguments = genericArguments; - IsArray = type.EndsWith("[]", StringComparison.OrdinalIgnoreCase); - } + private TypeNode(string assembly, string type, IReadOnlyCollection genericArguments) + { + Assembly = assembly; + Type = type; + GenericArguments = genericArguments; + IsArray = type.EndsWith("[]", StringComparison.OrdinalIgnoreCase); + } - /// - /// Assembly - /// - public string Assembly { get; } - - /// - /// Type - /// - public string Type { get; } - - /// - /// GenericArguments - /// - public IReadOnlyCollection GenericArguments { get; } - - /// - /// IsArray - /// - public bool IsArray { get; } - - /// - /// Implicit conversion operator to System.String - /// - /// TypeNode - /// System.String - public static implicit operator string(TypeNode node) => node.ToString(); - - /// - /// Implicit conversion operator from System.String - /// - /// Type - /// TypeNode - public static implicit operator TypeNode(string type) => FromString(type); - - /// - /// Implicit conversion operator to System.Type - /// - /// TypeNode - /// System.Type - public static implicit operator Type(TypeNode node) => ToType(node); - - /// - /// Implicit conversion operator from System.Type - /// - /// Type - /// TypeNode - public static implicit operator TypeNode(Type type) => FromType(type); - - #region IEquatable - - /// - /// operator == - /// - /// Left TypeNode - /// Right TypeNode - /// equals - public static bool operator ==(TypeNode? left, TypeNode? right) - { - return Equatable.Equals(left, right); - } + public string Assembly { get; } - /// - /// operator != - /// - /// Left TypeNode - /// Right TypeNode - /// not equals - public static bool operator !=(TypeNode? left, TypeNode? right) - { - return !Equatable.Equals(left, right); - } + public string Type { get; } - /// - public bool SafeEquals(TypeNode other) - { - return ToString().Equals(other.ToString(), StringComparison.Ordinal); - } + public IReadOnlyCollection GenericArguments { get; } - /// - public bool Equals(TypeNode? other) - { - return Equatable.Equals(this, other); - } + public bool IsArray { get; } - /// - public override bool Equals(object? obj) - { - return Equatable.Equals(this, obj); - } + public static implicit operator string(TypeNode node) => node.ToString(); - /// - public override int GetHashCode() - { - return ToString().GetHashCode(StringComparison.Ordinal); - } + public static implicit operator TypeNode(string type) => FromString(type); - #endregion + public static implicit operator Type(TypeNode node) => ToType(node); - /// - public override string ToString() - { - return Stringify(this, 0); + public static implicit operator TypeNode(Type type) => FromType(type); - static string Stringify(TypeNode node, int depth) - { - var sb = new StringBuilder(); + #region IEquatable - sb.Append(new string('\t', depth)); - sb.Append(node.Assembly); - sb.Append(' '); + public static bool operator ==(TypeNode? left, TypeNode? right) + { + return Equatable.Equals(left, right); + } - if (node.GenericArguments.Any()) - { - sb.AppendLine(node.Type); - - var lastGenericArgument = node.GenericArguments.Count - 1; - - node.GenericArguments.Select(arg => Stringify(arg, depth + 1)) - .Each((arg, i) => - { - if (i < lastGenericArgument) - { - sb.AppendLine(arg); - } - else - { - sb.Append(arg); - } - }); - } - else - { - sb.Append(node.Type); - } + public static bool operator !=(TypeNode? left, TypeNode? right) + { + return !Equatable.Equals(left, right); + } - return sb.ToString(); - } - } + public bool SafeEquals(TypeNode other) + { + return ToString().Equals(other.ToString(), StringComparison.Ordinal); + } - /// - /// Converts string representation to TypeNode - /// - /// String representation of TypeNode - /// TypeNode - public static TypeNode FromString(string typeFullName) - { - var infos = typeFullName - .Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries) - .Select(row => - { - var depth = 0; + public bool Equals(TypeNode? other) + { + return Equatable.Equals(this, other); + } - while (row[depth] == '\t') - { - depth++; - } + public override bool Equals(object? obj) + { + return Equatable.Equals(this, obj); + } - var pair = row - .TrimStart('\t') - .Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + public override int GetHashCode() + { + return ToString().GetHashCode(StringComparison.Ordinal); + } - var assembly = pair[0]; - var type = pair[1]; + #endregion - return (assembly, type, depth); - }) - .ToArray(); + public override string ToString() + { + return Stringify(this, 0); - var index = 0; + static string Stringify(TypeNode node, int depth) + { + var sb = new StringBuilder(); - return ParseType(infos, ref index, 0); + sb.Append(new string('\t', depth)); + sb.Append(node.Assembly); + sb.Append(' '); - static TypeNode ParseType( - (string assembly, string type, int depth)[] infos, - ref int index, - int depth) + if (node.GenericArguments.Any()) { - var assembly = infos[index].assembly; - var type = infos[index].type; - var genericArguments = new List(); + sb.AppendLine(node.Type); + + var lastGenericArgument = node.GenericArguments.Count - 1; + + node.GenericArguments.Select(arg => Stringify(arg, depth + 1)) + .Each((arg, i) => + { + if (i < lastGenericArgument) + { + sb.AppendLine(arg); + } + else + { + sb.Append(arg); + } + }); + } + else + { + sb.Append(node.Type); + } - index++; + return sb.ToString(); + } + } + + public static TypeNode FromString(string typeFullName) + { + var infos = typeFullName + .Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries) + .Select(row => + { + var depth = 0; - while (index < infos.Length && infos[index].depth > depth) + while (row[depth] == '\t') { - genericArguments.Add(ParseType(infos, ref index, depth + 1)); + depth++; } - return new TypeNode(assembly, type, genericArguments); - } - } + var pair = row + .TrimStart('\t') + .Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - /// - /// Converts System.Type to TypeNode - /// - /// Type - /// TypeNode - public static TypeNode FromType(Type type) - { - return new TypeNode(type); - } + var assembly = pair[0]; + var type = pair[1]; - /// - /// Converts TypeNode to System.Type - /// - /// TypeNode - /// System.Type - public static Type ToType(TypeNode node) + return (assembly, type, depth); + }) + .ToArray(); + + var index = 0; + + return ParseType(infos, ref index, 0); + + static TypeNode ParseType( + (string assembly, string type, int depth)[] infos, + ref int index, + int depth) { - return BuildType(node); + var assembly = infos[index].assembly; + var type = infos[index].type; + var genericArguments = new List(); - static Type BuildType(TypeNode node) + index++; + + while (index < infos.Length && infos[index].depth > depth) { - if (TypeInfoStorage.TryGet(node.Assembly, node.IsArray ? node.Type.Trim('[', ']') : node.Type, out var type)) - { - return node.GenericArguments.Any() - ? type.MakeGenericType(node.GenericArguments.Select(BuildType).ToArray()) - : node.IsArray - ? type.MakeArrayType() - : type; - } + genericArguments.Add(ParseType(infos, ref index, depth + 1)); + } + + return new TypeNode(assembly, type, genericArguments); + } + } - throw new SecurityException($"Unable to find type {node.Type}"); + public static TypeNode FromType(Type type) + { + return new TypeNode(type); + } + + public static Type ToType(TypeNode node) + { + return BuildType(node); + + static Type BuildType(TypeNode node) + { + if (TypeInfoStorage.TryGet(node.Assembly, node.IsArray ? node.Type.Trim('[', ']') : node.Type, out var type)) + { + return node.GenericArguments.Any() + ? type.MakeGenericType(node.GenericArguments.Select(BuildType).ToArray()) + : node.IsArray + ? type.MakeArrayType() + : type; } + + throw new SecurityException($"Unable to find type {node.Type}"); } } } \ No newline at end of file diff --git a/src/Common/Basics/UnitOfWork/AsyncUnitOfWork.cs b/src/Common/Basics/UnitOfWork/AsyncUnitOfWork.cs new file mode 100644 index 00000000..752cd17e --- /dev/null +++ b/src/Common/Basics/UnitOfWork/AsyncUnitOfWork.cs @@ -0,0 +1,110 @@ +namespace SpaceEngineers.Core.Basics.UnitOfWork; + +using System; +using System.Threading; +using System.Threading.Tasks; +using SynchronizationPrimitives; + +public abstract class AsyncUnitOfWork : IAsyncUnitOfWork +{ + private readonly Exclusive _exclusive = new Exclusive(); + + public async Task ExecuteInTransaction( + TContext context, + Func producer, + bool saveChanges, + CancellationToken token) + { + using (await _exclusive.Run(token).ConfigureAwait(false)) + { + var (behavior, startError) = await StartTransactionUnsafe(context, token) + .TryAsync() + .Catch() + .Invoke(StartExceptionResult, token) + .ConfigureAwait(false); + + if (startError != null) + { + throw startError.Rethrow(); + } + + if (behavior is EnUnitOfWorkBehavior.DoNotRun) + { + return; + } + + Exception? executionError = default; + + if (behavior is not EnUnitOfWorkBehavior.SkipProducer) + { + executionError = await ExecuteProducerUnsafe(context, producer, token) + .TryAsync() + .Catch() + .Invoke(ExceptionResult, token) + .ConfigureAwait(false); + } + + var finishError = await FinishTransactionUnsafe(context, saveChanges, executionError, token) + .TryAsync() + .Catch() + .Invoke(ExceptionResult, token) + .ConfigureAwait(false); + + var error = executionError ?? finishError; + + if (error != null) + { + throw error.Rethrow(); + } + } + } + + protected virtual Task Start(TContext context, CancellationToken token) + { + return Task.FromResult(EnUnitOfWorkBehavior.Regular); + } + + protected virtual Task Rollback(TContext context, Exception? exception, CancellationToken token) + { + return Task.CompletedTask; + } + + protected virtual Task Commit(TContext context, CancellationToken token) + { + return Task.CompletedTask; + } + + private async Task<(EnUnitOfWorkBehavior, Exception?)> StartTransactionUnsafe(TContext context, CancellationToken token) + { + var behavior = await Start(context, token).ConfigureAwait(false); + return (behavior, null); + } + + private static async Task ExecuteProducerUnsafe(TContext context, Func producer, CancellationToken token) + { + await producer.Invoke(context, token).ConfigureAwait(false); + + return null; + } + + private async Task FinishTransactionUnsafe(TContext context, bool saveChanges, Exception? exception, CancellationToken token) + { + var finishOperation = saveChanges && exception == null + ? Commit(context, token) + : Rollback(context, exception, token); + + await finishOperation.ConfigureAwait(false); + + return null; + } + + private static (EnUnitOfWorkBehavior, Exception?) StartExceptionResult(Exception exception) + { + return (EnUnitOfWorkBehavior.DoNotRun, (Exception?)exception); + } + + private static Exception? ExceptionResult(Exception exception) + { + return exception; + } +} \ No newline at end of file diff --git a/src/Common/Basics/UnitOfWork/EnUnitOfWorkBehavior.cs b/src/Common/Basics/UnitOfWork/EnUnitOfWorkBehavior.cs new file mode 100644 index 00000000..fc0b0e14 --- /dev/null +++ b/src/Common/Basics/UnitOfWork/EnUnitOfWorkBehavior.cs @@ -0,0 +1,22 @@ +namespace SpaceEngineers.Core.Basics.UnitOfWork; + +public enum EnUnitOfWorkBehavior +{ + /// + /// Regular behavior + /// After successful transaction opening tries to execute producer and finish the unit of work gracefully + /// + Regular = 0, + + /// + /// Skip producer behavior + /// After successful transaction opening skips producer execution and tries finish the unit of work gracefully + /// + SkipProducer = 1, + + /// + /// Do not run behavior + /// The unit of work is considered as non started, producer execution and graceful finish will be skipped + /// + DoNotRun = 2 +} \ No newline at end of file diff --git a/src/Common/Basics/UnitOfWork/IAsyncUnitOfWork.cs b/src/Common/Basics/UnitOfWork/IAsyncUnitOfWork.cs new file mode 100644 index 00000000..4992ea20 --- /dev/null +++ b/src/Common/Basics/UnitOfWork/IAsyncUnitOfWork.cs @@ -0,0 +1,14 @@ +namespace SpaceEngineers.Core.Basics.UnitOfWork; + +using System; +using System.Threading; +using System.Threading.Tasks; + +public interface IAsyncUnitOfWork +{ + Task ExecuteInTransaction( + TContext context, + Func producer, + bool saveChanges, + CancellationToken token); +} \ No newline at end of file diff --git a/tests/Tests/Basics.Test/AsyncSynchronizationPrimitivesTest.cs b/tests/Tests/Basics.Test/AsyncSynchronizationPrimitivesTest.cs index f2320c4f..2ea70c0e 100644 --- a/tests/Tests/Basics.Test/AsyncSynchronizationPrimitivesTest.cs +++ b/tests/Tests/Basics.Test/AsyncSynchronizationPrimitivesTest.cs @@ -1,184 +1,178 @@ -namespace SpaceEngineers.Core.Basics.Test +namespace SpaceEngineers.Core.Basics.Test; + +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using SynchronizationPrimitives; +using Xunit; +using Xunit.Abstractions; + +public class AsyncSynchronizationPrimitivesTest : BasicsTestBase { - using System; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using Primitives; - using Xunit; - using Xunit.Abstractions; - - /// - /// Test for async synchronization primitives - /// - public class AsyncSynchronizationPrimitivesTest : BasicsTestBase - { - private static readonly TimeSpan TestTimeout = TimeSpan.FromSeconds(1); + private static readonly TimeSpan TestTimeout = TimeSpan.FromSeconds(1); - /// .cctor - /// ITestOutputHelper - public AsyncSynchronizationPrimitivesTest(ITestOutputHelper output) - : base(output) - { - } + public AsyncSynchronizationPrimitivesTest(ITestOutputHelper output) + : base(output) + { + } - [Fact] - internal async Task TaskCancellationCompletionSourceTest() + [Fact] + internal async Task TaskCancellationCompletionSourceTest() + { + try { - try - { - using (var cts = new CancellationTokenSource()) - using (var tcs = new TaskCancellationCompletionSource(cts.Token)) - { - cts.Cancel(); - await tcs.Task.ConfigureAwait(false); - } - } - catch (OperationCanceledException) + using (var cts = new CancellationTokenSource()) + using (var tcs = new TaskCancellationCompletionSource(cts.Token)) { - return; + cts.Cancel(); + await tcs.Task.ConfigureAwait(false); } - - Assert.True(false); } + catch (OperationCanceledException) + { + return; + } + + Assert.True(false); + } - [Fact] - internal async Task WaitAsyncCancellationTest() + [Fact] + internal async Task WaitAsyncCancellationTest() + { + try { - try - { - using (var cts = new CancellationTokenSource()) - { - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var task = Basics.TaskExtensions.WaitAsync(tcs.Task, cts.Token); - - cts.Cancel(); - await task.ConfigureAwait(false); - } - } - catch (OperationCanceledException) + using (var cts = new CancellationTokenSource()) { - return; - } + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var task = Basics.TaskExtensions.WaitAsync(tcs.Task, cts.Token); - Assert.True(false); + cts.Cancel(); + await task.ConfigureAwait(false); + } } - - [Theory] - [InlineData(true)] - [InlineData(false)] - internal async Task AsyncManualResetEventTest(bool isSet) + catch (OperationCanceledException) { - var manualResetEvent = new AsyncManualResetEvent(isSet); + return; + } - var waitTask1 = manualResetEvent.WaitAsync(); - var waitTask2 = manualResetEvent.WaitAsync(); - var waitTask3 = manualResetEvent.WaitAsync(); + Assert.True(false); + } - var waitAll = Task.WhenAll(waitTask1, waitTask2, waitTask3); + [Theory] + [InlineData(true)] + [InlineData(false)] + internal async Task AsyncManualResetEventTest(bool isSet) + { + var manualResetEvent = new AsyncManualResetEvent(isSet); - Assert.True(isSet ? waitAll.IsCompleted : !waitAll.IsCompleted); + var waitTask1 = manualResetEvent.WaitAsync(); + var waitTask2 = manualResetEvent.WaitAsync(); + var waitTask3 = manualResetEvent.WaitAsync(); - if (!isSet) - { - manualResetEvent.Set(); - } + var waitAll = Task.WhenAll(waitTask1, waitTask2, waitTask3); - var actual = await Task.WhenAny(waitAll, Task.Delay(TestTimeout)).ConfigureAwait(false); + Assert.True(isSet ? waitAll.IsCompleted : !waitAll.IsCompleted); - Assert.Equal(waitAll, actual); + if (!isSet) + { + manualResetEvent.Set(); + } - await actual.ConfigureAwait(false); + var actual = await Task.WhenAny(waitAll, Task.Delay(TestTimeout)).ConfigureAwait(false); - Assert.True(manualResetEvent.WaitAsync().IsCompleted); + Assert.Equal(waitAll, actual); - manualResetEvent.Reset(); + await actual.ConfigureAwait(false); - var waitTaskAfterReset1 = manualResetEvent.WaitAsync(); - var waitTaskAfterReset2 = manualResetEvent.WaitAsync(); - var waitTaskAfterReset3 = manualResetEvent.WaitAsync(); + Assert.True(manualResetEvent.WaitAsync().IsCompleted); - Assert.False(waitTaskAfterReset1.IsCompleted); - Assert.False(waitTaskAfterReset2.IsCompleted); - Assert.False(waitTaskAfterReset3.IsCompleted); + manualResetEvent.Reset(); - var waitAllAfterReset = Task.WhenAll(waitTask1, waitTask2, waitTask3); + var waitTaskAfterReset1 = manualResetEvent.WaitAsync(); + var waitTaskAfterReset2 = manualResetEvent.WaitAsync(); + var waitTaskAfterReset3 = manualResetEvent.WaitAsync(); - manualResetEvent.Set(); + Assert.False(waitTaskAfterReset1.IsCompleted); + Assert.False(waitTaskAfterReset2.IsCompleted); + Assert.False(waitTaskAfterReset3.IsCompleted); - var actualAfterReset = await Task.WhenAny(waitAllAfterReset, Task.Delay(TestTimeout)).ConfigureAwait(false); + var waitAllAfterReset = Task.WhenAll(waitTask1, waitTask2, waitTask3); - Assert.Equal(waitAllAfterReset, actualAfterReset); + manualResetEvent.Set(); - await actualAfterReset.ConfigureAwait(false); - } + var actualAfterReset = await Task.WhenAny(waitAllAfterReset, Task.Delay(TestTimeout)).ConfigureAwait(false); - [Theory] - [InlineData(true)] - [InlineData(false)] - internal async Task AsyncAutoResetEventTest(bool isSet) - { - var autoResetEvent = new AsyncAutoResetEvent(isSet); + Assert.Equal(waitAllAfterReset, actualAfterReset); - var waitTask1 = autoResetEvent.WaitAsync(); - var waitTask2 = autoResetEvent.WaitAsync(); - var waitTask3 = autoResetEvent.WaitAsync(); + await actualAfterReset.ConfigureAwait(false); + } - Assert.True(isSet ? waitTask1.IsCompleted : !waitTask1.IsCompleted); + [Theory] + [InlineData(true)] + [InlineData(false)] + internal async Task AsyncAutoResetEventTest(bool isSet) + { + var autoResetEvent = new AsyncAutoResetEvent(isSet); - var waitAll = Task.WhenAll(waitTask1, waitTask2, waitTask3); - var timeout = Task.Delay(TestTimeout); - var actual = await Task.WhenAny(waitAll, timeout).ConfigureAwait(false); + var waitTask1 = autoResetEvent.WaitAsync(); + var waitTask2 = autoResetEvent.WaitAsync(); + var waitTask3 = autoResetEvent.WaitAsync(); - Assert.Equal(timeout, actual); + Assert.True(isSet ? waitTask1.IsCompleted : !waitTask1.IsCompleted); - await actual.ConfigureAwait(false); + var waitAll = Task.WhenAll(waitTask1, waitTask2, waitTask3); + var timeout = Task.Delay(TestTimeout); + var actual = await Task.WhenAny(waitAll, timeout).ConfigureAwait(false); - var range = isSet ? 2 : 3; - Enumerable.Range(0, range).Each(_ => autoResetEvent.Set()); + Assert.Equal(timeout, actual); - timeout = Task.Delay(TestTimeout); - actual = await Task.WhenAny(waitAll, timeout).ConfigureAwait(false); + await actual.ConfigureAwait(false); - Assert.Equal(waitAll, actual); + var range = isSet ? 2 : 3; + Enumerable.Range(0, range).Each(_ => autoResetEvent.Set()); - await actual.ConfigureAwait(false); + timeout = Task.Delay(TestTimeout); + actual = await Task.WhenAny(waitAll, timeout).ConfigureAwait(false); - Assert.False(autoResetEvent.WaitAsync().IsCompleted); - } + Assert.Equal(waitAll, actual); - [Theory] - [InlineData(0)] - [InlineData(3)] - internal async Task AsyncCountdownEventTest(int initialCount) - { - var countdownEvent = new AsyncCountdownEvent(initialCount); + await actual.ConfigureAwait(false); - Assert.True(initialCount <= 0 - ? countdownEvent.WaitAsync().IsCompleted - : !countdownEvent.WaitAsync().IsCompleted); + Assert.False(autoResetEvent.WaitAsync().IsCompleted); + } - if (initialCount <= 0) - { - Enumerable - .Range(0, 3) - .Each(_ => countdownEvent.Increment()); - } + [Theory] + [InlineData(0)] + [InlineData(3)] + internal async Task AsyncCountdownEventTest(int initialCount) + { + var countdownEvent = new AsyncCountdownEvent(initialCount); - Assert.Equal(3, countdownEvent.Read()); - Assert.False(countdownEvent.WaitAsync().IsCompleted); + Assert.True(initialCount <= 0 + ? countdownEvent.WaitAsync().IsCompleted + : !countdownEvent.WaitAsync().IsCompleted); + if (initialCount <= 0) + { Enumerable - .Range(0, 3 - 1) - .Each(_ => - { - countdownEvent.Decrement(); - Assert.False(countdownEvent.WaitAsync().IsCompleted); - }); - - Assert.Equal(0, countdownEvent.Decrement()); - Assert.True(countdownEvent.WaitAsync().IsCompleted); - await countdownEvent.WaitAsync().ConfigureAwait(false); + .Range(0, 3) + .Each(_ => countdownEvent.Increment()); } + + Assert.Equal(3, countdownEvent.Read()); + Assert.False(countdownEvent.WaitAsync().IsCompleted); + + Enumerable + .Range(0, 3 - 1) + .Each(_ => + { + countdownEvent.Decrement(); + Assert.False(countdownEvent.WaitAsync().IsCompleted); + }); + + Assert.Equal(0, countdownEvent.Decrement()); + Assert.True(countdownEvent.WaitAsync().IsCompleted); + await countdownEvent.WaitAsync().ConfigureAwait(false); } } \ No newline at end of file diff --git a/tests/Tests/Basics.Test/AsyncUnitOfWorkTest.cs b/tests/Tests/Basics.Test/AsyncUnitOfWorkTest.cs index 90c80230..cd4d58a8 100644 --- a/tests/Tests/Basics.Test/AsyncUnitOfWorkTest.cs +++ b/tests/Tests/Basics.Test/AsyncUnitOfWorkTest.cs @@ -1,192 +1,185 @@ -namespace SpaceEngineers.Core.Basics.Test +namespace SpaceEngineers.Core.Basics.Test; + +using System; +using System.Threading; +using System.Threading.Tasks; +using UnitOfWork; +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; + +public class AsyncUnitOfWorkTest : BasicsTestBase { - using System; - using System.Threading; - using System.Threading.Tasks; - using Enumerations; - using Primitives; - using Xunit; - using Xunit.Abstractions; - using Xunit.Sdk; - - /// - /// AsyncUnitOfWorkTest - /// - public class AsyncUnitOfWorkTest : BasicsTestBase + private static readonly Func EmptyProducer = (_, _) => Task.CompletedTask; + private static readonly Func ErrorProducer = (_, _) => throw TestExtensions.TrueException(); + + public AsyncUnitOfWorkTest(ITestOutputHelper output) + : base(output) { - private static readonly Func EmptyProducer = (_, _) => Task.CompletedTask; - private static readonly Func ErrorProducer = (_, _) => throw TestExtensions.TrueException(); + } - /// .ctor - /// ITestOutputHelper - public AsyncUnitOfWorkTest(ITestOutputHelper output) - : base(output) - { - } + [Fact] + internal void CommitTest() + { + var unitOfWork = new TestAsyncUnitOfWork(EnUnitOfWorkBehavior.Regular); + ExecuteInTransaction(unitOfWork, true, EmptyProducer); - [Fact] - internal void CommitTest() - { - var unitOfWork = new TestAsyncUnitOfWork(EnUnitOfWorkBehavior.Regular); - ExecuteInTransaction(unitOfWork, true, EmptyProducer); + Assert.True(unitOfWork.Started); + Assert.True(unitOfWork.Committed); + Assert.False(unitOfWork.RolledBack); + Assert.False(unitOfWork.RolledBackByException); + } - Assert.True(unitOfWork.Started); - Assert.True(unitOfWork.Committed); - Assert.False(unitOfWork.RolledBack); - Assert.False(unitOfWork.RolledBackByException); - } + [Fact] + internal void RollbackTest() + { + var unitOfWork = new TestAsyncUnitOfWork(EnUnitOfWorkBehavior.Regular); + ExecuteInTransaction(unitOfWork, false, EmptyProducer); - [Fact] - internal void RollbackTest() - { - var unitOfWork = new TestAsyncUnitOfWork(EnUnitOfWorkBehavior.Regular); - ExecuteInTransaction(unitOfWork, false, EmptyProducer); + Assert.True(unitOfWork.Started); + Assert.False(unitOfWork.Committed); + Assert.True(unitOfWork.RolledBack); + Assert.False(unitOfWork.RolledBackByException); + } - Assert.True(unitOfWork.Started); - Assert.False(unitOfWork.Committed); - Assert.True(unitOfWork.RolledBack); - Assert.False(unitOfWork.RolledBackByException); - } + [Fact] + internal void RollbackByExceptionTest() + { + var unitOfWork = new TestAsyncUnitOfWork(EnUnitOfWorkBehavior.Regular); + Assert.Throws(() => ExecuteInTransaction(unitOfWork, true, ErrorProducer)); - [Fact] - internal void RollbackByExceptionTest() - { - var unitOfWork = new TestAsyncUnitOfWork(EnUnitOfWorkBehavior.Regular); - Assert.Throws(() => ExecuteInTransaction(unitOfWork, true, ErrorProducer)); + Assert.True(unitOfWork.Started); + Assert.False(unitOfWork.Committed); + Assert.True(unitOfWork.RolledBack); + Assert.True(unitOfWork.RolledBackByException); + } - Assert.True(unitOfWork.Started); - Assert.False(unitOfWork.Committed); - Assert.True(unitOfWork.RolledBack); - Assert.True(unitOfWork.RolledBackByException); - } + [Fact] + internal void NestedStartsTest() + { + var unitOfWork = new TestAsyncUnitOfWork(EnUnitOfWorkBehavior.Regular); + Assert.Throws(() => ExecuteInTransaction(unitOfWork, true, NestedProducer)); - [Fact] - internal void NestedStartsTest() - { - var unitOfWork = new TestAsyncUnitOfWork(EnUnitOfWorkBehavior.Regular); - Assert.Throws(() => ExecuteInTransaction(unitOfWork, true, NestedProducer)); - - Assert.True(unitOfWork.Started); - Assert.False(unitOfWork.Committed); - Assert.True(unitOfWork.RolledBack); - Assert.True(unitOfWork.RolledBackByException); - - Task NestedProducer(object context, CancellationToken token) - { - ExecuteInTransaction(unitOfWork, true, EmptyProducer); - return Task.CompletedTask; - } - } + Assert.True(unitOfWork.Started); + Assert.False(unitOfWork.Committed); + Assert.True(unitOfWork.RolledBack); + Assert.True(unitOfWork.RolledBackByException); - [Fact] - internal void DoNotRunBehaviorTest() + Task NestedProducer(object context, CancellationToken token) { - var unitOfWork = new TestAsyncUnitOfWork(EnUnitOfWorkBehavior.DoNotRun); ExecuteInTransaction(unitOfWork, true, EmptyProducer); - - Assert.False(unitOfWork.Started); - Assert.False(unitOfWork.Committed); - Assert.False(unitOfWork.RolledBack); - Assert.False(unitOfWork.RolledBackByException); + return Task.CompletedTask; } + } - [Fact] - internal void SkipProducerBehaviorCommitTest() - { - var unitOfWork = new TestAsyncUnitOfWork(EnUnitOfWorkBehavior.SkipProducer); + [Fact] + internal void DoNotRunBehaviorTest() + { + var unitOfWork = new TestAsyncUnitOfWork(EnUnitOfWorkBehavior.DoNotRun); + ExecuteInTransaction(unitOfWork, true, EmptyProducer); - var producerExecuted = false; + Assert.False(unitOfWork.Started); + Assert.False(unitOfWork.Committed); + Assert.False(unitOfWork.RolledBack); + Assert.False(unitOfWork.RolledBackByException); + } + + [Fact] + internal void SkipProducerBehaviorCommitTest() + { + var unitOfWork = new TestAsyncUnitOfWork(EnUnitOfWorkBehavior.SkipProducer); - ExecuteInTransaction(unitOfWork, true, TrackableProducer); + var producerExecuted = false; - Assert.True(unitOfWork.Started); - Assert.True(unitOfWork.Committed); - Assert.False(unitOfWork.RolledBack); - Assert.False(unitOfWork.RolledBackByException); - Assert.False(producerExecuted); + ExecuteInTransaction(unitOfWork, true, TrackableProducer); - Task TrackableProducer(object state, CancellationToken token) - { - producerExecuted = true; - return Task.CompletedTask; - } - } + Assert.True(unitOfWork.Started); + Assert.True(unitOfWork.Committed); + Assert.False(unitOfWork.RolledBack); + Assert.False(unitOfWork.RolledBackByException); + Assert.False(producerExecuted); - [Fact] - internal void SkipProducerBehaviorRollbackTest() + Task TrackableProducer(object state, CancellationToken token) { - var unitOfWork = new TestAsyncUnitOfWork(EnUnitOfWorkBehavior.SkipProducer); + producerExecuted = true; + return Task.CompletedTask; + } + } - var producerExecuted = false; + [Fact] + internal void SkipProducerBehaviorRollbackTest() + { + var unitOfWork = new TestAsyncUnitOfWork(EnUnitOfWorkBehavior.SkipProducer); - ExecuteInTransaction(unitOfWork, false, TrackableProducer); + var producerExecuted = false; - Assert.True(unitOfWork.Started); - Assert.False(unitOfWork.Committed); - Assert.True(unitOfWork.RolledBack); - Assert.False(unitOfWork.RolledBackByException); - Assert.False(producerExecuted); + ExecuteInTransaction(unitOfWork, false, TrackableProducer); - Task TrackableProducer(object state, CancellationToken token) - { - producerExecuted = true; - return Task.CompletedTask; - } - } + Assert.True(unitOfWork.Started); + Assert.False(unitOfWork.Committed); + Assert.True(unitOfWork.RolledBack); + Assert.False(unitOfWork.RolledBackByException); + Assert.False(producerExecuted); - private static void ExecuteInTransaction( - IAsyncUnitOfWork unitOfWork, - bool saveChanges, - Func producer) + Task TrackableProducer(object state, CancellationToken token) { - ExecutionExtensions - .Try(ExecuteInTransaction, (unitOfWork, saveChanges, producer)) - .Catch(ex => throw ex.RealException()) - .Invoke(); + producerExecuted = true; + return Task.CompletedTask; } + } - private static void ExecuteInTransaction( - (IAsyncUnitOfWork, bool, Func) state) - { - var (unitOfWork, saveChanges, producer) = state; - unitOfWork.ExecuteInTransaction(new object(), producer, saveChanges, CancellationToken.None).Wait(); - } + private static void ExecuteInTransaction( + IAsyncUnitOfWork unitOfWork, + bool saveChanges, + Func producer) + { + ExecutionExtensions + .Try(ExecuteInTransaction, (unitOfWork, saveChanges, producer)) + .Catch(ex => throw ex.RealException()) + .Invoke(); + } - private class TestAsyncUnitOfWork : AsyncUnitOfWork - { - private readonly EnUnitOfWorkBehavior _behavior; + private static void ExecuteInTransaction( + (IAsyncUnitOfWork, bool, Func) state) + { + var (unitOfWork, saveChanges, producer) = state; + unitOfWork.ExecuteInTransaction(new object(), producer, saveChanges, CancellationToken.None).Wait(); + } - public TestAsyncUnitOfWork(EnUnitOfWorkBehavior behavior) - { - _behavior = behavior; - } + private class TestAsyncUnitOfWork : AsyncUnitOfWork + { + private readonly EnUnitOfWorkBehavior _behavior; + + public TestAsyncUnitOfWork(EnUnitOfWorkBehavior behavior) + { + _behavior = behavior; + } - internal bool Started { get; private set; } + internal bool Started { get; private set; } - internal bool Committed { get; private set; } + internal bool Committed { get; private set; } - internal bool RolledBack { get; private set; } + internal bool RolledBack { get; private set; } - internal bool RolledBackByException { get; private set; } + internal bool RolledBackByException { get; private set; } - protected override Task Start(object context, CancellationToken token) - { - Started = _behavior != EnUnitOfWorkBehavior.DoNotRun; - return Task.FromResult(_behavior); - } + protected override Task Start(object context, CancellationToken token) + { + Started = _behavior != EnUnitOfWorkBehavior.DoNotRun; + return Task.FromResult(_behavior); + } - protected override Task Commit(object context, CancellationToken token) - { - Committed = true; - return Task.CompletedTask; - } + protected override Task Commit(object context, CancellationToken token) + { + Committed = true; + return Task.CompletedTask; + } - protected override Task Rollback(object context, Exception? exception, CancellationToken token) - { - RolledBack = true; - RolledBackByException = exception != null; - return Task.CompletedTask; - } + protected override Task Rollback(object context, Exception? exception, CancellationToken token) + { + RolledBack = true; + RolledBackByException = exception != null; + return Task.CompletedTask; } } } \ No newline at end of file diff --git a/tests/Tests/Basics.Test/Basics.Test.csproj b/tests/Tests/Basics.Test/Basics.Test.csproj index 778d9dfa..267b6f35 100644 --- a/tests/Tests/Basics.Test/Basics.Test.csproj +++ b/tests/Tests/Basics.Test/Basics.Test.csproj @@ -4,70 +4,16 @@ net8.0 SpaceEngineers.Core.Basics.Test SpaceEngineers.Core.Basics.Test - false - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + + + - - - BasicsTestBase.cs - - - BasicsTestBase.cs - - - BasicsTestBase.cs - - - BasicsTestBase.cs - - - TypeExtensionsTest.cs - - - BasicsTestBase.cs - - - BasicsTestBase.cs - - - BasicsTestBase.cs - - - BasicsTestBase.cs - - - BasicsTestBase.cs - - - BasicsTestBase.cs - - - BasicsTestBase.cs - - - BasicsTestBase.cs - - - TypeExtensionsTest.cs - - \ No newline at end of file diff --git a/tests/Tests/Basics.Test/BasicsTestBase.cs b/tests/Tests/Basics.Test/BasicsTestBase.cs index 0e7357ef..17c46870 100644 --- a/tests/Tests/Basics.Test/BasicsTestBase.cs +++ b/tests/Tests/Basics.Test/BasicsTestBase.cs @@ -1,22 +1,13 @@ -namespace SpaceEngineers.Core.Basics.Test -{ - using Xunit.Abstractions; +namespace SpaceEngineers.Core.Basics.Test; - /// - /// Unit test base class - /// - public abstract class BasicsTestBase - { - /// .ctor - /// ITestOutputHelper - protected BasicsTestBase(ITestOutputHelper output) - { - Output = output; - } +using Xunit.Abstractions; - /// - /// ITestOutputHelper - /// - protected ITestOutputHelper Output { get; } +public abstract class BasicsTestBase +{ + protected BasicsTestBase(ITestOutputHelper output) + { + Output = output; } + + protected ITestOutputHelper Output { get; } } \ No newline at end of file diff --git a/tests/Tests/Basics.Test/DeepCopy/TestEnum.cs b/tests/Tests/Basics.Test/DeepCopy/TestEnum.cs index 78eefc58..4b71cf01 100644 --- a/tests/Tests/Basics.Test/DeepCopy/TestEnum.cs +++ b/tests/Tests/Basics.Test/DeepCopy/TestEnum.cs @@ -1,15 +1,14 @@ -namespace SpaceEngineers.Core.Basics.Test.DeepCopy +namespace SpaceEngineers.Core.Basics.Test.DeepCopy; + +internal enum TestEnum { - internal enum TestEnum - { - /// - /// Default - /// - Default = 0, + /// + /// Default + /// + Default = 0, - /// - /// Value - /// - Value = 1 - } + /// + /// Value + /// + Value = 1 } \ No newline at end of file diff --git a/tests/Tests/Basics.Test/DeepCopy/TestReferenceWithSystemTypes.cs b/tests/Tests/Basics.Test/DeepCopy/TestReferenceWithSystemTypes.cs index 50e1a650..b77561bd 100644 --- a/tests/Tests/Basics.Test/DeepCopy/TestReferenceWithSystemTypes.cs +++ b/tests/Tests/Basics.Test/DeepCopy/TestReferenceWithSystemTypes.cs @@ -1,39 +1,31 @@ -namespace SpaceEngineers.Core.Basics.Test.DeepCopy -{ - using System; - using System.Collections.Generic; +namespace SpaceEngineers.Core.Basics.Test.DeepCopy; - /// - /// TestReferenceWithSystemTypes - /// - [Serializable] - public class TestReferenceWithSystemTypes : TestReferenceWithoutSystemTypes - { - /* - * System.Type - */ - internal Type? Type { get; set; } +using System; +using System.Collections.Generic; - internal Array? TypeArray { get; set; } +[Serializable] +public class TestReferenceWithSystemTypes : TestReferenceWithoutSystemTypes +{ + /* + * System.Type + */ + internal Type? Type { get; set; } - internal ICollection? TypeCollection { get; set; } + internal Array? TypeArray { get; set; } - /// - /// Create TestReferenceWithSystemTypes instance - /// - /// TestReferenceWithSystemTypes instance - public static TestReferenceWithSystemTypes Create() + internal ICollection? TypeCollection { get; set; } + + public static TestReferenceWithSystemTypes Create() + { + var instance = new TestReferenceWithSystemTypes { - var instance = new TestReferenceWithSystemTypes - { - Type = typeof(TestReferenceWithoutSystemTypes), - TypeArray = new[] { typeof(TestReferenceWithoutSystemTypes), typeof(string), typeof(int) }, - TypeCollection = new List { typeof(TestReferenceWithoutSystemTypes), typeof(string), typeof(int) } - }; + Type = typeof(TestReferenceWithoutSystemTypes), + TypeArray = new[] { typeof(TestReferenceWithoutSystemTypes), typeof(string), typeof(int) }, + TypeCollection = new List { typeof(TestReferenceWithoutSystemTypes), typeof(string), typeof(int) } + }; - CreateOrInit(instance); + CreateOrInit(instance); - return instance; - } + return instance; } } \ No newline at end of file diff --git a/tests/Tests/Basics.Test/DeepCopy/TestReferenceWithoutSystemTypes.cs b/tests/Tests/Basics.Test/DeepCopy/TestReferenceWithoutSystemTypes.cs index d92b3f3e..4381f2d4 100644 --- a/tests/Tests/Basics.Test/DeepCopy/TestReferenceWithoutSystemTypes.cs +++ b/tests/Tests/Basics.Test/DeepCopy/TestReferenceWithoutSystemTypes.cs @@ -1,77 +1,66 @@ -namespace SpaceEngineers.Core.Basics.Test.DeepCopy -{ - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - - /// - /// TestReferenceWithoutSystemTypes - /// - [SuppressMessage("Analysis", "CA5362", Justification = "For test reasons")] - [Serializable] - public class TestReferenceWithoutSystemTypes - { - /* - * String - */ - internal string? String { get; set; } - - /* - * ValueType - */ - internal int Int { get; set; } +namespace SpaceEngineers.Core.Basics.Test.DeepCopy; - internal TestEnum TestEnum { get; set; } +using System; +using System.Collections.Generic; - internal Array? ValueTypeArray { get; set; } +[Serializable] +public class TestReferenceWithoutSystemTypes +{ + /* + * String + */ + internal string? String { get; set; } - internal ICollection? ValueTypeCollection { get; set; } + /* + * ValueType + */ + internal int Int { get; set; } - /* - * ReferenceType - */ - internal Array? ReferenceTypeArray { get; set; } + internal TestEnum TestEnum { get; set; } - internal ICollection? ReferenceTypeCollection { get; set; } + internal Array? ValueTypeArray { get; set; } - internal TestReferenceWithoutSystemTypes? CyclicReference { get; set; } + internal ICollection? ValueTypeCollection { get; set; } - internal static TestReferenceWithoutSystemTypes? StaticCyclicReference { get; set; } + /* + * ReferenceType + */ + internal Array? ReferenceTypeArray { get; set; } - /* - * Nullable - */ - internal int? NullableInt { get; } + internal ICollection? ReferenceTypeCollection { get; set; } - internal TestReferenceWithoutSystemTypes? NullableReference { get; } + internal TestReferenceWithoutSystemTypes? CyclicReference { get; set; } - internal Array? ArrayOfNulls { get; set; } + internal static TestReferenceWithoutSystemTypes? StaticCyclicReference { get; set; } - internal ICollection? CollectionOfNulls { get; set; } + /* + * Nullable + */ + internal int? NullableInt { get; } - /// - /// CreateOrInit TestReferenceWithoutSystemTypes instance - /// - /// TestReferenceWithoutSystemTypes (optional) - /// Initialized TestReferenceWithoutSystemTypes instance - public static TestReferenceWithoutSystemTypes CreateOrInit(TestReferenceWithoutSystemTypes? instance = null) - { - instance ??= new TestReferenceWithoutSystemTypes(); + internal TestReferenceWithoutSystemTypes? NullableReference { get; } - instance.String = "PublicString123#'!"; - instance.Int = 100; - instance.TestEnum = TestEnum.Value; - instance.ValueTypeArray = new[] { 1, 2, 3, 4, 5 }; - instance.ValueTypeCollection = new List { 1, 2, 3, 4, 5 }; - instance.ReferenceTypeArray = new[] { new object(), new object(), new object() }; - instance.ReferenceTypeCollection = new[] { new object(), new object(), new object() }; - instance.ArrayOfNulls = new object?[] { null, null, null }; - instance.CollectionOfNulls = new List { null, null, null }; + internal Array? ArrayOfNulls { get; set; } - instance.CyclicReference = instance; - StaticCyclicReference = instance; + internal ICollection? CollectionOfNulls { get; set; } - return instance; - } + public static TestReferenceWithoutSystemTypes CreateOrInit(TestReferenceWithoutSystemTypes? instance = null) + { + instance ??= new TestReferenceWithoutSystemTypes(); + + instance.String = "PublicString123#'!"; + instance.Int = 100; + instance.TestEnum = TestEnum.Value; + instance.ValueTypeArray = new[] { 1, 2, 3, 4, 5 }; + instance.ValueTypeCollection = new List { 1, 2, 3, 4, 5 }; + instance.ReferenceTypeArray = new[] { new object(), new object(), new object() }; + instance.ReferenceTypeCollection = new[] { new object(), new object(), new object() }; + instance.ArrayOfNulls = new object?[] { null, null, null }; + instance.CollectionOfNulls = new List { null, null, null }; + + instance.CyclicReference = instance; + StaticCyclicReference = instance; + + return instance; } } \ No newline at end of file diff --git a/tests/Tests/Basics.Test/DeferredQueueTest.cs b/tests/Tests/Basics.Test/DeferredQueueTest.cs index 7921c6cc..97bdade5 100644 --- a/tests/Tests/Basics.Test/DeferredQueueTest.cs +++ b/tests/Tests/Basics.Test/DeferredQueueTest.cs @@ -1,269 +1,259 @@ -namespace SpaceEngineers.Core.Basics.Test +namespace SpaceEngineers.Core.Basics.Test; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Heap; +using Queue; +using Xunit; +using Xunit.Abstractions; + +public class DeferredQueueTest : BasicsTestBase { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using Enumerations; - using Primitives; - using Xunit; - using Xunit.Abstractions; - - /// - /// DeferredQueue primitive test - /// - public class DeferredQueueTest : BasicsTestBase + public DeferredQueueTest(ITestOutputHelper output) + : base(output) { - /// .cctor - /// ITestOutputHelper - public DeferredQueueTest(ITestOutputHelper output) - : base(output) - { - } - - /// - /// OnRootNodeChangedTestData - /// - /// TestData - public static IEnumerable DeferredQueueTestData() - { - var emptyQueue = new DeferredQueue(new BinaryHeap>(EnOrderingDirection.Asc), PrioritySelector); - - yield return new object[] { emptyQueue }; + } - static DateTime PrioritySelector(Entry entry) => entry.Planned; - } + public static IEnumerable DeferredQueueTestData() + { + var emptyQueue = new DeferredQueue(new BinaryHeap>(EnOrderingDirection.Asc), PrioritySelector); - [Theory(Timeout = 60_000)] - [MemberData(nameof(DeferredQueueTestData))] - internal async Task OnRootNodeChangedTest(DeferredQueue queue) - { - Assert.Throws(queue.Dequeue); - Assert.Throws(() => queue.TryDequeue(out _)); - Assert.Throws(queue.Peek); - Assert.Throws(() => queue.TryPeek(out _)); + yield return new object[] { emptyQueue }; - Assert.True(queue.IsEmpty); + static DateTime PrioritySelector(Entry entry) => entry.Planned; + } - var step = TimeSpan.FromMilliseconds(100); - var startFrom = DateTime.UtcNow.Add(step); + [Theory(Timeout = 60_000)] + [MemberData(nameof(DeferredQueueTestData))] + internal async Task OnRootNodeChangedTest(DeferredQueue queue) + { + Assert.Throws(queue.Dequeue); + Assert.Throws(() => queue.TryDequeue(out _)); + Assert.Throws(queue.Peek); + Assert.Throws(() => queue.TryPeek(out _)); - queue.Enqueue(new Entry(0, startFrom)); - queue.Enqueue(new Entry(2, startFrom.Add(2 * step))); - queue.Enqueue(new Entry(4, startFrom.Add(4 * step))); + Assert.True(queue.IsEmpty); - var entries = new List(); - var started = DateTime.UtcNow; - Output.WriteLine($"Started at: {started:O}"); - using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3))) - { - var backgroundPublisher = Task.Run(async () => - { - var corrected = startFrom.Add(step / 2) - DateTime.UtcNow; + var step = TimeSpan.FromMilliseconds(100); + var startFrom = DateTime.UtcNow.Add(step); - await Task.Delay(corrected, cts.Token).ConfigureAwait(false); - queue.Enqueue(new Entry(1, startFrom.Add(1 * step))); + queue.Enqueue(new Entry(0, startFrom)); + queue.Enqueue(new Entry(2, startFrom.Add(2 * step))); + queue.Enqueue(new Entry(4, startFrom.Add(4 * step))); - await Task.Delay(step, cts.Token).ConfigureAwait(false); - queue.Enqueue(new Entry(3, startFrom.Add(3 * step))); + var entries = new List(); + var started = DateTime.UtcNow; + Output.WriteLine($"Started at: {started:O}"); + using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3))) + { + var backgroundPublisher = Task.Run(async () => + { + var corrected = startFrom.Add(step / 2) - DateTime.UtcNow; - await Task.Delay(step, cts.Token).ConfigureAwait(false); - queue.Enqueue(new Entry(5, startFrom.Add(5 * step))); - }, - cts.Token); + await Task.Delay(corrected, cts.Token).ConfigureAwait(false); + queue.Enqueue(new Entry(1, startFrom.Add(1 * step))); - var deferredDeliveryOperation = queue.Run(Callback, cts.Token); + await Task.Delay(step, cts.Token).ConfigureAwait(false); + queue.Enqueue(new Entry(3, startFrom.Add(3 * step))); - await backgroundPublisher.ConfigureAwait(false); + await Task.Delay(step, cts.Token).ConfigureAwait(false); + queue.Enqueue(new Entry(5, startFrom.Add(5 * step))); + }, + cts.Token); - try - { - await deferredDeliveryOperation.ConfigureAwait(false); - } - catch (OperationCanceledException) - { - } - } + var deferredDeliveryOperation = queue.Run(Callback, cts.Token); - entries.Each(entry => Output.WriteLine(entry.ToString())); - Assert.True(queue.IsEmpty); - Assert.True(started <= entries.First().Planned); + await backgroundPublisher.ConfigureAwait(false); - var deltas = new List(); - _ = entries.Aggregate((prev, next) => + try { - var delta = next.Actual - prev.Actual; - deltas.Add(delta); - return next; - }); - - deltas.Each(delta => Output.WriteLine(delta.ToString())); - Assert.Equal(Enumerable.Range(0, 6).ToArray(), entries.Select(entry => entry.Index).ToArray()); - - Task Callback(Entry entry, CancellationToken token) + await deferredDeliveryOperation.ConfigureAwait(false); + } + catch (OperationCanceledException) { - entry.Actual = DateTime.UtcNow; - entries.Add(entry); - - return Task.CompletedTask; } } - [Theory(Timeout = 60_000)] - [MemberData(nameof(DeferredQueueTestData))] - internal async Task IntensiveReadWriteTest(DeferredQueue queue) + entries.Each(entry => Output.WriteLine(entry.ToString())); + Assert.True(queue.IsEmpty); + Assert.True(started <= entries.First().Planned); + + var deltas = new List(); + _ = entries.Aggregate((prev, next) => { - Assert.True(queue.IsEmpty); + var delta = next.Actual - prev.Actual; + deltas.Add(delta); + return next; + }); - var publishersCount = 10; - var publicationsCount = 100; + deltas.Each(delta => Output.WriteLine(delta.ToString())); + Assert.Equal(Enumerable.Range(0, 6).ToArray(), entries.Select(entry => entry.Index).ToArray()); - var step = TimeSpan.FromMilliseconds(10); - var startFrom = DateTime.UtcNow.Add(TimeSpan.FromMilliseconds(100)); + Task Callback(Entry entry, CancellationToken token) + { + entry.Actual = DateTime.UtcNow; + entries.Add(entry); - var actualCount = 0; - var expectedCount = publishersCount * publicationsCount; + return Task.CompletedTask; + } + } - var started = DateTime.UtcNow; + [Theory(Timeout = 60_000)] + [MemberData(nameof(DeferredQueueTestData))] + internal async Task IntensiveReadWriteTest(DeferredQueue queue) + { + Assert.True(queue.IsEmpty); - using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3))) - { - var token = cts.Token; + var publishersCount = 10; + var publicationsCount = 100; - var deferredDeliveryOperation = queue.Run(Callback(cts), token); + var step = TimeSpan.FromMilliseconds(10); + var startFrom = DateTime.UtcNow.Add(TimeSpan.FromMilliseconds(100)); - var publishers = Enumerable.Range(0, publishersCount) - .Select(i => Task.Run(() => StartPublishing(queue, publicationsCount, startFrom.Add(step * i), step, token), token)) - .ToList(); + var actualCount = 0; + var expectedCount = publishersCount * publicationsCount; - try - { - await Task.WhenAll(publishers).ConfigureAwait(false); - await deferredDeliveryOperation.ConfigureAwait(false); - } - catch (OperationCanceledException) - { - } - } + var started = DateTime.UtcNow; - var finished = DateTime.UtcNow; - var duration = finished - started; - Output.WriteLine(duration.ToString()); + using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3))) + { + var token = cts.Token; + + var deferredDeliveryOperation = queue.Run(Callback(cts), token); - Assert.True(queue.IsEmpty); - Assert.Equal(expectedCount, actualCount); + var publishers = Enumerable.Range(0, publishersCount) + .Select(i => Task.Run(() => StartPublishing(queue, publicationsCount, startFrom.Add(step * i), step, token), token)) + .ToList(); - Func Callback(CancellationTokenSource cts) + try { - return (_, _) => - { - Interlocked.Increment(ref actualCount); + await Task.WhenAll(publishers).ConfigureAwait(false); + await deferredDeliveryOperation.ConfigureAwait(false); + } + catch (OperationCanceledException) + { + } + } - if (expectedCount == actualCount) - { - cts.Cancel(); - } + var finished = DateTime.UtcNow; + var duration = finished - started; + Output.WriteLine(duration.ToString()); - return Task.CompletedTask; - }; - } + Assert.True(queue.IsEmpty); + Assert.Equal(expectedCount, actualCount); - static async Task StartPublishing( - IAsyncQueue queue, - int publicationsCount, - DateTime startFrom, - TimeSpan step, - CancellationToken token) + Func Callback(CancellationTokenSource cts) + { + return (_, _) => { - for (var i = 1; i <= publicationsCount; i++) + Interlocked.Increment(ref actualCount); + + if (expectedCount == actualCount) { - await queue.Enqueue(new Entry(i, startFrom.Add(step * i)), token).ConfigureAwait(false); + cts.Cancel(); } - } + + return Task.CompletedTask; + }; } - internal class Entry : IEquatable, - ISafelyEquatable, - ISafelyComparable, - IComparable, - IComparable + static async Task StartPublishing( + IAsyncQueue queue, + int publicationsCount, + DateTime startFrom, + TimeSpan step, + CancellationToken token) { - private DateTime? _actual; - - public Entry(int index, DateTime planned) + for (var i = 1; i <= publicationsCount; i++) { - Index = index; - Planned = planned; + await queue.Enqueue(new Entry(i, startFrom.Add(step * i)), token).ConfigureAwait(false); } + } + } - public int Index { get; } + internal class Entry : IEquatable, + ISafelyEquatable, + ISafelyComparable, + IComparable, + IComparable + { + private DateTime? _actual; - public DateTime Planned { get; } + public Entry(int index, DateTime planned) + { + Index = index; + Planned = planned; + } - public DateTime Actual - { - get => _actual ?? throw new InvalidOperationException("Elapsed should be set"); - set => _actual = value; - } + public int Index { get; } - #region IEquatable + public DateTime Planned { get; } - public static bool operator ==(Entry? left, Entry? right) - { - return Equatable.Equals(left, right); - } + public DateTime Actual + { + get => _actual ?? throw new InvalidOperationException("Elapsed should be set"); + set => _actual = value; + } - public static bool operator !=(Entry? left, Entry? right) - { - return !Equatable.Equals(left, right); - } + #region IEquatable - public override int GetHashCode() - { - return Index; - } + public static bool operator ==(Entry? left, Entry? right) + { + return Equatable.Equals(left, right); + } - public override bool Equals(object? obj) - { - return Equatable.Equals(this, obj); - } + public static bool operator !=(Entry? left, Entry? right) + { + return !Equatable.Equals(left, right); + } - public bool Equals(Entry? other) - { - return Equatable.Equals(this, other); - } + public override int GetHashCode() + { + return Index; + } - public bool SafeEquals(Entry other) - { - return Index == other.Index; - } + public override bool Equals(object? obj) + { + return Equatable.Equals(this, obj); + } - #endregion + public bool Equals(Entry? other) + { + return Equatable.Equals(this, other); + } - #region IComparable + public bool SafeEquals(Entry other) + { + return Index == other.Index; + } - public int SafeCompareTo(Entry other) - { - return Index.CompareTo(other.Index); - } + #endregion - public int CompareTo(Entry? other) - { - return Comparable.CompareTo(this, other); - } + #region IComparable - public int CompareTo(object? obj) - { - return Comparable.CompareTo(this, obj); - } + public int SafeCompareTo(Entry other) + { + return Index.CompareTo(other.Index); + } - #endregion + public int CompareTo(Entry? other) + { + return Comparable.CompareTo(this, other); + } - public override string ToString() - { - return $"[{Index}] - {Planned:O} - {_actual?.ToString("O") ?? "null"}"; - } + public int CompareTo(object? obj) + { + return Comparable.CompareTo(this, obj); + } + + #endregion + + public override string ToString() + { + return $"[{Index}] - {Planned:O} - {_actual?.ToString("O") ?? "null"}"; } } } \ No newline at end of file diff --git a/tests/Tests/Basics.Test/EnumerableExtensionsTest.cs b/tests/Tests/Basics.Test/EnumerableExtensionsTest.cs index ec09f959..ca98fe6a 100644 --- a/tests/Tests/Basics.Test/EnumerableExtensionsTest.cs +++ b/tests/Tests/Basics.Test/EnumerableExtensionsTest.cs @@ -1,222 +1,216 @@ -namespace SpaceEngineers.Core.Basics.Test +namespace SpaceEngineers.Core.Basics.Test; + +using System; +using System.Collections.Generic; +using System.Linq; +using Xunit; +using Xunit.Abstractions; + +public class EnumerableExtensionsTest : BasicsTestBase { - using System; - using System.Collections.Generic; - using System.Linq; - using Xunit; - using Xunit.Abstractions; - - /// - /// EnumerableExtensions test - /// - public class EnumerableExtensionsTest : BasicsTestBase + public EnumerableExtensionsTest(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + internal void SimpleColumnsCartesianProductTest() { - /// .ctor - /// ITestOutputHelper - public EnumerableExtensionsTest(ITestOutputHelper output) - : base(output) + IEnumerable> columns = new List> { - } + new List + { + typeof(object), + typeof(bool), + typeof(string), + typeof(Enum) + } + }; - [Fact] - internal void SimpleColumnsCartesianProductTest() + IEnumerable> expected = new List> { - IEnumerable> columns = new List> - { - new List - { - typeof(object), - typeof(bool), - typeof(string), - typeof(Enum) - } - }; - - IEnumerable> expected = new List> - { - new List - { - typeof(object) - }, - new List - { - typeof(bool) - }, - new List - { - typeof(string) - }, - new List - { - typeof(Enum) - } - }; - - Assert.True(CheckEquality(expected, columns.ColumnsCartesianProduct())); - - columns = new List> - { - new List - { - typeof(object), - typeof(bool) - }, - new List - { - typeof(string), - typeof(Enum) - } - }; - - expected = new List> - { - new List - { - typeof(object), - typeof(string) - }, - new List - { - typeof(object), - typeof(Enum) - }, - new List - { - typeof(bool), - typeof(string) - }, - new List - { - typeof(bool), - typeof(Enum) - } - }; - - Assert.True(CheckEquality(expected, columns.ColumnsCartesianProduct())); - } + new List + { + typeof(object) + }, + new List + { + typeof(bool) + }, + new List + { + typeof(string) + }, + new List + { + typeof(Enum) + } + }; + + Assert.True(CheckEquality(expected, columns.ColumnsCartesianProduct())); - [Fact] - internal void ComplexColumnsCartesianProductTest() + columns = new List> { - IEnumerable> columns = new List> - { - new List - { - typeof(object), - typeof(bool) - }, - new List - { - typeof(string), - typeof(Enum) - }, - new List - { - typeof(int), - typeof(decimal) - } - }; - - IEnumerable> expected = new List> - { - new List - { - typeof(object), - typeof(string), - typeof(int) - }, - new List - { - typeof(object), - typeof(string), - typeof(decimal) - }, - new List - { - typeof(object), - typeof(Enum), - typeof(int) - }, - new List - { - typeof(object), - typeof(Enum), - typeof(decimal) - }, - new List - { - typeof(bool), - typeof(string), - typeof(int) - }, - new List - { - typeof(bool), - typeof(string), - typeof(decimal) - }, - new List - { - typeof(bool), - typeof(Enum), - typeof(int) - }, - new List - { - typeof(bool), - typeof(Enum), - typeof(decimal) - } - }; - - Assert.True(CheckEquality(expected, columns.ColumnsCartesianProduct())); - } + new List + { + typeof(object), + typeof(bool) + }, + new List + { + typeof(string), + typeof(Enum) + } + }; - [Fact] - internal void EmptyColumnsCartesianProductTest() + expected = new List> { - IEnumerable> columns = Enumerable.Empty>(); - IEnumerable> expected = Enumerable.Empty>(); - - Assert.True(CheckEquality(expected, columns.ColumnsCartesianProduct())); - - columns = new List> - { - new List - { - typeof(object), - typeof(bool) - }, - new List - { - typeof(string), - typeof(Enum) - }, - Enumerable.Empty() - }; - - Assert.True(CheckEquality(expected, columns.ColumnsCartesianProduct())); - } + new List + { + typeof(object), + typeof(string) + }, + new List + { + typeof(object), + typeof(Enum) + }, + new List + { + typeof(bool), + typeof(string) + }, + new List + { + typeof(bool), + typeof(Enum) + } + }; + + Assert.True(CheckEquality(expected, columns.ColumnsCartesianProduct())); + } - private bool CheckEquality(IEnumerable> expected, IEnumerable> actual) + [Fact] + internal void ComplexColumnsCartesianProductTest() + { + IEnumerable> columns = new List> { - Show(actual); + new List + { + typeof(object), + typeof(bool) + }, + new List + { + typeof(string), + typeof(Enum) + }, + new List + { + typeof(int), + typeof(decimal) + } + }; - return expected.Count() == actual.Count() - && expected.Zip(actual).All(pair => pair.First.SequenceEqual(pair.Second)); - } + IEnumerable> expected = new List> + { + new List + { + typeof(object), + typeof(string), + typeof(int) + }, + new List + { + typeof(object), + typeof(string), + typeof(decimal) + }, + new List + { + typeof(object), + typeof(Enum), + typeof(int) + }, + new List + { + typeof(object), + typeof(Enum), + typeof(decimal) + }, + new List + { + typeof(bool), + typeof(string), + typeof(int) + }, + new List + { + typeof(bool), + typeof(string), + typeof(decimal) + }, + new List + { + typeof(bool), + typeof(Enum), + typeof(int) + }, + new List + { + typeof(bool), + typeof(Enum), + typeof(decimal) + } + }; + + Assert.True(CheckEquality(expected, columns.ColumnsCartesianProduct())); + } - private void Show(IEnumerable> source) + [Fact] + internal void EmptyColumnsCartesianProductTest() + { + IEnumerable> columns = Enumerable.Empty>(); + IEnumerable> expected = Enumerable.Empty>(); + + Assert.True(CheckEquality(expected, columns.ColumnsCartesianProduct())); + + columns = new List> { - foreach (var row in source) + new List + { + typeof(object), + typeof(bool) + }, + new List { - foreach (var item in row) - { - Output.WriteLine(item.Name); - } + typeof(string), + typeof(Enum) + }, + Enumerable.Empty() + }; + + Assert.True(CheckEquality(expected, columns.ColumnsCartesianProduct())); + } + + private bool CheckEquality(IEnumerable> expected, IEnumerable> actual) + { + Show(actual); + + return expected.Count() == actual.Count() + && expected.Zip(actual).All(pair => pair.First.SequenceEqual(pair.Second)); + } - Output.WriteLine("-*-*-*-*-*-"); + private void Show(IEnumerable> source) + { + foreach (var row in source) + { + foreach (var item in row) + { + Output.WriteLine(item.Name); } + + Output.WriteLine("-*-*-*-*-*-"); } } } \ No newline at end of file diff --git a/tests/Tests/Basics.Test/ExecutionExtensionsActionsTest.cs b/tests/Tests/Basics.Test/ExecutionExtensionsActionsTest.cs index 41e5265a..a34dd6ff 100644 --- a/tests/Tests/Basics.Test/ExecutionExtensionsActionsTest.cs +++ b/tests/Tests/Basics.Test/ExecutionExtensionsActionsTest.cs @@ -1,113 +1,107 @@ -namespace SpaceEngineers.Core.Basics.Test +namespace SpaceEngineers.Core.Basics.Test; + +using System; +using Basics; +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; + +public class ExecutionExtensionsActionsTest : BasicsTestBase { - using System; - using Basics; - using Xunit; - using Xunit.Abstractions; - using Xunit.Sdk; - - /// - /// ExecutionExtensions class tests - /// - public class ExecutionExtensionsActionsTest : BasicsTestBase + public ExecutionExtensionsActionsTest(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + internal void HandleCaughtExceptionsTest() + { + Action action = () => throw TestExtensions.TrueException(); + + ExecutionExtensions.Try(action).Catch().Invoke(); + ExecutionExtensions.Try(action).Catch(ex => { }).Invoke(); + + void HandleCaught() => ExecutionExtensions + .Try(action) + .Catch(ex => throw ex) + .Invoke(); + + Assert.Throws(HandleCaught); + + void Rethrow() => ExecutionExtensions + .Try(action) + .Catch(ex => throw TestExtensions.FalseException()) + .Invoke(); + + Assert.Throws(Rethrow); + + void Unhandled() => ExecutionExtensions + .Try(action) + .Catch(_ => throw TestExtensions.FalseException()) + .Invoke(); + + Assert.Throws(Unhandled); + } + + [Fact] + internal void SimpleTest() + { + Action action = () => { }; + + ExecutionExtensions + .Try(action) + .Catch() + .Catch() + .Invoke(); + } + + [Fact] + internal void HandledExceptionTest() { - /// .ctor - /// ITestOutputHelper - public ExecutionExtensionsActionsTest(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - internal void HandleCaughtExceptionsTest() - { - Action action = () => throw TestExtensions.TrueException(); - - ExecutionExtensions.Try(action).Catch().Invoke(); - ExecutionExtensions.Try(action).Catch(ex => { }).Invoke(); - - void HandleCaught() => ExecutionExtensions - .Try(action) - .Catch(ex => throw ex) - .Invoke(); - - Assert.Throws(HandleCaught); - - void Rethrow() => ExecutionExtensions - .Try(action) - .Catch(ex => throw TestExtensions.FalseException()) - .Invoke(); - - Assert.Throws(Rethrow); - - void Unhandled() => ExecutionExtensions - .Try(action) - .Catch(_ => throw TestExtensions.FalseException()) - .Invoke(); - - Assert.Throws(Unhandled); - } - - [Fact] - internal void SimpleTest() - { - Action action = () => { }; - - ExecutionExtensions - .Try(action) - .Catch() - .Catch() - .Invoke(); - } - - [Fact] - internal void HandledExceptionTest() - { - Action action = () => throw TestExtensions.FalseException(); - - ExecutionExtensions - .Try(action) - .Catch() - .Invoke(); - } - - [Fact] - internal void SeveralCatchBlocksTest() - { - Action action = () => throw TestExtensions.FalseException(); - - ExecutionExtensions - .Try(action) - .Catch(ex => throw ex) - .Catch() - .Invoke(); - } - - [Fact] - internal void ThrowInCatchBlockTest() - { - Action action = () => throw TestExtensions.FalseException(); - - void TestAction() => ExecutionExtensions - .Try(action) - .Catch(ex => throw TestExtensions.TrueException()) - .Invoke(); - - Assert.Throws(TestAction); - } - - [Fact] - internal void ThrowInFinallyBlockTest() - { - Action action = () => throw TestExtensions.FalseException(); - - void TestAction() => ExecutionExtensions - .Try(action) - .Catch(ex => throw ex) - .Finally(() => throw TestExtensions.TrueException()) - .Invoke(); - - Assert.Throws(TestAction); - } + Action action = () => throw TestExtensions.FalseException(); + + ExecutionExtensions + .Try(action) + .Catch() + .Invoke(); + } + + [Fact] + internal void SeveralCatchBlocksTest() + { + Action action = () => throw TestExtensions.FalseException(); + + ExecutionExtensions + .Try(action) + .Catch(ex => throw ex) + .Catch() + .Invoke(); + } + + [Fact] + internal void ThrowInCatchBlockTest() + { + Action action = () => throw TestExtensions.FalseException(); + + void TestAction() => ExecutionExtensions + .Try(action) + .Catch(ex => throw TestExtensions.TrueException()) + .Invoke(); + + Assert.Throws(TestAction); + } + + [Fact] + internal void ThrowInFinallyBlockTest() + { + Action action = () => throw TestExtensions.FalseException(); + + void TestAction() => ExecutionExtensions + .Try(action) + .Catch(ex => throw ex) + .Finally(() => throw TestExtensions.TrueException()) + .Invoke(); + + Assert.Throws(TestAction); } } \ No newline at end of file diff --git a/tests/Tests/Basics.Test/ExecutionExtensionsFunctionsTest.cs b/tests/Tests/Basics.Test/ExecutionExtensionsFunctionsTest.cs index 58123bba..36d98b7c 100644 --- a/tests/Tests/Basics.Test/ExecutionExtensionsFunctionsTest.cs +++ b/tests/Tests/Basics.Test/ExecutionExtensionsFunctionsTest.cs @@ -1,142 +1,136 @@ -namespace SpaceEngineers.Core.Basics.Test +namespace SpaceEngineers.Core.Basics.Test; + +using System; +using Basics; +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; + +public class ExecutionExtensionsFunctionsTest : BasicsTestBase { - using System; - using Basics; - using Xunit; - using Xunit.Abstractions; - using Xunit.Sdk; - - /// - /// ExecutionExtensions class tests - /// - public class ExecutionExtensionsFunctionsTest : BasicsTestBase + public ExecutionExtensionsFunctionsTest(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + internal void HandleCaughtExceptionsTest() + { + Func func = () => throw TestExtensions.TrueException(); + + var emptyHandlerBlockResult = ExecutionExtensions + .Try(func) + .Catch() + .Invoke(_ => default); + + Assert.False(emptyHandlerBlockResult); + + emptyHandlerBlockResult = ExecutionExtensions + .Try(func) + .Catch(_ => { }) + .Invoke(_ => default); + + Assert.False(emptyHandlerBlockResult); + + var result = ExecutionExtensions + .Try(func) + .Catch() + .Invoke(_ => true); + + Assert.True(result); + + void HandleNotCaught() => ExecutionExtensions + .Try(func) + .Catch() + .Invoke(_ => true); + + Assert.Throws(HandleNotCaught); + } + + [Fact] + internal void SimpleTest() { - /// .ctor - /// ITestOutputHelper - public ExecutionExtensionsFunctionsTest(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - internal void HandleCaughtExceptionsTest() - { - Func func = () => throw TestExtensions.TrueException(); - - var emptyHandlerBlockResult = ExecutionExtensions - .Try(func) - .Catch() - .Invoke(_ => default); - - Assert.False(emptyHandlerBlockResult); - - emptyHandlerBlockResult = ExecutionExtensions - .Try(func) - .Catch(_ => { }) - .Invoke(_ => default); - - Assert.False(emptyHandlerBlockResult); - - var result = ExecutionExtensions - .Try(func) - .Catch() - .Invoke(_ => true); - - Assert.True(result); - - void HandleNotCaught() => ExecutionExtensions - .Try(func) - .Catch() - .Invoke(_ => true); - - Assert.Throws(HandleNotCaught); - } - - [Fact] - internal void SimpleTest() - { - // 1 - value type - Func valueTypeFunction = () => true; - var result1 = ExecutionExtensions.Try(valueTypeFunction).Invoke(_ => default); - Assert.True(result1); - - // 2 - nullable value type - Func nullableValueTypeFunction = () => null; - var result2 = ExecutionExtensions.Try(nullableValueTypeFunction).Invoke(_ => default); - Assert.Null(result2); - - // 3 - reference type - Func referenceFunction = () => new object(); - var result3 = ExecutionExtensions.Try(referenceFunction).Invoke(_ => new object()); - Assert.NotNull(result3); - - // nullable-reference type - Func nullableReferenceFunction = () => null; - var result4 = ExecutionExtensions.Try(nullableReferenceFunction).Invoke(_ => default); - Assert.Null(result4); - } - - [Fact] - internal void HandledExceptionTest() - { - Func function = () => throw TestExtensions.FalseException(); - - ExecutionExtensions - .Try(function) - .Catch() - .Invoke(_ => default); - } - - [Fact] - internal void SeveralCatchBlocksTest() - { - Func function = () => throw TestExtensions.FalseException(); - - ExecutionExtensions - .Try(function) - .Catch(ex => throw ex) - .Catch() - .Invoke(_ => default); - } - - [Fact] - internal void ThrowInCatchBlockTest() - { - Func function = () => throw TestExtensions.FalseException(); - - void TestFunction() => ExecutionExtensions - .Try(function) - .Catch(_ => throw TestExtensions.TrueException()) - .Invoke(_ => default); - - Assert.Throws(TestFunction); - } - - [Fact] - internal void ThrowInInvokeBlockTest() - { - Func function = () => throw TestExtensions.FalseException(); - - void TestFunction() => ExecutionExtensions - .Try(function) - .Catch() - .Invoke(ex => throw TestExtensions.TrueException()); - - Assert.Throws(TestFunction); - } - - [Fact] - internal void ThrowInFinallyBlockTest() - { - Func function = () => throw TestExtensions.FalseException(); - - void TestFunction() => ExecutionExtensions - .Try(function) - .Catch(ex => throw ex) - .Finally(() => throw TestExtensions.TrueException()) - .Invoke(_ => default); - - Assert.Throws(TestFunction); - } + // 1 - value type + Func valueTypeFunction = () => true; + var result1 = ExecutionExtensions.Try(valueTypeFunction).Invoke(_ => default); + Assert.True(result1); + + // 2 - nullable value type + Func nullableValueTypeFunction = () => null; + var result2 = ExecutionExtensions.Try(nullableValueTypeFunction).Invoke(_ => default); + Assert.Null(result2); + + // 3 - reference type + Func referenceFunction = () => new object(); + var result3 = ExecutionExtensions.Try(referenceFunction).Invoke(_ => new object()); + Assert.NotNull(result3); + + // nullable-reference type + Func nullableReferenceFunction = () => null; + var result4 = ExecutionExtensions.Try(nullableReferenceFunction).Invoke(_ => default); + Assert.Null(result4); + } + + [Fact] + internal void HandledExceptionTest() + { + Func function = () => throw TestExtensions.FalseException(); + + ExecutionExtensions + .Try(function) + .Catch() + .Invoke(_ => default); + } + + [Fact] + internal void SeveralCatchBlocksTest() + { + Func function = () => throw TestExtensions.FalseException(); + + ExecutionExtensions + .Try(function) + .Catch(ex => throw ex) + .Catch() + .Invoke(_ => default); + } + + [Fact] + internal void ThrowInCatchBlockTest() + { + Func function = () => throw TestExtensions.FalseException(); + + void TestFunction() => ExecutionExtensions + .Try(function) + .Catch(_ => throw TestExtensions.TrueException()) + .Invoke(_ => default); + + Assert.Throws(TestFunction); + } + + [Fact] + internal void ThrowInInvokeBlockTest() + { + Func function = () => throw TestExtensions.FalseException(); + + void TestFunction() => ExecutionExtensions + .Try(function) + .Catch() + .Invoke(ex => throw TestExtensions.TrueException()); + + Assert.Throws(TestFunction); + } + + [Fact] + internal void ThrowInFinallyBlockTest() + { + Func function = () => throw TestExtensions.FalseException(); + + void TestFunction() => ExecutionExtensions + .Try(function) + .Catch(ex => throw ex) + .Finally(() => throw TestExtensions.TrueException()) + .Invoke(_ => default); + + Assert.Throws(TestFunction); } } \ No newline at end of file diff --git a/tests/Tests/Basics.Test/HeapTest.cs b/tests/Tests/Basics.Test/HeapTest.cs index 6fee14fe..f9737152 100644 --- a/tests/Tests/Basics.Test/HeapTest.cs +++ b/tests/Tests/Basics.Test/HeapTest.cs @@ -1,118 +1,107 @@ -namespace SpaceEngineers.Core.Basics.Test +namespace SpaceEngineers.Core.Basics.Test; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Heap; +using Xunit; +using Xunit.Abstractions; + +public class HeapTest : BasicsTestBase { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Linq; - using System.Threading.Tasks; - using Enumerations; - using Primitives; - using Xunit; - using Xunit.Abstractions; - - /// - /// HeapTest - /// - public class HeapTest : BasicsTestBase + public HeapTest(ITestOutputHelper output) + : base(output) { - /// .cctor - /// ITestOutputHelper - public HeapTest(ITestOutputHelper output) - : base(output) - { - } - - /// Heap test data member - /// Test data - [SuppressMessage("Analysis", "CA5394", Justification = "Test data generation")] - public static IEnumerable HeapTestData() - { - var count = 100; - var array = new int[count]; - var random = new Random(100); + } - for (var i = 0; i < count; i++) - { - array[i] = random.Next(0, count); - } + public static IEnumerable HeapTestData() + { + var count = 100; + var array = new int[count]; + var random = new Random(100); - yield return new object[] { new BinaryHeap(array, EnOrderingDirection.Asc), EnOrderingDirection.Asc }; - yield return new object[] { new BinaryHeap(array, EnOrderingDirection.Desc), EnOrderingDirection.Desc }; + for (var i = 0; i < count; i++) + { + array[i] = random.Next(0, count); } - [Theory] - [MemberData(nameof(HeapTestData))] - internal void OrderingTest(IHeap heap, EnOrderingDirection orderingKind) - { - Output.WriteLine(heap.ToString()); + yield return new object[] { new BinaryHeap(array, EnOrderingDirection.Asc), EnOrderingDirection.Asc }; + yield return new object[] { new BinaryHeap(array, EnOrderingDirection.Desc), EnOrderingDirection.Desc }; + } - var enumeratedArray = (orderingKind == EnOrderingDirection.Asc - ? heap.OrderBy(it => it) - : heap.OrderByDescending(it => it)) - .ToArray(); + [Theory] + [MemberData(nameof(HeapTestData))] + internal void OrderingTest(IHeap heap, EnOrderingDirection orderingKind) + { + Output.WriteLine(heap.ToString()); - var orderedArray = heap.ExtractArray(); + var enumeratedArray = (orderingKind == EnOrderingDirection.Asc + ? heap.OrderBy(it => it) + : heap.OrderByDescending(it => it)) + .ToArray(); - Assert.Equal(enumeratedArray, orderedArray); - Assert.True(heap.IsEmpty); - Assert.Equal(0, heap.Count); - } + var orderedArray = heap.ExtractArray(); - [Theory] - [MemberData(nameof(HeapTestData))] - internal void MultiThreadAccessTest(IHeap heap, EnOrderingDirection orderingKind) - { - var count = heap.Count; + Assert.Equal(enumeratedArray, orderedArray); + Assert.True(heap.IsEmpty); + Assert.Equal(0, heap.Count); + } + + [Theory] + [MemberData(nameof(HeapTestData))] + internal void MultiThreadAccessTest(IHeap heap, EnOrderingDirection orderingKind) + { + var count = heap.Count; - Output.WriteLine(heap.ToString()); + Output.WriteLine(heap.ToString()); - var enumeratedArray = (orderingKind == EnOrderingDirection.Asc - ? heap.OrderBy(it => it) - : heap.OrderByDescending(it => it)) - .ToArray(); + var enumeratedArray = (orderingKind == EnOrderingDirection.Asc + ? heap.OrderBy(it => it) + : heap.OrderByDescending(it => it)) + .ToArray(); - Parallel.For( - 0, - count, - new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }, - Modify); + Parallel.For( + 0, + count, + new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }, + Modify); - var orderedArray = heap.ExtractArray(); + var orderedArray = heap.ExtractArray(); - Assert.Equal(enumeratedArray, orderedArray); - Assert.True(heap.IsEmpty); - Assert.Equal(0, heap.Count); + Assert.Equal(enumeratedArray, orderedArray); + Assert.True(heap.IsEmpty); + Assert.Equal(0, heap.Count); - void Modify(int index) + void Modify(int index) + { + switch (index % 2) { - switch (index % 2) - { - case 0: Read(); break; - default: Write(); break; - } + case 0: Read(); break; + default: Write(); break; } + } - void Read() + void Read() + { + lock (heap) { - lock (heap) - { - _ = heap.Count; - _ = heap.IsEmpty; - _ = heap.Peek(); - _ = heap.TryPeek(out _); - } + _ = heap.Count; + _ = heap.IsEmpty; + _ = heap.Peek(); + _ = heap.TryPeek(out _); } + } - void Write() + void Write() + { + lock (heap) { - lock (heap) - { - heap.Insert(heap.Extract()); + heap.Insert(heap.Extract()); - if (heap.TryExtract(out var element)) - { - heap.Insert(element); - } + if (heap.TryExtract(out var element)) + { + heap.Insert(element); } } } diff --git a/tests/Tests/Basics.Test/MethodExtensionsTest.cs b/tests/Tests/Basics.Test/MethodExtensionsTest.cs index f1120c75..a6b4d65d 100644 --- a/tests/Tests/Basics.Test/MethodExtensionsTest.cs +++ b/tests/Tests/Basics.Test/MethodExtensionsTest.cs @@ -1,284 +1,272 @@ -namespace SpaceEngineers.Core.Basics.Test +namespace SpaceEngineers.Core.Basics.Test; + +using System.Linq; +using System.Reflection; +using System.Diagnostics.CodeAnalysis; +using Basics; +using Exceptions; +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; + +public class MethodExtensionsTest : BasicsTestBase { - using System.Diagnostics.CodeAnalysis; - using System.Linq; - using System.Reflection; - using Basics; - using Exceptions; - using Xunit; - using Xunit.Abstractions; - using Xunit.Sdk; - - /// - /// MethodExtensions class tests - /// - public class MethodExtensionsTest : BasicsTestBase - { - /// .ctor - /// ITestOutputHelper - public MethodExtensionsTest(ITestOutputHelper output) - : base(output) { } + public MethodExtensionsTest(ITestOutputHelper output) + : base(output) { } - [Fact] - internal void CallStaticMethodTest() - { - Assert.True(typeof(StaticTestClass).CallMethod(nameof(StaticTestClass.PublicStaticMethod)).Invoke()); - Assert.True(typeof(StaticTestClass).CallMethod("PrivateStaticMethod").Invoke()); + [Fact] + internal void CallStaticMethodTest() + { + Assert.True(typeof(StaticTestClass).CallMethod(nameof(StaticTestClass.PublicStaticMethod)).Invoke()); + Assert.True(typeof(StaticTestClass).CallMethod("PrivateStaticMethod").Invoke()); - Assert.True(typeof(StaticTestClass).CallMethod(nameof(StaticTestClass.PublicStaticMethodWithArgs)).WithArgument(true).Invoke()); - Assert.True(typeof(StaticTestClass).CallMethod("PrivateStaticMethodWithArgs").WithArgument(true).Invoke()); + Assert.True(typeof(StaticTestClass).CallMethod(nameof(StaticTestClass.PublicStaticMethodWithArgs)).WithArgument(true).Invoke()); + Assert.True(typeof(StaticTestClass).CallMethod("PrivateStaticMethodWithArgs").WithArgument(true).Invoke()); - Assert.True(typeof(StaticTestClass).CallMethod(nameof(StaticTestClass.PublicStaticMethodWithSeveralArgs)).WithArgument(true).WithArgument(true).Invoke()); - Assert.True(typeof(StaticTestClass).CallMethod(nameof(StaticTestClass.PublicStaticMethodWithSeveralArgs)).WithArgument(true).WithArgument(true).WithArgument(true).Invoke()); - Assert.True(typeof(StaticTestClass).CallMethod("PrivateStaticMethodWithSeveralArgs").WithArgument(true).WithArgument(true).Invoke()); + Assert.True(typeof(StaticTestClass).CallMethod(nameof(StaticTestClass.PublicStaticMethodWithSeveralArgs)).WithArgument(true).WithArgument(true).Invoke()); + Assert.True(typeof(StaticTestClass).CallMethod(nameof(StaticTestClass.PublicStaticMethodWithSeveralArgs)).WithArgument(true).WithArgument(true).WithArgument(true).Invoke()); + Assert.True(typeof(StaticTestClass).CallMethod("PrivateStaticMethodWithSeveralArgs").WithArgument(true).WithArgument(true).Invoke()); - Assert.Throws(() => typeof(StaticTestClass).CallMethod(nameof(StaticTestClass.PublicStaticMethodWithParams)).WithArgument(true).WithArgument(true).WithArgument(true).Invoke()); - Assert.Throws(() => typeof(StaticTestClass).CallMethod("PrivateStaticMethodWithParams").WithArgument(true).WithArgument(true).WithArgument(true).Invoke()); + Assert.Throws(() => typeof(StaticTestClass).CallMethod(nameof(StaticTestClass.PublicStaticMethodWithParams)).WithArgument(true).WithArgument(true).WithArgument(true).Invoke()); + Assert.Throws(() => typeof(StaticTestClass).CallMethod("PrivateStaticMethodWithParams").WithArgument(true).WithArgument(true).WithArgument(true).Invoke()); - Assert.True(typeof(StaticTestClass).CallMethod(nameof(StaticTestClass.PublicStaticMethodWithParams)).WithArgument(new object[] { true, true, true }).Invoke()); - Assert.True(typeof(StaticTestClass).CallMethod("PrivateStaticMethodWithParams").WithArgument(new object[] { true, true, true }).Invoke()); - } + Assert.True(typeof(StaticTestClass).CallMethod(nameof(StaticTestClass.PublicStaticMethodWithParams)).WithArgument(new object[] { true, true, true }).Invoke()); + Assert.True(typeof(StaticTestClass).CallMethod("PrivateStaticMethodWithParams").WithArgument(new object[] { true, true, true }).Invoke()); + } - [Fact] - internal void CallInstanceMethodTest() - { - var target = new InstanceTestClass(); + [Fact] + internal void CallInstanceMethodTest() + { + var target = new InstanceTestClass(); - Assert.True(target.CallMethod(nameof(InstanceTestClass.PublicMethod)).Invoke()); - Assert.True(target.CallMethod("PrivateMethod").Invoke()); + Assert.True(target.CallMethod(nameof(InstanceTestClass.PublicMethod)).Invoke()); + Assert.True(target.CallMethod("PrivateMethod").Invoke()); - Assert.True(target.CallMethod(nameof(InstanceTestClass.PublicMethodWithArgs)).WithArgument(true).Invoke()); - Assert.True(target.CallMethod("PrivateMethodWithArgs").WithArgument(true).Invoke()); + Assert.True(target.CallMethod(nameof(InstanceTestClass.PublicMethodWithArgs)).WithArgument(true).Invoke()); + Assert.True(target.CallMethod("PrivateMethodWithArgs").WithArgument(true).Invoke()); - Assert.True(target.CallMethod(nameof(InstanceTestClass.PublicMethodWithSeveralArgs)).WithArgument(true).WithArgument(true).Invoke()); - Assert.True(target.CallMethod(nameof(InstanceTestClass.PublicMethodWithSeveralArgs)).WithArgument(true).WithArgument(true).WithArgument(true).Invoke()); - Assert.True(target.CallMethod("PrivateMethodWithSeveralArgs").WithArgument(true).WithArgument(true).Invoke()); + Assert.True(target.CallMethod(nameof(InstanceTestClass.PublicMethodWithSeveralArgs)).WithArgument(true).WithArgument(true).Invoke()); + Assert.True(target.CallMethod(nameof(InstanceTestClass.PublicMethodWithSeveralArgs)).WithArgument(true).WithArgument(true).WithArgument(true).Invoke()); + Assert.True(target.CallMethod("PrivateMethodWithSeveralArgs").WithArgument(true).WithArgument(true).Invoke()); - Assert.Throws(() => target.CallMethod(nameof(InstanceTestClass.PublicMethodWithParams)).WithArgument(true).WithArgument(true).WithArgument(true).Invoke()); - Assert.Throws(() => target.CallMethod("PrivateMethodWithParams").WithArgument(true).WithArgument(true).WithArgument(true).Invoke()); + Assert.Throws(() => target.CallMethod(nameof(InstanceTestClass.PublicMethodWithParams)).WithArgument(true).WithArgument(true).WithArgument(true).Invoke()); + Assert.Throws(() => target.CallMethod("PrivateMethodWithParams").WithArgument(true).WithArgument(true).WithArgument(true).Invoke()); - Assert.True(target.CallMethod(nameof(InstanceTestClass.PublicMethodWithParams)).WithArgument(new object[] { true, true, true }).Invoke()); - Assert.True(target.CallMethod("PrivateMethodWithParams").WithArgument(new object[] { true, true, true }).Invoke()); - } + Assert.True(target.CallMethod(nameof(InstanceTestClass.PublicMethodWithParams)).WithArgument(new object[] { true, true, true }).Invoke()); + Assert.True(target.CallMethod("PrivateMethodWithParams").WithArgument(new object[] { true, true, true }).Invoke()); + } - [Fact] - internal void CallStaticGenericMethodTest() - { - // 1 - close on reference - Assert.Throws(() => typeof(StaticTestClass).CallMethod(nameof(StaticTestClass.PublicStaticGenericMethod)).WithTypeArgument().Invoke()); - Assert.Throws(() => typeof(StaticTestClass).CallMethod("PrivateStaticGenericMethod").WithTypeArgument().Invoke()); + [Fact] + internal void CallStaticGenericMethodTest() + { + // 1 - close on reference + Assert.Throws(() => typeof(StaticTestClass).CallMethod(nameof(StaticTestClass.PublicStaticGenericMethod)).WithTypeArgument().Invoke()); + Assert.Throws(() => typeof(StaticTestClass).CallMethod("PrivateStaticGenericMethod").WithTypeArgument().Invoke()); - Assert.Throws(() => typeof(StaticTestClass).CallMethod(nameof(StaticTestClass.PublicStaticGenericMethod)).WithTypeArgument().WithTypeArgument().Invoke()); - Assert.Throws(() => typeof(StaticTestClass).CallMethod("PrivateStaticGenericMethod").WithTypeArgument().WithTypeArgument().Invoke()); + Assert.Throws(() => typeof(StaticTestClass).CallMethod(nameof(StaticTestClass.PublicStaticGenericMethod)).WithTypeArgument().WithTypeArgument().Invoke()); + Assert.Throws(() => typeof(StaticTestClass).CallMethod("PrivateStaticGenericMethod").WithTypeArgument().WithTypeArgument().Invoke()); - Assert.NotNull(typeof(StaticTestClass).CallMethod(nameof(StaticTestClass.PublicStaticGenericMethod)).WithTypeArgument().WithArgument(new object()).Invoke()); - Assert.NotNull(typeof(StaticTestClass).CallMethod("PrivateStaticGenericMethod").WithTypeArgument().WithArgument(new object()).Invoke()); + Assert.NotNull(typeof(StaticTestClass).CallMethod(nameof(StaticTestClass.PublicStaticGenericMethod)).WithTypeArgument().WithArgument(new object()).Invoke()); + Assert.NotNull(typeof(StaticTestClass).CallMethod("PrivateStaticGenericMethod").WithTypeArgument().WithArgument(new object()).Invoke()); - Assert.NotNull(typeof(StaticTestClass).CallMethod(nameof(StaticTestClass.AmbiguousPublicStaticGenericMethod)).WithTypeArgument().WithArgument(new object()).Invoke()); - Assert.NotNull(typeof(StaticTestClass).CallMethod("AmbiguousPrivateStaticGenericMethod").WithTypeArgument().WithArgument(new object()).Invoke()); + Assert.NotNull(typeof(StaticTestClass).CallMethod(nameof(StaticTestClass.AmbiguousPublicStaticGenericMethod)).WithTypeArgument().WithArgument(new object()).Invoke()); + Assert.NotNull(typeof(StaticTestClass).CallMethod("AmbiguousPrivateStaticGenericMethod").WithTypeArgument().WithArgument(new object()).Invoke()); - // 2 - close on value - Assert.True(typeof(StaticTestClass).CallMethod(nameof(StaticTestClass.PublicStaticGenericMethod)).WithTypeArgument().WithArgument(true).Invoke()); - Assert.True(typeof(StaticTestClass).CallMethod("PrivateStaticGenericMethod").WithTypeArgument().WithArgument(true).Invoke()); + // 2 - close on value + Assert.True(typeof(StaticTestClass).CallMethod(nameof(StaticTestClass.PublicStaticGenericMethod)).WithTypeArgument().WithArgument(true).Invoke()); + Assert.True(typeof(StaticTestClass).CallMethod("PrivateStaticGenericMethod").WithTypeArgument().WithArgument(true).Invoke()); - Assert.True(typeof(StaticTestClass).CallMethod(nameof(StaticTestClass.PublicStaticGenericMethod)).WithTypeArgument(typeof(bool)).WithArgument(true).Invoke()); - Assert.True(typeof(StaticTestClass).CallMethod("PrivateStaticGenericMethod").WithTypeArgument(typeof(bool)).WithArgument(true).Invoke()); + Assert.True(typeof(StaticTestClass).CallMethod(nameof(StaticTestClass.PublicStaticGenericMethod)).WithTypeArgument(typeof(bool)).WithArgument(true).Invoke()); + Assert.True(typeof(StaticTestClass).CallMethod("PrivateStaticGenericMethod").WithTypeArgument(typeof(bool)).WithArgument(true).Invoke()); - Assert.True(typeof(StaticTestClass).CallMethod(nameof(StaticTestClass.PublicStaticGenericMethod)).WithTypeArgument().WithArgument(true).Invoke()); - Assert.True(typeof(StaticTestClass).CallMethod("PrivateStaticGenericMethod").WithTypeArgument().WithArgument(true).Invoke()); + Assert.True(typeof(StaticTestClass).CallMethod(nameof(StaticTestClass.PublicStaticGenericMethod)).WithTypeArgument().WithArgument(true).Invoke()); + Assert.True(typeof(StaticTestClass).CallMethod("PrivateStaticGenericMethod").WithTypeArgument().WithArgument(true).Invoke()); - Assert.Throws(() => typeof(StaticTestClass).CallMethod(nameof(StaticTestClass.AmbiguousPublicStaticGenericMethod)).WithTypeArgument().WithArgument(true).Invoke()); - Assert.Throws(() => typeof(StaticTestClass).CallMethod("AmbiguousPrivateStaticGenericMethod").WithTypeArgument().WithArgument(true).Invoke()); - } + Assert.Throws(() => typeof(StaticTestClass).CallMethod(nameof(StaticTestClass.AmbiguousPublicStaticGenericMethod)).WithTypeArgument().WithArgument(true).Invoke()); + Assert.Throws(() => typeof(StaticTestClass).CallMethod("AmbiguousPrivateStaticGenericMethod").WithTypeArgument().WithArgument(true).Invoke()); + } - [Fact] - internal void CallInstanceGenericMethodTest() - { - var target = new InstanceTestClass(); + [Fact] + internal void CallInstanceGenericMethodTest() + { + var target = new InstanceTestClass(); - // 1 - close on reference - Assert.Throws(() => target.CallMethod(nameof(InstanceTestClass.PublicGenericMethod)).WithTypeArgument().Invoke()); - Assert.Throws(() => target.CallMethod("PrivateGenericMethod").WithTypeArgument().Invoke()); + // 1 - close on reference + Assert.Throws(() => target.CallMethod(nameof(InstanceTestClass.PublicGenericMethod)).WithTypeArgument().Invoke()); + Assert.Throws(() => target.CallMethod("PrivateGenericMethod").WithTypeArgument().Invoke()); - Assert.Throws(() => target.CallMethod(nameof(InstanceTestClass.PublicGenericMethod)).WithTypeArgument().WithTypeArgument().Invoke()); - Assert.Throws(() => target.CallMethod("PrivateGenericMethod").WithTypeArgument().WithTypeArgument().Invoke()); + Assert.Throws(() => target.CallMethod(nameof(InstanceTestClass.PublicGenericMethod)).WithTypeArgument().WithTypeArgument().Invoke()); + Assert.Throws(() => target.CallMethod("PrivateGenericMethod").WithTypeArgument().WithTypeArgument().Invoke()); - Assert.NotNull(target.CallMethod(nameof(InstanceTestClass.PublicGenericMethod)).WithTypeArgument().WithArgument(new object()).Invoke()); - Assert.NotNull(target.CallMethod("PrivateGenericMethod").WithTypeArgument().WithArgument(new object()).Invoke()); + Assert.NotNull(target.CallMethod(nameof(InstanceTestClass.PublicGenericMethod)).WithTypeArgument().WithArgument(new object()).Invoke()); + Assert.NotNull(target.CallMethod("PrivateGenericMethod").WithTypeArgument().WithArgument(new object()).Invoke()); - Assert.NotNull(target.CallMethod(nameof(InstanceTestClass.AmbiguousPublicGenericMethod)).WithTypeArgument().WithArgument(new object()).Invoke()); - Assert.NotNull(target.CallMethod("AmbiguousPrivateGenericMethod").WithTypeArgument().WithArgument(new object()).Invoke()); + Assert.NotNull(target.CallMethod(nameof(InstanceTestClass.AmbiguousPublicGenericMethod)).WithTypeArgument().WithArgument(new object()).Invoke()); + Assert.NotNull(target.CallMethod("AmbiguousPrivateGenericMethod").WithTypeArgument().WithArgument(new object()).Invoke()); - // 2 - close on value - Assert.True(target.CallMethod(nameof(InstanceTestClass.PublicGenericMethod)).WithTypeArgument().WithArgument(true).Invoke()); - Assert.True(target.CallMethod("PrivateGenericMethod").WithTypeArgument().WithArgument(true).Invoke()); + // 2 - close on value + Assert.True(target.CallMethod(nameof(InstanceTestClass.PublicGenericMethod)).WithTypeArgument().WithArgument(true).Invoke()); + Assert.True(target.CallMethod("PrivateGenericMethod").WithTypeArgument().WithArgument(true).Invoke()); - Assert.True(target.CallMethod(nameof(InstanceTestClass.PublicGenericMethod)).WithTypeArgument(typeof(bool)).WithArgument(true).Invoke()); - Assert.True(target.CallMethod("PrivateGenericMethod").WithTypeArgument(typeof(bool)).WithArgument(true).Invoke()); + Assert.True(target.CallMethod(nameof(InstanceTestClass.PublicGenericMethod)).WithTypeArgument(typeof(bool)).WithArgument(true).Invoke()); + Assert.True(target.CallMethod("PrivateGenericMethod").WithTypeArgument(typeof(bool)).WithArgument(true).Invoke()); - Assert.True(target.CallMethod(nameof(InstanceTestClass.PublicGenericMethod)).WithTypeArgument().WithArgument(true).Invoke()); - Assert.True(target.CallMethod("PrivateGenericMethod").WithTypeArgument().WithArgument(true).Invoke()); + Assert.True(target.CallMethod(nameof(InstanceTestClass.PublicGenericMethod)).WithTypeArgument().WithArgument(true).Invoke()); + Assert.True(target.CallMethod("PrivateGenericMethod").WithTypeArgument().WithArgument(true).Invoke()); - Assert.Throws(() => target.CallMethod(nameof(InstanceTestClass.AmbiguousPublicGenericMethod)).WithTypeArgument().WithArgument(true).Invoke()); - Assert.Throws(() => target.CallMethod("AmbiguousPrivateGenericMethod").WithTypeArgument().WithArgument(true).Invoke()); - } + Assert.Throws(() => target.CallMethod(nameof(InstanceTestClass.AmbiguousPublicGenericMethod)).WithTypeArgument().WithArgument(true).Invoke()); + Assert.Throws(() => target.CallMethod("AmbiguousPrivateGenericMethod").WithTypeArgument().WithArgument(true).Invoke()); + } - [Fact] - internal void NullArgumentTest() - { - var target = new NullableTestClass(); + [Fact] + internal void NullArgumentTest() + { + var target = new NullableTestClass(); - Assert.Throws(() => target.CallMethod(nameof(NullableTestClass.MethodWithNullArgs)).WithArgument(null).Invoke()); - Assert.Throws(() => target.CallMethod(nameof(NullableTestClass.MethodWithOptionalNullArgs)).WithArgument(null).Invoke()); - } + Assert.Throws(() => target.CallMethod(nameof(NullableTestClass.MethodWithNullArgs)).WithArgument(null).Invoke()); + Assert.Throws(() => target.CallMethod(nameof(NullableTestClass.MethodWithOptionalNullArgs)).WithArgument(null).Invoke()); + } - [Fact] - internal void InheritanceCallTest() - { - var baseTarget = new TestClassBase(); - var derivedTarget = new DerivedClass(); + [Fact] + internal void InheritanceCallTest() + { + var baseTarget = new TestClassBase(); + var derivedTarget = new DerivedClass(); - Assert.Throws(() => baseTarget.CallMethod(nameof(TestClassBase.BaseMethod)).Invoke()); - Assert.Throws(() => baseTarget.CallMethod("VirtualMethod").Invoke()); - Assert.Throws(() => derivedTarget.CallMethod(nameof(DerivedClass.BaseMethod)).Invoke()); + Assert.Throws(() => baseTarget.CallMethod(nameof(TestClassBase.BaseMethod)).Invoke()); + Assert.Throws(() => baseTarget.CallMethod("VirtualMethod").Invoke()); + Assert.Throws(() => derivedTarget.CallMethod(nameof(DerivedClass.BaseMethod)).Invoke()); - Assert.Throws(() => derivedTarget.CallMethod(nameof(DerivedClass.DerivedMethod)).Invoke()); - Assert.Throws(() => derivedTarget.CallMethod("VirtualMethod").Invoke()); - } + Assert.Throws(() => derivedTarget.CallMethod(nameof(DerivedClass.DerivedMethod)).Invoke()); + Assert.Throws(() => derivedTarget.CallMethod("VirtualMethod").Invoke()); + } - [SuppressMessage("Analysis", "SA1202", Justification = "For test reasons")] - [SuppressMessage("Analysis", "IDE0051", Justification = "For test reasons")] - private class StaticTestClass - { - internal static bool PublicStaticMethod() => true; + [SuppressMessage("Analysis", "SA1202", Justification = "For test reasons")] + private class StaticTestClass + { + internal static bool PublicStaticMethod() => true; - private static bool PrivateStaticMethod() => true; + private static bool PrivateStaticMethod() => true; - internal static bool PublicStaticMethodWithArgs(bool flag) => flag; + internal static bool PublicStaticMethodWithArgs(bool flag) => flag; - private static bool PrivateStaticMethodWithArgs(bool flag) => flag; + private static bool PrivateStaticMethodWithArgs(bool flag) => flag; - internal static bool PublicStaticMethodWithSeveralArgs(bool flag1, bool flag2) => flag1 && flag2; + internal static bool PublicStaticMethodWithSeveralArgs(bool flag1, bool flag2) => flag1 && flag2; - internal static bool PublicStaticMethodWithSeveralArgs(bool flag1, bool flag2, bool flag3) => flag1 && flag2 && flag3; + internal static bool PublicStaticMethodWithSeveralArgs(bool flag1, bool flag2, bool flag3) => flag1 && flag2 && flag3; - private static bool PrivateStaticMethodWithSeveralArgs(bool flag1, bool flag2) => flag1 && flag2; + private static bool PrivateStaticMethodWithSeveralArgs(bool flag1, bool flag2) => flag1 && flag2; - internal static bool PublicStaticMethodWithParams(params object[] flags) => flags.OfType().All(z => z); + internal static bool PublicStaticMethodWithParams(params object[] flags) => flags.OfType().All(z => z); - private static bool PrivateStaticMethodWithParams(params object[] flags) => flags.OfType().All(z => z); + private static bool PrivateStaticMethodWithParams(params object[] flags) => flags.OfType().All(z => z); - public static T PublicStaticGenericMethod() => throw TestExtensions.TrueException(); + public static T PublicStaticGenericMethod() => throw TestExtensions.TrueException(); - private static T PrivateStaticGenericMethod() => throw TestExtensions.TrueException(); + private static T PrivateStaticGenericMethod() => throw TestExtensions.TrueException(); - public static T1 PublicStaticGenericMethod() => throw TestExtensions.FalseException(); + public static T1 PublicStaticGenericMethod() => throw TestExtensions.FalseException(); - private static T1 PrivateStaticGenericMethod() => throw TestExtensions.FalseException(); + private static T1 PrivateStaticGenericMethod() => throw TestExtensions.FalseException(); - public static bool AmbiguousPublicStaticGenericMethod(bool flag) => flag; + public static bool AmbiguousPublicStaticGenericMethod(bool flag) => flag; - private static bool AmbiguousPrivateStaticGenericMethod(bool flag) => flag; + private static bool AmbiguousPrivateStaticGenericMethod(bool flag) => flag; - public static T AmbiguousPublicStaticGenericMethod(T flag) => flag; + public static T AmbiguousPublicStaticGenericMethod(T flag) => flag; - private static T AmbiguousPrivateStaticGenericMethod(T flag) => flag; + private static T AmbiguousPrivateStaticGenericMethod(T flag) => flag; - public static T PublicStaticGenericMethod(T flag) => flag; + public static T PublicStaticGenericMethod(T flag) => flag; - private static T PrivateStaticGenericMethod(T flag) => flag; - } + private static T PrivateStaticGenericMethod(T flag) => flag; + } - [SuppressMessage("Analysis", "SA1202", Justification = "For test reasons")] - [SuppressMessage("Analysis", "IDE0051", Justification = "For test reasons")] - [SuppressMessage("Analysis", "CA1822", Justification = "For test reasons")] - private class InstanceTestClass - { - internal bool PublicMethod() => true; + [SuppressMessage("Analysis", "SA1202", Justification = "For test reasons")] + private class InstanceTestClass + { + internal bool PublicMethod() => true; - private bool PrivateMethod() => true; + private bool PrivateMethod() => true; - internal bool PublicMethodWithArgs(bool flag) => flag; + internal bool PublicMethodWithArgs(bool flag) => flag; - private bool PrivateMethodWithArgs(bool flag) => flag; + private bool PrivateMethodWithArgs(bool flag) => flag; - internal bool PublicMethodWithSeveralArgs(bool flag1, bool flag2) => flag1 && flag2; + internal bool PublicMethodWithSeveralArgs(bool flag1, bool flag2) => flag1 && flag2; - internal bool PublicMethodWithSeveralArgs(bool flag1, bool flag2, bool flag3) => flag1 && flag2 && flag3; + internal bool PublicMethodWithSeveralArgs(bool flag1, bool flag2, bool flag3) => flag1 && flag2 && flag3; - private bool PrivateMethodWithSeveralArgs(bool flag1, bool flag2) => flag1 && flag2; + private bool PrivateMethodWithSeveralArgs(bool flag1, bool flag2) => flag1 && flag2; - internal bool PublicMethodWithParams(params object[] flags) => flags.OfType().All(z => z); + internal bool PublicMethodWithParams(params object[] flags) => flags.OfType().All(z => z); - private bool PrivateMethodWithParams(params object[] flags) => flags.OfType().All(z => z); + private bool PrivateMethodWithParams(params object[] flags) => flags.OfType().All(z => z); - public T PublicGenericMethod() => throw TestExtensions.TrueException(); + public T PublicGenericMethod() => throw TestExtensions.TrueException(); - private T PrivateGenericMethod() => throw TestExtensions.TrueException(); + private T PrivateGenericMethod() => throw TestExtensions.TrueException(); - public T1 PublicGenericMethod() => throw TestExtensions.FalseException(); + public T1 PublicGenericMethod() => throw TestExtensions.FalseException(); - private T1 PrivateGenericMethod() => throw TestExtensions.FalseException(); + private T1 PrivateGenericMethod() => throw TestExtensions.FalseException(); - public bool AmbiguousPublicGenericMethod(bool flag) => flag; + public bool AmbiguousPublicGenericMethod(bool flag) => flag; - private bool AmbiguousPrivateGenericMethod(bool flag) => flag; + private bool AmbiguousPrivateGenericMethod(bool flag) => flag; - public T AmbiguousPublicGenericMethod(T input) => input; + public T AmbiguousPublicGenericMethod(T input) => input; - private T AmbiguousPrivateGenericMethod(T input) => input; + private T AmbiguousPrivateGenericMethod(T input) => input; - public T PublicGenericMethod(T input) => input; + public T PublicGenericMethod(T input) => input; - private T PrivateGenericMethod(T input) => input; - } + private T PrivateGenericMethod(T input) => input; + } - [SuppressMessage("Analysis", "CA1822", Justification = "For test reasons")] - private class NullableTestClass + private class NullableTestClass + { + public void MethodWithNullArgs(object? arg) { - public void MethodWithNullArgs(object? arg) + if (arg == null) { - if (arg == null) - { - throw TestExtensions.TrueException(); - } + throw TestExtensions.TrueException(); } + } - public void MethodWithOptionalNullArgs(object? arg = null) + public void MethodWithOptionalNullArgs(object? arg = null) + { + if (arg == null) { - if (arg == null) - { - throw TestExtensions.TrueException(); - } + throw TestExtensions.TrueException(); } } + } - [SuppressMessage("Analysis", "CA1822", Justification = "For test reasons")] - private class TestClassBase + private class TestClassBase + { + public void BaseMethod() { - public void BaseMethod() - { - throw TestExtensions.FalseException(); - } + throw TestExtensions.FalseException(); + } - protected virtual void VirtualMethod() - { - throw TestExtensions.FalseException(); - } + protected virtual void VirtualMethod() + { + throw TestExtensions.FalseException(); } + } - [SuppressMessage("Analysis", "CA1822", Justification = "For test reasons")] - private class DerivedClass : TestClassBase + private class DerivedClass : TestClassBase + { + public void DerivedMethod() { - public void DerivedMethod() - { - throw TestExtensions.TrueException(); - } + throw TestExtensions.TrueException(); + } - protected override void VirtualMethod() - { - throw TestExtensions.TrueException(); - } + protected override void VirtualMethod() + { + throw TestExtensions.TrueException(); } } } \ No newline at end of file diff --git a/tests/Tests/Basics.Test/ObjectExtensionsDeepCopyTest.cs b/tests/Tests/Basics.Test/ObjectExtensionsDeepCopyTest.cs index dff39d02..e8818be2 100644 --- a/tests/Tests/Basics.Test/ObjectExtensionsDeepCopyTest.cs +++ b/tests/Tests/Basics.Test/ObjectExtensionsDeepCopyTest.cs @@ -1,219 +1,210 @@ -namespace SpaceEngineers.Core.Basics.Test +namespace SpaceEngineers.Core.Basics.Test; + +using System; +using System.Linq; +using Basics; +using DeepCopy; +using Xunit; +using Xunit.Abstractions; + +public class ObjectExtensionsDeepCopyTest : BasicsTestBase { - using System; - using System.Diagnostics.CodeAnalysis; - using System.Linq; - using Basics; - using DeepCopy; - using Xunit; - using Xunit.Abstractions; - - /// - /// DeepCopy test - /// - [SuppressMessage("Analysis", "SA1201", Justification = "For test reasons")] - public class ObjectExtensionsDeepCopyTest : BasicsTestBase + public ObjectExtensionsDeepCopyTest(ITestOutputHelper output) + : base(output) { } + + [Fact] + internal void DeepCopyObjectTest() { - /// .ctor - /// ITestOutputHelper - public ObjectExtensionsDeepCopyTest(ITestOutputHelper output) - : base(output) { } + var original = new object(); + var clone = original.DeepCopy(); - [Fact] - internal void DeepCopyObjectTest() - { - var original = new object(); - var clone = original.DeepCopy(); + Assert.NotEqual(original, clone); + Assert.False(ReferenceEquals(original, clone)); - Assert.NotEqual(original, clone); - Assert.False(ReferenceEquals(original, clone)); + var instance1 = new object(); + var instance2 = new object(); + clone = instance1.DeepCopy(); - var instance1 = new object(); - var instance2 = new object(); - clone = instance1.DeepCopy(); + Assert.True(instance1.GetType() == instance2.GetType()); + Assert.True(ReferenceEquals(instance1.GetType(), instance2.GetType())); - Assert.True(instance1.GetType() == instance2.GetType()); - Assert.True(ReferenceEquals(instance1.GetType(), instance2.GetType())); + Assert.True(instance1.GetType() == clone.GetType()); + Assert.True(ReferenceEquals(instance1.GetType(), clone.GetType())); + } - Assert.True(instance1.GetType() == clone.GetType()); - Assert.True(ReferenceEquals(instance1.GetType(), clone.GetType())); - } + [Fact] + internal void DeepCopyTest() + { + var original = TestReferenceWithSystemTypes.Create(); + var clone = original.DeepCopy(); - [Fact] - internal void DeepCopyTest() - { - var original = TestReferenceWithSystemTypes.Create(); - var clone = original.DeepCopy(); + AssertTestReferenceTypeWithTypes(original, clone, false); + } - AssertTestReferenceTypeWithTypes(original, clone, false); - } + private static void AssertTestReferenceTypeWithTypes(TestReferenceWithSystemTypes original, + TestReferenceWithSystemTypes clone, + bool bySerialization) + { + /* + * Type + */ + Assert.True(original.GetType() == clone.GetType()); + Assert.True(ReferenceEquals(original.GetType(), clone.GetType())); + Assert.True(original.Type == clone.Type); + Assert.True(ReferenceEquals(original.Type, clone.Type)); + + Assert.False(original.TypeArray?.Equals(clone.TypeArray)); + Assert.False(ReferenceEquals(original.TypeArray, clone.TypeArray)); + Assert.True(CheckReferenceArraySequentially(original.TypeArray, clone.TypeArray)); + + Assert.False(original.TypeCollection?.Equals(clone.TypeCollection)); + Assert.False(ReferenceEquals(original.TypeCollection, clone.TypeCollection)); + Assert.NotNull(original.TypeCollection); + Assert.NotNull(clone.TypeCollection); + Assert.True(original.TypeCollection != null + && clone.TypeCollection != null + && original.TypeCollection.SequenceEqual(clone.TypeCollection)); + + AssertTestReferenceTypeWithOutTypes(original, clone, bySerialization); + } - private static void AssertTestReferenceTypeWithTypes(TestReferenceWithSystemTypes original, - TestReferenceWithSystemTypes clone, - bool bySerialization) - { - /* - * Type - */ - Assert.True(original.GetType() == clone.GetType()); - Assert.True(ReferenceEquals(original.GetType(), clone.GetType())); - Assert.True(original.Type == clone.Type); - Assert.True(ReferenceEquals(original.Type, clone.Type)); - - Assert.False(original.TypeArray?.Equals(clone.TypeArray)); - Assert.False(ReferenceEquals(original.TypeArray, clone.TypeArray)); - Assert.True(CheckReferenceArraySequentially(original.TypeArray, clone.TypeArray)); - - Assert.False(original.TypeCollection?.Equals(clone.TypeCollection)); - Assert.False(ReferenceEquals(original.TypeCollection, clone.TypeCollection)); - Assert.NotNull(original.TypeCollection); - Assert.NotNull(clone.TypeCollection); - Assert.True(original.TypeCollection != null - && clone.TypeCollection != null - && original.TypeCollection.SequenceEqual(clone.TypeCollection)); - - AssertTestReferenceTypeWithOutTypes(original, clone, bySerialization); - } + private static void AssertTestReferenceTypeWithOutTypes(TestReferenceWithoutSystemTypes original, + TestReferenceWithoutSystemTypes clone, + bool bySerialization) + { + /* + * String + */ + Assert.Equal(original.String, clone.String); + Assert.True(bySerialization + ? !ReferenceEquals(original.String, clone.String) + : ReferenceEquals(original.String, clone.String)); + + /* + * ValueType + */ + Assert.Equal(original.Int, clone.Int); + + Assert.Equal(original.TestEnum, clone.TestEnum); + + Assert.False(original.ValueTypeArray?.Equals(clone.ValueTypeArray)); + Assert.False(ReferenceEquals(original.ValueTypeArray, clone.ValueTypeArray)); + Assert.True(CheckValueArraySequentially(original.ValueTypeArray, clone.ValueTypeArray)); + + Assert.False(original.ValueTypeCollection?.Equals(clone.ValueTypeCollection)); + Assert.False(ReferenceEquals(original.ValueTypeCollection, clone.ValueTypeCollection)); + Assert.NotNull(original.ValueTypeCollection); + Assert.NotNull(clone.ValueTypeCollection); + Assert.True(original.ValueTypeCollection != null + && clone.ValueTypeCollection != null + && original.ValueTypeCollection.SequenceEqual(clone.ValueTypeCollection)); + + /* + * ReferenceType + */ + Assert.False(original.ReferenceTypeArray?.Equals(clone.ReferenceTypeArray)); + Assert.False(ReferenceEquals(original.ReferenceTypeArray, clone.ReferenceTypeArray)); + Assert.False(CheckReferenceArraySequentially(original.ReferenceTypeArray, clone.ReferenceTypeArray)); + + Assert.False(original.ReferenceTypeCollection?.Equals(clone.ReferenceTypeCollection)); + Assert.False(ReferenceEquals(original.ReferenceTypeCollection, clone.ReferenceTypeCollection)); + Assert.NotNull(original.ReferenceTypeCollection); + Assert.NotNull(clone.ReferenceTypeCollection); + Assert.False(original.ReferenceTypeCollection != null + && clone.ReferenceTypeCollection != null + && original.ReferenceTypeCollection.SequenceEqual(clone.ReferenceTypeCollection)); + + Assert.True(original.Equals(original.CyclicReference)); + Assert.True(ReferenceEquals(original, original.CyclicReference)); + Assert.True(clone.Equals(clone.CyclicReference)); + Assert.True(ReferenceEquals(clone, clone.CyclicReference)); + + Assert.False(original.Equals(clone)); + Assert.False(ReferenceEquals(original, clone)); + + Assert.False(original.Equals(clone.CyclicReference)); + Assert.False(ReferenceEquals(original, clone.CyclicReference)); + + Assert.False(original.CyclicReference?.Equals(clone.CyclicReference)); + Assert.False(ReferenceEquals(original.CyclicReference, clone.CyclicReference)); + + Assert.True(original.Equals(TestReferenceWithoutSystemTypes.StaticCyclicReference)); + Assert.True(ReferenceEquals(original, TestReferenceWithoutSystemTypes.StaticCyclicReference)); + Assert.False(clone.Equals(TestReferenceWithoutSystemTypes.StaticCyclicReference)); + Assert.False(ReferenceEquals(clone, TestReferenceWithoutSystemTypes.StaticCyclicReference)); + + /* + * Nullable + */ + Assert.Null(clone.NullableInt); + Assert.Null(clone.NullableReference); + Assert.True(clone.CollectionOfNulls?.All(z => z == null)); + Assert.True(CheckArray(clone.ArrayOfNulls, null)); + } - private static void AssertTestReferenceTypeWithOutTypes(TestReferenceWithoutSystemTypes original, - TestReferenceWithoutSystemTypes clone, - bool bySerialization) + private static bool CheckArray(Array? array, object? value) + { + if (array == null) { - /* - * String - */ - Assert.Equal(original.String, clone.String); - Assert.True(bySerialization - ? !ReferenceEquals(original.String, clone.String) - : ReferenceEquals(original.String, clone.String)); - - /* - * ValueType - */ - Assert.Equal(original.Int, clone.Int); - - Assert.Equal(original.TestEnum, clone.TestEnum); - - Assert.False(original.ValueTypeArray?.Equals(clone.ValueTypeArray)); - Assert.False(ReferenceEquals(original.ValueTypeArray, clone.ValueTypeArray)); - Assert.True(CheckValueArraySequentially(original.ValueTypeArray, clone.ValueTypeArray)); - - Assert.False(original.ValueTypeCollection?.Equals(clone.ValueTypeCollection)); - Assert.False(ReferenceEquals(original.ValueTypeCollection, clone.ValueTypeCollection)); - Assert.NotNull(original.ValueTypeCollection); - Assert.NotNull(clone.ValueTypeCollection); - Assert.True(original.ValueTypeCollection != null - && clone.ValueTypeCollection != null - && original.ValueTypeCollection.SequenceEqual(clone.ValueTypeCollection)); - - /* - * ReferenceType - */ - Assert.False(original.ReferenceTypeArray?.Equals(clone.ReferenceTypeArray)); - Assert.False(ReferenceEquals(original.ReferenceTypeArray, clone.ReferenceTypeArray)); - Assert.False(CheckReferenceArraySequentially(original.ReferenceTypeArray, clone.ReferenceTypeArray)); - - Assert.False(original.ReferenceTypeCollection?.Equals(clone.ReferenceTypeCollection)); - Assert.False(ReferenceEquals(original.ReferenceTypeCollection, clone.ReferenceTypeCollection)); - Assert.NotNull(original.ReferenceTypeCollection); - Assert.NotNull(clone.ReferenceTypeCollection); - Assert.False(original.ReferenceTypeCollection != null - && clone.ReferenceTypeCollection != null - && original.ReferenceTypeCollection.SequenceEqual(clone.ReferenceTypeCollection)); - - Assert.True(original.Equals(original.CyclicReference)); - Assert.True(ReferenceEquals(original, original.CyclicReference)); - Assert.True(clone.Equals(clone.CyclicReference)); - Assert.True(ReferenceEquals(clone, clone.CyclicReference)); - - Assert.False(original.Equals(clone)); - Assert.False(ReferenceEquals(original, clone)); - - Assert.False(original.Equals(clone.CyclicReference)); - Assert.False(ReferenceEquals(original, clone.CyclicReference)); - - Assert.False(original.CyclicReference?.Equals(clone.CyclicReference)); - Assert.False(ReferenceEquals(original.CyclicReference, clone.CyclicReference)); - - Assert.True(original.Equals(TestReferenceWithoutSystemTypes.StaticCyclicReference)); - Assert.True(ReferenceEquals(original, TestReferenceWithoutSystemTypes.StaticCyclicReference)); - Assert.False(clone.Equals(TestReferenceWithoutSystemTypes.StaticCyclicReference)); - Assert.False(ReferenceEquals(clone, TestReferenceWithoutSystemTypes.StaticCyclicReference)); - - /* - * Nullable - */ - Assert.Null(clone.NullableInt); - Assert.Null(clone.NullableReference); - Assert.True(clone.CollectionOfNulls?.All(z => z == null)); - Assert.True(CheckArray(clone.ArrayOfNulls, null)); + return false; } - private static bool CheckArray(Array? array, object? value) + foreach (var item in array) { - if (array == null) + if (item != value) { return false; } + } - foreach (var item in array) - { - if (item != value) - { - return false; - } - } + return true; + } - return true; + private static bool CheckValueArraySequentially(Array? array, Array? compareWith) + where T : struct + { + if (array == null || compareWith == null) + { + return false; } - [SuppressMessage("Analysis", "CA1508", Justification = "Analyzer error")] - private static bool CheckValueArraySequentially(Array? array, Array? compareWith) - where T : struct + for (var i = 0; i < array.Length; ++i) { - if (array == null || compareWith == null) + var left = (T?)array.GetValue(i); + var right = (T?)compareWith.GetValue(i); + + if (left == null + || right == null + || !((T)left).Equals((T)right)) { return false; } + } - for (var i = 0; i < array.Length; ++i) - { - var left = (T?)array.GetValue(i); - var right = (T?)compareWith.GetValue(i); - - if (left == null - || right == null - || !((T)left).Equals((T)right)) - { - return false; - } - } + return true; + } - return true; + private static bool CheckReferenceArraySequentially(Array? array, Array? compareWith) + where T : class + { + if (array == null || compareWith == null) + { + return false; } - private static bool CheckReferenceArraySequentially(Array? array, Array? compareWith) - where T : class + for (var i = 0; i < array.Length; ++i) { - if (array == null || compareWith == null) - { - return false; - } + var left = (T?)array.GetValue(i); + var right = (T?)compareWith.GetValue(i); - for (var i = 0; i < array.Length; ++i) + if (left == null + || right == null + || !left.Equals(right)) { - var left = (T?)array.GetValue(i); - var right = (T?)compareWith.GetValue(i); - - if (left == null - || right == null - || !left.Equals(right)) - { - return false; - } + return false; } - - return true; } + + return true; } } \ No newline at end of file diff --git a/tests/Tests/Basics.Test/OrderByDependencyTestData.cs b/tests/Tests/Basics.Test/OrderByDependencyTestData.cs deleted file mode 100644 index 42e9b364..00000000 --- a/tests/Tests/Basics.Test/OrderByDependencyTestData.cs +++ /dev/null @@ -1,64 +0,0 @@ -namespace SpaceEngineers.Core.Basics.Test -{ - using Attributes; - - internal class OrderByDependencyTestData - { - /* - * non generic - */ - [After(typeof(DependencyTest2))] - internal class DependencyTest1 { } - - [After(typeof(DependencyTest3))] - internal class DependencyTest2 { } - - internal class DependencyTest3 { } - - [Before(typeof(DependencyTest3))] - internal class DependencyTest4 { } - - /* - * weakly typed - */ - [After("SpaceEngineers.Core.Basics.Test SpaceEngineers.Core.Basics.Test.OrderByDependencyTestData+WeakDependencyTest2")] - internal class WeakDependencyTest1 { } - - [After("SpaceEngineers.Core.Basics.Test SpaceEngineers.Core.Basics.Test.OrderByDependencyTestData+WeakDependencyTest3")] - internal class WeakDependencyTest2 { } - - internal class WeakDependencyTest3 { } - - [Before("SpaceEngineers.Core.Basics.Test SpaceEngineers.Core.Basics.Test.OrderByDependencyTestData+WeakDependencyTest3")] - internal class WeakDependencyTest4 { } - - /* - * generic - */ - [After(typeof(GenericDependencyTest2<>))] - internal class GenericDependencyTest1 { } - - [After(typeof(GenericDependencyTest3<>))] - internal class GenericDependencyTest2 { } - - internal class GenericDependencyTest3 { } - - [Before(typeof(GenericDependencyTest3<>))] - internal class GenericDependencyTest4 { } - - /* - * cycle dependency - */ - [After(typeof(CycleDependencyTest2))] - internal class CycleDependencyTest1 { } - - [After(typeof(CycleDependencyTest3))] - internal class CycleDependencyTest2 { } - - internal class CycleDependencyTest3 { } - - [Before(typeof(CycleDependencyTest3))] - [After(typeof(CycleDependencyTest1))] - internal class CycleDependencyTest4 { } - } -} \ No newline at end of file diff --git a/tests/Tests/Basics.Test/StreamExtensionsTest.cs b/tests/Tests/Basics.Test/StreamExtensionsTest.cs index 6958679c..6f0fac3f 100644 --- a/tests/Tests/Basics.Test/StreamExtensionsTest.cs +++ b/tests/Tests/Basics.Test/StreamExtensionsTest.cs @@ -1,99 +1,93 @@ -namespace SpaceEngineers.Core.Basics.Test +namespace SpaceEngineers.Core.Basics.Test; + +using System; +using System.Globalization; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +public class StreamExtensionsTest : BasicsTestBase { - using System; - using System.Globalization; - using System.Text; - using System.Threading; - using System.Threading.Tasks; - using Xunit; - using Xunit.Abstractions; - - /// - /// StreamExtensions class test - /// - public class StreamExtensionsTest : BasicsTestBase - { - /// .ctor - /// ITestOutputHelper - public StreamExtensionsTest(ITestOutputHelper output) - : base(output) { } + public StreamExtensionsTest(ITestOutputHelper output) + : base(output) { } - [Fact] - internal void OverwriteTest() - { - const string str1 = "Hello world!"; - const string str2 = "Hello!"; + [Fact] + internal void OverwriteTest() + { + const string str1 = "Hello world!"; + const string str2 = "Hello!"; - Assert.NotEqual(str1, str2); + Assert.NotEqual(str1, str2); - var encoding = Encoding.UTF8; + var encoding = Encoding.UTF8; - ReadOnlySpan bytes = encoding.GetBytes(str1); + ReadOnlySpan bytes = encoding.GetBytes(str1); - using (var stream = bytes.AsMemoryStream()) - { - var read = stream.AsString(encoding); - Assert.Equal(str1, read); + using (var stream = bytes.AsMemoryStream()) + { + var read = stream.AsString(encoding); + Assert.Equal(str1, read); - stream.Overwrite(encoding.GetBytes(str2)); + stream.Overwrite(encoding.GetBytes(str2)); - read = stream.AsString(encoding); - Assert.Equal(str2, read); - } + read = stream.AsString(encoding); + Assert.Equal(str2, read); } + } - [Fact] - internal async Task OverwriteAsyncTest() - { - const string str1 = "Hello world!"; - const string str2 = "Hello!"; + [Fact] + internal async Task OverwriteAsyncTest() + { + const string str1 = "Hello world!"; + const string str2 = "Hello!"; - Assert.NotEqual(str1, str2); + Assert.NotEqual(str1, str2); - var encoding = Encoding.UTF8; + var encoding = Encoding.UTF8; - ReadOnlyMemory bytes = encoding.GetBytes(str1); + ReadOnlyMemory bytes = encoding.GetBytes(str1); - using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3))) - using (var stream = bytes.Span.AsMemoryStream()) - { - var read = await stream.AsString(encoding, cts.Token).ConfigureAwait(false); - Assert.Equal(str1, read); + using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3))) + using (var stream = bytes.Span.AsMemoryStream()) + { + var read = await stream.AsString(encoding, cts.Token).ConfigureAwait(false); + Assert.Equal(str1, read); - await stream.Overwrite(encoding.GetBytes(str2), cts.Token).ConfigureAwait(false); + await stream.Overwrite(encoding.GetBytes(str2), cts.Token).ConfigureAwait(false); - read = await stream.AsString(encoding, cts.Token).ConfigureAwait(false); - Assert.Equal(str2, read); - } + read = await stream.AsString(encoding, cts.Token).ConfigureAwait(false); + Assert.Equal(str2, read); } + } - [Fact] - internal void CompressionTest() - { - var sb = new StringBuilder(); + [Fact] + internal void CompressionTest() + { + var sb = new StringBuilder(); - for (var i = 0; i < 42; i++) - { - sb.Append(nameof(CompressionTest)); - } + for (var i = 0; i < 42; i++) + { + sb.Append(nameof(CompressionTest)); + } - var repeatedString = sb.ToString(); - ReadOnlySpan bytes = Encoding.UTF8.GetBytes(repeatedString); + var repeatedString = sb.ToString(); + ReadOnlySpan bytes = Encoding.UTF8.GetBytes(repeatedString); - ReadOnlySpan compressedBytes = bytes.Compress().Span; + ReadOnlySpan compressedBytes = bytes.Compress().Span; - Output.WriteLine(bytes.Length.ToString(CultureInfo.InvariantCulture)); - Output.WriteLine(compressedBytes.Length.ToString(CultureInfo.InvariantCulture)); - Assert.True(bytes.Length > compressedBytes.Length); + Output.WriteLine(bytes.Length.ToString(CultureInfo.InvariantCulture)); + Output.WriteLine(compressedBytes.Length.ToString(CultureInfo.InvariantCulture)); + Assert.True(bytes.Length > compressedBytes.Length); - ReadOnlySpan decompressedBytes = compressedBytes.Decompress().Span; + ReadOnlySpan decompressedBytes = compressedBytes.Decompress().Span; - Output.WriteLine(decompressedBytes.Length.ToString(CultureInfo.InvariantCulture)); - Assert.Equal(bytes.Length, decompressedBytes.Length); + Output.WriteLine(decompressedBytes.Length.ToString(CultureInfo.InvariantCulture)); + Assert.Equal(bytes.Length, decompressedBytes.Length); - var decompressedRepeatedString = Encoding.UTF8.GetString(decompressedBytes); + var decompressedRepeatedString = Encoding.UTF8.GetString(decompressedBytes); - Assert.Equal(repeatedString, decompressedRepeatedString); - } + Assert.Equal(repeatedString, decompressedRepeatedString); } } \ No newline at end of file diff --git a/tests/Tests/Basics.Test/StringExtensionsTest.cs b/tests/Tests/Basics.Test/StringExtensionsTest.cs index e0f583f4..4b6d93f6 100644 --- a/tests/Tests/Basics.Test/StringExtensionsTest.cs +++ b/tests/Tests/Basics.Test/StringExtensionsTest.cs @@ -1,23 +1,17 @@ -namespace SpaceEngineers.Core.Basics.Test +namespace SpaceEngineers.Core.Basics.Test; + +using Xunit; +using Xunit.Abstractions; + +public class StringExtensionsTest : BasicsTestBase { - using Xunit; - using Xunit.Abstractions; + public StringExtensionsTest(ITestOutputHelper output) + : base(output) { } - /// - /// StringExtensions class test - /// - public class StringExtensionsTest : BasicsTestBase + [Theory] + [InlineData("qwerty", "Qwerty")] + internal void StartFromCapitalLetterTest(string source, string expected) { - /// .ctor - /// ITestOutputHelper - public StringExtensionsTest(ITestOutputHelper output) - : base(output) { } - - [Theory] - [InlineData("qwerty", "Qwerty")] - internal void StartFromCapitalLetterTest(string source, string expected) - { - Assert.Equal(expected, source.StartFromCapitalLetter()); - } + Assert.Equal(expected, source.StartFromCapitalLetter()); } } \ No newline at end of file diff --git a/tests/Tests/Basics.Test/TestExtensions.cs b/tests/Tests/Basics.Test/TestExtensions.cs index 09ddd258..2c0048f1 100644 --- a/tests/Tests/Basics.Test/TestExtensions.cs +++ b/tests/Tests/Basics.Test/TestExtensions.cs @@ -1,24 +1,16 @@ -namespace SpaceEngineers.Core.Basics.Test -{ - using Xunit.Sdk; +namespace SpaceEngineers.Core.Basics.Test; + +using Xunit.Sdk; - /// - /// extensions for tests - /// - public static class TestExtensions +public static class TestExtensions +{ + public static FalseException FalseException() { - /// Create FalseException - /// FalseException - public static FalseException FalseException() - { - return Xunit.Sdk.FalseException.ForNonFalseValue(nameof(FalseException), null); - } + return Xunit.Sdk.FalseException.ForNonFalseValue(nameof(FalseException), null); + } - /// Create TrueException - /// TrueException - public static TrueException TrueException() - { - return Xunit.Sdk.TrueException.ForNonTrueValue(nameof(TrueException), null); - } + public static TrueException TrueException() + { + return Xunit.Sdk.TrueException.ForNonTrueValue(nameof(TrueException), null); } } \ No newline at end of file diff --git a/tests/Tests/Basics.Test/TestRecord.cs b/tests/Tests/Basics.Test/TestRecord.cs index 036b9be7..70cb2815 100644 --- a/tests/Tests/Basics.Test/TestRecord.cs +++ b/tests/Tests/Basics.Test/TestRecord.cs @@ -1,7 +1,6 @@ -namespace SpaceEngineers.Core.Basics.Test +namespace SpaceEngineers.Core.Basics.Test; + +internal record TestRecord { - internal record TestRecord - { - public string StringValue { get; init; } = default!; - } + public string StringValue { get; init; } = default!; } \ No newline at end of file diff --git a/tests/Tests/Basics.Test/TypeExtensionsTest.cs b/tests/Tests/Basics.Test/TypeExtensionsTest.cs index 7f5af598..619547ea 100644 --- a/tests/Tests/Basics.Test/TypeExtensionsTest.cs +++ b/tests/Tests/Basics.Test/TypeExtensionsTest.cs @@ -1,364 +1,290 @@ -namespace SpaceEngineers.Core.Basics.Test +namespace SpaceEngineers.Core.Basics.Test; + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Basics; +using Xunit; +using Xunit.Abstractions; +using SynchronizationPrimitives; + +[SuppressMessage("Analysis", "SA1201", Justification = "For test reasons")] +public class TypeExtensionsTest : BasicsTestBase { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Linq; - using Basics; - using Xunit; - using Xunit.Abstractions; - - /// - /// TypeExtensions class tests - /// - [SuppressMessage("Analysis", "SA1201", Justification = "For test reasons")] - public class TypeExtensionsTest : BasicsTestBase + public TypeExtensionsTest(ITestOutputHelper output) + : base(output) { - /// .ctor - /// ITestOutputHelper - public TypeExtensionsTest(ITestOutputHelper output) - : base(output) - { - } + } - [Theory] - [InlineData(typeof(object), false)] - [InlineData(typeof(string), false)] - [InlineData(typeof(int), false)] - [InlineData(typeof(KeyValuePair), false)] - [InlineData(typeof(TestRecord), true)] - internal void IsRecordTest(Type type, bool result) - { - Assert.Equal(result, type.IsRecord()); - } + [Theory] + [InlineData(typeof(object), false)] + [InlineData(typeof(string), false)] + [InlineData(typeof(int), false)] + [InlineData(typeof(KeyValuePair), false)] + [InlineData(typeof(TestRecord), true)] + internal void IsRecordTest(Type type, bool result) + { + Assert.Equal(result, type.IsRecord()); + } - [Theory] - [InlineData(typeof(string))] - [InlineData(typeof(SpaceEngineers.Core.Basics.Primitives.AsyncManualResetEvent))] - [InlineData(typeof(SpaceEngineers.Core.Basics.Primitives.PriorityQueue<,>))] - [InlineData(typeof(SpaceEngineers.Core.Basics.Primitives.PriorityQueue))] - [InlineData(typeof(SpaceEngineers.Core.Basics.Primitives.PriorityQueue, string>))] - [InlineData(typeof(SpaceEngineers.Core.Basics.Primitives.PriorityQueue>, string>))] - [InlineData(typeof(SpaceEngineers.Core.Basics.Primitives.PriorityQueue, string>))] - internal void FindTypeByFullNameTest(Type type) - { - var typeNode = TypeNode.FromType(type); + [Theory] + [InlineData(typeof(string))] + [InlineData(typeof(AsyncManualResetEvent))] + [InlineData(typeof(PriorityQueue<,>))] + [InlineData(typeof(PriorityQueue))] + [InlineData(typeof(PriorityQueue, string>))] + [InlineData(typeof(PriorityQueue>, string>))] + [InlineData(typeof(PriorityQueue, string>))] + internal void FindTypeByFullNameTest(Type type) + { + var typeNode = TypeNode.FromType(type); - Output.WriteLine(typeNode.ToString()); + Output.WriteLine(typeNode.ToString()); - typeNode = TypeNode.FromString(typeNode.ToString()); + typeNode = TypeNode.FromString(typeNode.ToString()); - Type builtType = typeNode; + Type builtType = typeNode; - Assert.Equal(type, builtType); - } + Assert.Equal(type, builtType); + } - [Fact] - internal void OrderByDependencyCycleDependencyTest() - { - var test1 = new[] - { - typeof(OrderByDependencyTestData.CycleDependencyTest1), - typeof(OrderByDependencyTestData.CycleDependencyTest2), - typeof(OrderByDependencyTestData.CycleDependencyTest3) - }; - - Assert.Throws(() => test1.OrderByDependencies().ToArray()); - - var test2 = new[] - { - typeof(OrderByDependencyTestData.CycleDependencyTest1), - typeof(OrderByDependencyTestData.CycleDependencyTest2), - typeof(OrderByDependencyTestData.CycleDependencyTest3), - typeof(OrderByDependencyTestData.CycleDependencyTest4) - }; - - Assert.Throws(() => test2.OrderByDependencies().ToArray()); - } + [Fact] + internal void IsNullableTest() + { + Assert.False(typeof(bool).IsNullable()); + Assert.True(typeof(bool?).IsNullable()); + Assert.True(typeof(Nullable<>).IsNullable()); - [Fact] - internal void OrderByDependencyTest() - { - var test1 = new[] - { - typeof(OrderByDependencyTestData.DependencyTest1), - typeof(OrderByDependencyTestData.DependencyTest2), - typeof(OrderByDependencyTestData.DependencyTest3), - typeof(OrderByDependencyTestData.DependencyTest4) - }; - - Assert.True(test1.Reverse().SequenceEqual(test1.OrderByDependencies())); - - var test2 = new[] - { - typeof(OrderByDependencyTestData.DependencyTest1), - typeof(OrderByDependencyTestData.DependencyTest2), - typeof(OrderByDependencyTestData.DependencyTest3), - typeof(OrderByDependencyTestData.DependencyTest4) - }; - - Assert.True(test2.Reverse().SequenceEqual(test2.OrderByDependencies())); - - var test3 = new[] - { - typeof(OrderByDependencyTestData.GenericDependencyTest1<>), - typeof(OrderByDependencyTestData.GenericDependencyTest2<>), - typeof(OrderByDependencyTestData.GenericDependencyTest3<>), - typeof(OrderByDependencyTestData.GenericDependencyTest4<>) - }; - - Assert.True(test3.Reverse().SequenceEqual(test3.OrderByDependencies())); - - var test4 = new[] - { - typeof(OrderByDependencyTestData.GenericDependencyTest1), - typeof(OrderByDependencyTestData.GenericDependencyTest2), - typeof(OrderByDependencyTestData.GenericDependencyTest3), - typeof(OrderByDependencyTestData.GenericDependencyTest4) - }; - - Assert.True(test4.Reverse().SequenceEqual(test4.OrderByDependencies())); - } + Assert.False(typeof(string).IsNullable()); + Assert.False(typeof(object).IsNullable()); - [Fact] - internal void IsNullableTest() - { - Assert.False(typeof(bool).IsNullable()); - Assert.True(typeof(bool?).IsNullable()); - Assert.True(typeof(Nullable<>).IsNullable()); + var nullableString = (string?)string.Empty; + var nullableObject = (object?)string.Empty; - Assert.False(typeof(string).IsNullable()); - Assert.False(typeof(object).IsNullable()); + Assert.False(nullableString.GetType().IsNullable()); + Assert.False(nullableObject.GetType().IsNullable()); + } - var nullableString = (string?)string.Empty; - var nullableObject = (object?)string.Empty; + [Fact] + internal void IsNullableMemberTest() + { + var notNullableValueField = typeof(ClassWithNullableMembers).GetField(nameof(ClassWithNullableMembers._notNullableValue)); + var nullableValueField = typeof(ClassWithNullableMembers).GetField(nameof(ClassWithNullableMembers._nullableValue)); + var notNullableReferenceField = typeof(ClassWithNullableMembers).GetField(nameof(ClassWithNullableMembers._notNullableReference)); + var nullableReferenceField = typeof(ClassWithNullableMembers).GetField(nameof(ClassWithNullableMembers._nullableReference)); + + Assert.NotNull(notNullableValueField); + Assert.NotNull(nullableValueField); + Assert.NotNull(notNullableReferenceField); + Assert.NotNull(nullableReferenceField); + + Assert.False(notNullableValueField!.IsNullable()); + Assert.True(nullableValueField!.IsNullable()); + Assert.False(notNullableReferenceField!.IsNullable()); + Assert.True(nullableReferenceField!.IsNullable()); + + var notNullableValueProperty = typeof(ClassWithNullableMembers).GetProperty(nameof(ClassWithNullableMembers.NotNullableValue)); + var nullableValueProperty = typeof(ClassWithNullableMembers).GetProperty(nameof(ClassWithNullableMembers.NullableValue)); + var notNullableReferenceProperty = typeof(ClassWithNullableMembers).GetProperty(nameof(ClassWithNullableMembers.NotNullableReference)); + var nullableReferenceProperty = typeof(ClassWithNullableMembers).GetProperty(nameof(ClassWithNullableMembers.NullableReference)); + + Assert.NotNull(notNullableValueProperty); + Assert.NotNull(nullableValueProperty); + Assert.NotNull(notNullableReferenceProperty); + Assert.NotNull(nullableReferenceProperty); + + Assert.False(notNullableValueProperty!.IsNullable()); + Assert.True(nullableValueProperty!.IsNullable()); + Assert.False(notNullableReferenceProperty!.IsNullable()); + Assert.True(nullableReferenceProperty!.IsNullable()); + } - Assert.False(nullableString.GetType().IsNullable()); - Assert.False(nullableObject.GetType().IsNullable()); - } + [Fact] + internal void IsSubclassOfOpenGenericTest() + { + // ITestInterface + Assert.False(typeof(ITestGenericInterfaceBase<>).IsSubclassOfOpenGeneric(typeof(ITestInterface))); + Assert.False(typeof(TestTypeImplementation).IsSubclassOfOpenGeneric(typeof(ITestInterface))); + Assert.False(typeof(ITestGenericInterface<>).IsSubclassOfOpenGeneric(typeof(ITestInterface))); + Assert.False(typeof(TestGenericTypeImplementationBase<>).IsSubclassOfOpenGeneric(typeof(ITestInterface))); + + // ITestGenericInterfaceBase + Assert.True(typeof(ITestGenericInterfaceBase<>).IsSubclassOfOpenGeneric(typeof(ITestGenericInterfaceBase<>))); + Assert.True(typeof(ITestGenericInterface<>).IsSubclassOfOpenGeneric(typeof(ITestGenericInterfaceBase<>))); + Assert.True(typeof(TestGenericTypeImplementationBase<>).IsSubclassOfOpenGeneric(typeof(ITestGenericInterfaceBase<>))); + Assert.True(typeof(TestTypeImplementation).IsSubclassOfOpenGeneric(typeof(ITestGenericInterfaceBase<>))); + Assert.True(typeof(TestGenericTypeImplementation<>).IsSubclassOfOpenGeneric(typeof(ITestGenericInterfaceBase<>))); + + // ITestGenericInterface + Assert.False(typeof(ITestGenericInterface<>).IsSubclassOfOpenGeneric(typeof(ITestGenericInterface))); + Assert.False(typeof(TestGenericTypeImplementationBase<>).IsSubclassOfOpenGeneric(typeof(ITestGenericInterface))); + Assert.False(typeof(TestTypeImplementation).IsSubclassOfOpenGeneric(typeof(ITestGenericInterface))); + Assert.False(typeof(TestGenericTypeImplementation<>).IsSubclassOfOpenGeneric(typeof(ITestGenericInterface))); + } - [Fact] - internal void IsNullableMemberTest() - { - var notNullableValueField = typeof(ClassWithNullableMembers).GetField(nameof(ClassWithNullableMembers._notNullableValue)); - var nullableValueField = typeof(ClassWithNullableMembers).GetField(nameof(ClassWithNullableMembers._nullableValue)); - var notNullableReferenceField = typeof(ClassWithNullableMembers).GetField(nameof(ClassWithNullableMembers._notNullableReference)); - var nullableReferenceField = typeof(ClassWithNullableMembers).GetField(nameof(ClassWithNullableMembers._nullableReference)); - - Assert.NotNull(notNullableValueField); - Assert.NotNull(nullableValueField); - Assert.NotNull(notNullableReferenceField); - Assert.NotNull(nullableReferenceField); - - Assert.False(notNullableValueField!.IsNullable()); - Assert.True(nullableValueField!.IsNullable()); - Assert.False(notNullableReferenceField!.IsNullable()); - Assert.True(nullableReferenceField!.IsNullable()); - - var notNullableValueProperty = typeof(ClassWithNullableMembers).GetProperty(nameof(ClassWithNullableMembers.NotNullableValue)); - var nullableValueProperty = typeof(ClassWithNullableMembers).GetProperty(nameof(ClassWithNullableMembers.NullableValue)); - var notNullableReferenceProperty = typeof(ClassWithNullableMembers).GetProperty(nameof(ClassWithNullableMembers.NotNullableReference)); - var nullableReferenceProperty = typeof(ClassWithNullableMembers).GetProperty(nameof(ClassWithNullableMembers.NullableReference)); - - Assert.NotNull(notNullableValueProperty); - Assert.NotNull(nullableValueProperty); - Assert.NotNull(notNullableReferenceProperty); - Assert.NotNull(nullableReferenceProperty); - - Assert.False(notNullableValueProperty!.IsNullable()); - Assert.True(nullableValueProperty!.IsNullable()); - Assert.False(notNullableReferenceProperty!.IsNullable()); - Assert.True(nullableReferenceProperty!.IsNullable()); - } + [Fact] + internal void IsContainsInterfaceDeclarationTest() + { + Assert.True(typeof(ITestGenericInterfaceBase).IsContainsInterfaceDeclaration(typeof(ITestInterface))); + Assert.False(typeof(ITestGenericInterface).IsContainsInterfaceDeclaration(typeof(ITestInterface))); - [Fact] - internal void IsSubclassOfOpenGenericTest() - { - // ITestInterface - Assert.False(typeof(ITestGenericInterfaceBase<>).IsSubclassOfOpenGeneric(typeof(ITestInterface))); - Assert.False(typeof(TestTypeImplementation).IsSubclassOfOpenGeneric(typeof(ITestInterface))); - Assert.False(typeof(ITestGenericInterface<>).IsSubclassOfOpenGeneric(typeof(ITestInterface))); - Assert.False(typeof(TestGenericTypeImplementationBase<>).IsSubclassOfOpenGeneric(typeof(ITestInterface))); - - // ITestGenericInterfaceBase - Assert.True(typeof(ITestGenericInterfaceBase<>).IsSubclassOfOpenGeneric(typeof(ITestGenericInterfaceBase<>))); - Assert.True(typeof(ITestGenericInterface<>).IsSubclassOfOpenGeneric(typeof(ITestGenericInterfaceBase<>))); - Assert.True(typeof(TestGenericTypeImplementationBase<>).IsSubclassOfOpenGeneric(typeof(ITestGenericInterfaceBase<>))); - Assert.True(typeof(TestTypeImplementation).IsSubclassOfOpenGeneric(typeof(ITestGenericInterfaceBase<>))); - Assert.True(typeof(TestGenericTypeImplementation<>).IsSubclassOfOpenGeneric(typeof(ITestGenericInterfaceBase<>))); - - // ITestGenericInterface - Assert.False(typeof(ITestGenericInterface<>).IsSubclassOfOpenGeneric(typeof(ITestGenericInterface))); - Assert.False(typeof(TestGenericTypeImplementationBase<>).IsSubclassOfOpenGeneric(typeof(ITestGenericInterface))); - Assert.False(typeof(TestTypeImplementation).IsSubclassOfOpenGeneric(typeof(ITestGenericInterface))); - Assert.False(typeof(TestGenericTypeImplementation<>).IsSubclassOfOpenGeneric(typeof(ITestGenericInterface))); - } + Assert.False(typeof(ITestInterface).IsContainsInterfaceDeclaration(typeof(ITestInterface))); + Assert.False(typeof(TestTypeImplementation).IsContainsInterfaceDeclaration(typeof(ITestInterface))); - [Fact] - internal void IsContainsInterfaceDeclarationTest() - { - Assert.True(typeof(ITestGenericInterfaceBase).IsContainsInterfaceDeclaration(typeof(ITestInterface))); - Assert.False(typeof(ITestGenericInterface).IsContainsInterfaceDeclaration(typeof(ITestInterface))); + Assert.False(typeof(TestGenericTypeImplementationBase).IsContainsInterfaceDeclaration(typeof(ITestGenericInterface))); + Assert.False(typeof(ITestGenericInterface).IsContainsInterfaceDeclaration(typeof(ITestGenericInterfaceBase))); + Assert.False(typeof(DirectTestTypeImplementation).IsContainsInterfaceDeclaration(typeof(ITestGenericInterface))); - Assert.False(typeof(ITestInterface).IsContainsInterfaceDeclaration(typeof(ITestInterface))); - Assert.False(typeof(TestTypeImplementation).IsContainsInterfaceDeclaration(typeof(ITestInterface))); + Assert.True(typeof(TestGenericTypeImplementationBase).IsContainsInterfaceDeclaration(typeof(ITestGenericInterface))); + Assert.True(typeof(ITestGenericInterface).IsContainsInterfaceDeclaration(typeof(ITestGenericInterfaceBase))); + Assert.True(typeof(DirectTestTypeImplementation).IsContainsInterfaceDeclaration(typeof(ITestGenericInterface))); - Assert.False(typeof(TestGenericTypeImplementationBase).IsContainsInterfaceDeclaration(typeof(ITestGenericInterface))); - Assert.False(typeof(ITestGenericInterface).IsContainsInterfaceDeclaration(typeof(ITestGenericInterfaceBase))); - Assert.False(typeof(DirectTestTypeImplementation).IsContainsInterfaceDeclaration(typeof(ITestGenericInterface))); + Assert.False(typeof(TestGenericTypeImplementationBase).IsContainsInterfaceDeclaration(typeof(ITestGenericInterface<>))); + Assert.False(typeof(ITestGenericInterface).IsContainsInterfaceDeclaration(typeof(ITestGenericInterfaceBase<>))); + Assert.False(typeof(DirectTestTypeImplementation).IsContainsInterfaceDeclaration(typeof(ITestGenericInterface<>))); + } - Assert.True(typeof(TestGenericTypeImplementationBase).IsContainsInterfaceDeclaration(typeof(ITestGenericInterface))); - Assert.True(typeof(ITestGenericInterface).IsContainsInterfaceDeclaration(typeof(ITestGenericInterfaceBase))); - Assert.True(typeof(DirectTestTypeImplementation).IsContainsInterfaceDeclaration(typeof(ITestGenericInterface))); + [Fact] + internal void ExtractGenericArgumentsAtTest() + { + Assert.Equal(typeof(string), typeof(ITestGenericInterface).ExtractGenericArgumentsAt(typeof(ITestGenericInterfaceBase<>), 0).Single()); + Assert.Equal(typeof(object), typeof(TestTypeImplementation).ExtractGenericArgumentsAt(typeof(ITestGenericInterfaceBase<>), 0).Single()); + Assert.Equal(typeof(object), typeof(DirectTestTypeImplementation).ExtractGenericArgumentsAt(typeof(ITestGenericInterfaceBase<>), 0).Single()); + Assert.Equal(typeof(bool), typeof(TestGenericTypeImplementation).ExtractGenericArgumentsAt(typeof(TestGenericTypeImplementationBase<>), 0).Single()); + + Assert.Equal(typeof(bool), typeof(ClosedImplementation).ExtractGenericArgumentsAt(typeof(ITestInterface<,>), 0).Single()); + Assert.Equal(typeof(object), typeof(ClosedImplementation).ExtractGenericArgumentsAt(typeof(ITestInterface<,>), 1).Single()); + Assert.Equal("T1", typeof(OpenedImplementation<,>).ExtractGenericArgumentsAt(typeof(ITestInterface<,>), 0).Single().Name); + Assert.Equal("T2", typeof(OpenedImplementation<,>).ExtractGenericArgumentsAt(typeof(ITestInterface<,>), 1).Single().Name); + Assert.Equal("T1", typeof(HalfOpenedImplementation<>).ExtractGenericArgumentsAt(typeof(ITestInterface<,>), 0).Single().Name); + Assert.Equal(typeof(object), typeof(HalfOpenedImplementation<>).ExtractGenericArgumentsAt(typeof(ITestInterface<,>), 1).Single()); + Assert.True(new List { typeof(bool), typeof(string) }.SequenceEqual(typeof(SeveralImplementations).ExtractGenericArgumentsAt(typeof(ITestInterface<,>), 0).ToList())); + Assert.True(new List { typeof(object), typeof(int) }.SequenceEqual(typeof(SeveralImplementations).ExtractGenericArgumentsAt(typeof(ITestInterface<,>), 1).ToList())); + + Assert.Throws(() => typeof(ITestGenericInterface).ExtractGenericArgumentsAt(typeof(ITestGenericInterfaceBase), 0).Any()); + Assert.Throws(() => typeof(TestTypeImplementation).ExtractGenericArgumentsAt(typeof(ITestGenericInterfaceBase), 0).Any()); + Assert.Throws(() => typeof(DirectTestTypeImplementation).ExtractGenericArgumentsAt(typeof(ITestGenericInterfaceBase), 0).Any()); + Assert.Throws(() => typeof(TestGenericTypeImplementation).ExtractGenericArgumentsAt(typeof(TestGenericTypeImplementationBase), 0).Any()); + } - Assert.False(typeof(TestGenericTypeImplementationBase).IsContainsInterfaceDeclaration(typeof(ITestGenericInterface<>))); - Assert.False(typeof(ITestGenericInterface).IsContainsInterfaceDeclaration(typeof(ITestGenericInterfaceBase<>))); - Assert.False(typeof(DirectTestTypeImplementation).IsContainsInterfaceDeclaration(typeof(ITestGenericInterface<>))); - } + [Fact] + internal void FitsForTypeArgumentTest() + { + Assert.True(typeof(bool).FitsForTypeArgument(typeof(ITestInterface<,>).GetGenericArguments()[0])); + Assert.True(typeof(Enum).FitsForTypeArgument(typeof(ITestInterface<,>).GetGenericArguments()[0])); + Assert.True(typeof(StringSplitOptions).FitsForTypeArgument(typeof(ITestInterface<,>).GetGenericArguments()[0])); + Assert.True(typeof(StructWithParameter).FitsForTypeArgument(typeof(ITestInterface<,>).GetGenericArguments()[0])); + Assert.True(typeof(object).FitsForTypeArgument(typeof(ITestInterface<,>).GetGenericArguments()[0])); + Assert.True(typeof(ClassWithParameter).FitsForTypeArgument(typeof(ITestInterface<,>).GetGenericArguments()[0])); + + Assert.True(typeof(bool).FitsForTypeArgument(typeof(ITestInterface<,>).GetGenericArguments()[1])); + Assert.True(typeof(Enum).FitsForTypeArgument(typeof(ITestInterface<,>).GetGenericArguments()[1])); + Assert.True(typeof(StringSplitOptions).FitsForTypeArgument(typeof(ITestInterface<,>).GetGenericArguments()[1])); + Assert.True(typeof(StructWithParameter).FitsForTypeArgument(typeof(ITestInterface<,>).GetGenericArguments()[1])); + Assert.True(typeof(object).FitsForTypeArgument(typeof(ITestInterface<,>).GetGenericArguments()[1])); + Assert.True(typeof(ClassWithParameter).FitsForTypeArgument(typeof(ITestInterface<,>).GetGenericArguments()[1])); + + Assert.False(typeof(bool).FitsForTypeArgument(typeof(IClassConstrained<>).GetGenericArguments()[0])); + Assert.True(typeof(Enum).FitsForTypeArgument(typeof(IClassConstrained<>).GetGenericArguments()[0])); + Assert.False(typeof(StringSplitOptions).FitsForTypeArgument(typeof(IClassConstrained<>).GetGenericArguments()[0])); + Assert.False(typeof(StructWithParameter).FitsForTypeArgument(typeof(IClassConstrained<>).GetGenericArguments()[0])); + Assert.True(typeof(object).FitsForTypeArgument(typeof(IClassConstrained<>).GetGenericArguments()[0])); + Assert.True(typeof(ClassWithParameter).FitsForTypeArgument(typeof(IClassConstrained<>).GetGenericArguments()[0])); + + Assert.True(typeof(bool).FitsForTypeArgument(typeof(IStructConstrained<>).GetGenericArguments()[0])); + Assert.False(typeof(Enum).FitsForTypeArgument(typeof(IStructConstrained<>).GetGenericArguments()[0])); + Assert.True(typeof(StringSplitOptions).FitsForTypeArgument(typeof(IStructConstrained<>).GetGenericArguments()[0])); + Assert.True(typeof(StructWithParameter).FitsForTypeArgument(typeof(IStructConstrained<>).GetGenericArguments()[0])); + Assert.False(typeof(object).FitsForTypeArgument(typeof(IStructConstrained<>).GetGenericArguments()[0])); + Assert.False(typeof(ClassWithParameter).FitsForTypeArgument(typeof(IStructConstrained<>).GetGenericArguments()[0])); + + Assert.True(typeof(bool).FitsForTypeArgument(typeof(IDefaultCtorConstrained<>).GetGenericArguments()[0])); + Assert.False(typeof(Enum).FitsForTypeArgument(typeof(IDefaultCtorConstrained<>).GetGenericArguments()[0])); + Assert.True(typeof(StringSplitOptions).FitsForTypeArgument(typeof(IDefaultCtorConstrained<>).GetGenericArguments()[0])); + Assert.True(typeof(StructWithParameter).FitsForTypeArgument(typeof(IDefaultCtorConstrained<>).GetGenericArguments()[0])); + Assert.True(typeof(object).FitsForTypeArgument(typeof(IDefaultCtorConstrained<>).GetGenericArguments()[0])); + Assert.False(typeof(ClassWithParameter).FitsForTypeArgument(typeof(IDefaultCtorConstrained<>).GetGenericArguments()[0])); + + Assert.True(typeof(HalfOpenedImplementation).FitsForTypeArgument(typeof(ITestInterface))); + } - [Fact] - internal void ExtractGenericArgumentsAtTest() - { - Assert.Equal(typeof(string), typeof(ITestGenericInterface).ExtractGenericArgumentsAt(typeof(ITestGenericInterfaceBase<>), 0).Single()); - Assert.Equal(typeof(object), typeof(TestTypeImplementation).ExtractGenericArgumentsAt(typeof(ITestGenericInterfaceBase<>), 0).Single()); - Assert.Equal(typeof(object), typeof(DirectTestTypeImplementation).ExtractGenericArgumentsAt(typeof(ITestGenericInterfaceBase<>), 0).Single()); - Assert.Equal(typeof(bool), typeof(TestGenericTypeImplementation).ExtractGenericArgumentsAt(typeof(TestGenericTypeImplementationBase<>), 0).Single()); - - Assert.Equal(typeof(bool), typeof(ClosedImplementation).ExtractGenericArgumentsAt(typeof(ITestInterface<,>), 0).Single()); - Assert.Equal(typeof(object), typeof(ClosedImplementation).ExtractGenericArgumentsAt(typeof(ITestInterface<,>), 1).Single()); - Assert.Equal("T1", typeof(OpenedImplementation<,>).ExtractGenericArgumentsAt(typeof(ITestInterface<,>), 0).Single().Name); - Assert.Equal("T2", typeof(OpenedImplementation<,>).ExtractGenericArgumentsAt(typeof(ITestInterface<,>), 1).Single().Name); - Assert.Equal("T1", typeof(HalfOpenedImplementation<>).ExtractGenericArgumentsAt(typeof(ITestInterface<,>), 0).Single().Name); - Assert.Equal(typeof(object), typeof(HalfOpenedImplementation<>).ExtractGenericArgumentsAt(typeof(ITestInterface<,>), 1).Single()); - Assert.True(new List { typeof(bool), typeof(string) }.SequenceEqual(typeof(SeveralImplementations).ExtractGenericArgumentsAt(typeof(ITestInterface<,>), 0).ToList())); - Assert.True(new List { typeof(object), typeof(int) }.SequenceEqual(typeof(SeveralImplementations).ExtractGenericArgumentsAt(typeof(ITestInterface<,>), 1).ToList())); - - Assert.Throws(() => typeof(ITestGenericInterface).ExtractGenericArgumentsAt(typeof(ITestGenericInterfaceBase), 0).Any()); - Assert.Throws(() => typeof(TestTypeImplementation).ExtractGenericArgumentsAt(typeof(ITestGenericInterfaceBase), 0).Any()); - Assert.Throws(() => typeof(DirectTestTypeImplementation).ExtractGenericArgumentsAt(typeof(ITestGenericInterfaceBase), 0).Any()); - Assert.Throws(() => typeof(TestGenericTypeImplementation).ExtractGenericArgumentsAt(typeof(TestGenericTypeImplementationBase), 0).Any()); - } + private interface ITestInterface { } - [Fact] - internal void FitsForTypeArgumentTest() - { - Assert.True(typeof(bool).FitsForTypeArgument(typeof(ITestInterface<,>).GetGenericArguments()[0])); - Assert.True(typeof(Enum).FitsForTypeArgument(typeof(ITestInterface<,>).GetGenericArguments()[0])); - Assert.True(typeof(StringSplitOptions).FitsForTypeArgument(typeof(ITestInterface<,>).GetGenericArguments()[0])); - Assert.True(typeof(StructWithParameter).FitsForTypeArgument(typeof(ITestInterface<,>).GetGenericArguments()[0])); - Assert.True(typeof(object).FitsForTypeArgument(typeof(ITestInterface<,>).GetGenericArguments()[0])); - Assert.True(typeof(ClassWithParameter).FitsForTypeArgument(typeof(ITestInterface<,>).GetGenericArguments()[0])); - - Assert.True(typeof(bool).FitsForTypeArgument(typeof(ITestInterface<,>).GetGenericArguments()[1])); - Assert.True(typeof(Enum).FitsForTypeArgument(typeof(ITestInterface<,>).GetGenericArguments()[1])); - Assert.True(typeof(StringSplitOptions).FitsForTypeArgument(typeof(ITestInterface<,>).GetGenericArguments()[1])); - Assert.True(typeof(StructWithParameter).FitsForTypeArgument(typeof(ITestInterface<,>).GetGenericArguments()[1])); - Assert.True(typeof(object).FitsForTypeArgument(typeof(ITestInterface<,>).GetGenericArguments()[1])); - Assert.True(typeof(ClassWithParameter).FitsForTypeArgument(typeof(ITestInterface<,>).GetGenericArguments()[1])); - - Assert.False(typeof(bool).FitsForTypeArgument(typeof(IClassConstrained<>).GetGenericArguments()[0])); - Assert.True(typeof(Enum).FitsForTypeArgument(typeof(IClassConstrained<>).GetGenericArguments()[0])); - Assert.False(typeof(StringSplitOptions).FitsForTypeArgument(typeof(IClassConstrained<>).GetGenericArguments()[0])); - Assert.False(typeof(StructWithParameter).FitsForTypeArgument(typeof(IClassConstrained<>).GetGenericArguments()[0])); - Assert.True(typeof(object).FitsForTypeArgument(typeof(IClassConstrained<>).GetGenericArguments()[0])); - Assert.True(typeof(ClassWithParameter).FitsForTypeArgument(typeof(IClassConstrained<>).GetGenericArguments()[0])); - - Assert.True(typeof(bool).FitsForTypeArgument(typeof(IStructConstrained<>).GetGenericArguments()[0])); - Assert.False(typeof(Enum).FitsForTypeArgument(typeof(IStructConstrained<>).GetGenericArguments()[0])); - Assert.True(typeof(StringSplitOptions).FitsForTypeArgument(typeof(IStructConstrained<>).GetGenericArguments()[0])); - Assert.True(typeof(StructWithParameter).FitsForTypeArgument(typeof(IStructConstrained<>).GetGenericArguments()[0])); - Assert.False(typeof(object).FitsForTypeArgument(typeof(IStructConstrained<>).GetGenericArguments()[0])); - Assert.False(typeof(ClassWithParameter).FitsForTypeArgument(typeof(IStructConstrained<>).GetGenericArguments()[0])); - - Assert.True(typeof(bool).FitsForTypeArgument(typeof(IDefaultCtorConstrained<>).GetGenericArguments()[0])); - Assert.False(typeof(Enum).FitsForTypeArgument(typeof(IDefaultCtorConstrained<>).GetGenericArguments()[0])); - Assert.True(typeof(StringSplitOptions).FitsForTypeArgument(typeof(IDefaultCtorConstrained<>).GetGenericArguments()[0])); - Assert.True(typeof(StructWithParameter).FitsForTypeArgument(typeof(IDefaultCtorConstrained<>).GetGenericArguments()[0])); - Assert.True(typeof(object).FitsForTypeArgument(typeof(IDefaultCtorConstrained<>).GetGenericArguments()[0])); - Assert.False(typeof(ClassWithParameter).FitsForTypeArgument(typeof(IDefaultCtorConstrained<>).GetGenericArguments()[0])); - - Assert.True(typeof(HalfOpenedImplementation).FitsForTypeArgument(typeof(ITestInterface))); - } + private interface ITestInterface { } + + private interface ITestGenericInterfaceBase : ITestInterface { } + + private interface ITestGenericInterface : ITestGenericInterfaceBase, ITestInterface { } + + private abstract class TestGenericTypeImplementationBase : ITestGenericInterface { } - private interface ITestInterface { } + private class DirectTestTypeImplementation : ITestGenericInterface { } - private interface ITestInterface { } + private class TestTypeImplementation : TestGenericTypeImplementationBase { } - private interface ITestGenericInterfaceBase : ITestInterface { } + private class TestGenericTypeImplementation : TestGenericTypeImplementationBase { } - private interface ITestGenericInterface : ITestGenericInterfaceBase, ITestInterface { } + private class ClosedImplementation : ITestInterface { } - private abstract class TestGenericTypeImplementationBase : ITestGenericInterface { } + private class OpenedImplementation : ITestInterface { } - private class DirectTestTypeImplementation : ITestGenericInterface { } + private class HalfOpenedImplementation : ITestInterface { } - private class TestTypeImplementation : TestGenericTypeImplementationBase { } + private class SeveralImplementations : ITestInterface, ITestInterface { } - private class TestGenericTypeImplementation : TestGenericTypeImplementationBase { } + private interface IClassConstrained + where T : class { } - private class ClosedImplementation : ITestInterface { } + private interface IStructConstrained + where T : struct { } - private class OpenedImplementation : ITestInterface { } + private interface IDefaultCtorConstrained + where T : new() { } - private class HalfOpenedImplementation : ITestInterface { } + private class ClassWithParameter + { + public ClassWithParameter(object param) { } + } - private class SeveralImplementations : ITestInterface, ITestInterface { } + private struct StructWithParameter + { + public StructWithParameter(object param) { } + } - private interface IClassConstrained - where T : class { } + [SuppressMessage("Analysis", "SA1401", Justification = "For test reasons")] + private class ClassWithNullableMembers + { + public bool _notNullableValue; + + public bool? _nullableValue; - private interface IStructConstrained - where T : struct { } + public object _notNullableReference = null!; - private interface IDefaultCtorConstrained - where T : new() { } + public object? _nullableReference; - [SuppressMessage("Analysis", "CA1801", Justification = "For test reasons")] - private class ClassWithParameter + public bool NotNullableValue { - public ClassWithParameter(object param) { } + get => _notNullableValue; + set => _notNullableValue = value; } - [SuppressMessage("Analysis", "CA1801", Justification = "For test reasons")] - private struct StructWithParameter + public bool? NullableValue { - public StructWithParameter(object param) { } + get => _nullableValue; + set => _nullableValue = value; } - [SuppressMessage("Analysis", "SA1401", Justification = "For test reasons")] - private class ClassWithNullableMembers + public object NotNullableReference { - public bool _notNullableValue; - - public bool? _nullableValue; - - public object _notNullableReference = null!; - - public object? _nullableReference; - - public bool NotNullableValue - { - get => _notNullableValue; - set => _notNullableValue = value; - } - - public bool? NullableValue - { - get => _nullableValue; - set => _nullableValue = value; - } - - public object NotNullableReference - { - get => _notNullableReference; - set => _notNullableReference = value; - } + get => _notNullableReference; + set => _notNullableReference = value; + } - public object? NullableReference - { - get => _nullableReference; - set => _nullableReference = value; - } + public object? NullableReference + { + get => _nullableReference; + set => _nullableReference = value; } } } \ No newline at end of file diff --git a/tests/Tests/CompositionRoot.Test/OrderByDependenciesTest.cs b/tests/Tests/CompositionRoot.Test/OrderByDependenciesTest.cs new file mode 100644 index 00000000..8d2b0169 --- /dev/null +++ b/tests/Tests/CompositionRoot.Test/OrderByDependenciesTest.cs @@ -0,0 +1,71 @@ +namespace SpaceEngineers.Core.CompositionRoot.Test; + +public class OrderByDependenciesTest +{ + [Fact] + internal void OrderByDependencyCycleDependencyTest() + { + var test1 = new[] + { + typeof(OrderByDependencyTestData.CycleDependencyTest1), + typeof(OrderByDependencyTestData.CycleDependencyTest2), + typeof(OrderByDependencyTestData.CycleDependencyTest3) + }; + + Assert.Throws(() => test1.OrderByDependencies().ToArray()); + + var test2 = new[] + { + typeof(OrderByDependencyTestData.CycleDependencyTest1), + typeof(OrderByDependencyTestData.CycleDependencyTest2), + typeof(OrderByDependencyTestData.CycleDependencyTest3), + typeof(OrderByDependencyTestData.CycleDependencyTest4) + }; + + Assert.Throws(() => test2.OrderByDependencies().ToArray()); + } + + [Fact] + internal void OrderByDependencyTest() + { + var test1 = new[] + { + typeof(OrderByDependencyTestData.DependencyTest1), + typeof(OrderByDependencyTestData.DependencyTest2), + typeof(OrderByDependencyTestData.DependencyTest3), + typeof(OrderByDependencyTestData.DependencyTest4) + }; + + Assert.True(test1.Reverse().SequenceEqual(test1.OrderByDependencies())); + + var test2 = new[] + { + typeof(OrderByDependencyTestData.DependencyTest1), + typeof(OrderByDependencyTestData.DependencyTest2), + typeof(OrderByDependencyTestData.DependencyTest3), + typeof(OrderByDependencyTestData.DependencyTest4) + }; + + Assert.True(test2.Reverse().SequenceEqual(test2.OrderByDependencies())); + + var test3 = new[] + { + typeof(OrderByDependencyTestData.GenericDependencyTest1<>), + typeof(OrderByDependencyTestData.GenericDependencyTest2<>), + typeof(OrderByDependencyTestData.GenericDependencyTest3<>), + typeof(OrderByDependencyTestData.GenericDependencyTest4<>) + }; + + Assert.True(test3.Reverse().SequenceEqual(test3.OrderByDependencies())); + + var test4 = new[] + { + typeof(OrderByDependencyTestData.GenericDependencyTest1), + typeof(OrderByDependencyTestData.GenericDependencyTest2), + typeof(OrderByDependencyTestData.GenericDependencyTest3), + typeof(OrderByDependencyTestData.GenericDependencyTest4) + }; + + Assert.True(test4.Reverse().SequenceEqual(test4.OrderByDependencies())); + } +} \ No newline at end of file diff --git a/tests/Tests/CompositionRoot.Test/OrderByDependencyTestData.cs b/tests/Tests/CompositionRoot.Test/OrderByDependencyTestData.cs new file mode 100644 index 00000000..f147130b --- /dev/null +++ b/tests/Tests/CompositionRoot.Test/OrderByDependencyTestData.cs @@ -0,0 +1,61 @@ +namespace SpaceEngineers.Core.Basics.Test; + +internal class OrderByDependencyTestData +{ + /* + * non generic + */ + [After(typeof(DependencyTest2))] + internal class DependencyTest1 { } + + [After(typeof(DependencyTest3))] + internal class DependencyTest2 { } + + internal class DependencyTest3 { } + + [Before(typeof(DependencyTest3))] + internal class DependencyTest4 { } + + /* + * weakly typed + */ + [After("SpaceEngineers.Core.Basics.Test SpaceEngineers.Core.Basics.Test.OrderByDependencyTestData+WeakDependencyTest2")] + internal class WeakDependencyTest1 { } + + [After("SpaceEngineers.Core.Basics.Test SpaceEngineers.Core.Basics.Test.OrderByDependencyTestData+WeakDependencyTest3")] + internal class WeakDependencyTest2 { } + + internal class WeakDependencyTest3 { } + + [Before("SpaceEngineers.Core.Basics.Test SpaceEngineers.Core.Basics.Test.OrderByDependencyTestData+WeakDependencyTest3")] + internal class WeakDependencyTest4 { } + + /* + * generic + */ + [After(typeof(GenericDependencyTest2<>))] + internal class GenericDependencyTest1 { } + + [After(typeof(GenericDependencyTest3<>))] + internal class GenericDependencyTest2 { } + + internal class GenericDependencyTest3 { } + + [Before(typeof(GenericDependencyTest3<>))] + internal class GenericDependencyTest4 { } + + /* + * cycle dependency + */ + [After(typeof(CycleDependencyTest2))] + internal class CycleDependencyTest1 { } + + [After(typeof(CycleDependencyTest3))] + internal class CycleDependencyTest2 { } + + internal class CycleDependencyTest3 { } + + [Before(typeof(CycleDependencyTest3))] + [After(typeof(CycleDependencyTest1))] + internal class CycleDependencyTest4 { } +} \ No newline at end of file