From 026cd71441feace93a45d30de011a1c2c83f598d Mon Sep 17 00:00:00 2001 From: Getup98 <98alino98@gmail.com> Date: Sun, 19 Jan 2025 19:32:25 +0100 Subject: [PATCH 01/12] Upgrade to .net9.0 --- _build/_build.csproj | 2 +- .../Backdash.Benchmarks.Ping/Backdash.Benchmarks.Ping.csproj | 4 ++-- benchmarks/Backdash.Benchmarks/Backdash.Benchmarks.csproj | 4 ++-- global.json | 2 +- src/Backdash.Analyzers/Backdash.Analyzers.csproj | 4 ++-- src/Backdash.Utils/Backdash.Utils.csproj | 4 ++-- src/Backdash/Backdash.csproj | 4 ++-- tests/Backdash.Tests/Backdash.Tests.csproj | 2 +- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/_build/_build.csproj b/_build/_build.csproj index 937be89e..349c539f 100644 --- a/_build/_build.csproj +++ b/_build/_build.csproj @@ -1,7 +1,7 @@ Exe - net8.0 + net9.0 $(NoWarn);CS0649;CS0169;CS1591;CS1573 disable diff --git a/benchmarks/Backdash.Benchmarks.Ping/Backdash.Benchmarks.Ping.csproj b/benchmarks/Backdash.Benchmarks.Ping/Backdash.Benchmarks.Ping.csproj index 5fd943a2..426457ba 100644 --- a/benchmarks/Backdash.Benchmarks.Ping/Backdash.Benchmarks.Ping.csproj +++ b/benchmarks/Backdash.Benchmarks.Ping/Backdash.Benchmarks.Ping.csproj @@ -1,7 +1,7 @@ - + Exe - net8.0 + net9.0 enable enable CS1591,S125,S1199,CS016,S1144,S3903,CS159,S1104 diff --git a/benchmarks/Backdash.Benchmarks/Backdash.Benchmarks.csproj b/benchmarks/Backdash.Benchmarks/Backdash.Benchmarks.csproj index 8960fbea..94131335 100644 --- a/benchmarks/Backdash.Benchmarks/Backdash.Benchmarks.csproj +++ b/benchmarks/Backdash.Benchmarks/Backdash.Benchmarks.csproj @@ -1,7 +1,7 @@ - + Exe - net8.0 + net9.0 enable enable CS1591;S125;S1199;CS016;S1144;S3903;CS159;S1104;MSB3277 diff --git a/global.json b/global.json index d47cb3fb..aeceea9d 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.404", + "version": "9.0.101", "rollForward": "latestMinor", "allowPrerelease": false } diff --git a/src/Backdash.Analyzers/Backdash.Analyzers.csproj b/src/Backdash.Analyzers/Backdash.Analyzers.csproj index d77fc889..dcb7665e 100644 --- a/src/Backdash.Analyzers/Backdash.Analyzers.csproj +++ b/src/Backdash.Analyzers/Backdash.Analyzers.csproj @@ -1,6 +1,6 @@ - + - net8.0 + net9.0 true diff --git a/src/Backdash.Utils/Backdash.Utils.csproj b/src/Backdash.Utils/Backdash.Utils.csproj index 66fdbfdf..eaa0af83 100644 --- a/src/Backdash.Utils/Backdash.Utils.csproj +++ b/src/Backdash.Utils/Backdash.Utils.csproj @@ -1,7 +1,7 @@ - + - net8.0 + net9.0 enable true Backdash diff --git a/src/Backdash/Backdash.csproj b/src/Backdash/Backdash.csproj index 28da869d..0c7926c4 100644 --- a/src/Backdash/Backdash.csproj +++ b/src/Backdash/Backdash.csproj @@ -1,6 +1,6 @@ - + - net8.0 + net9.0 enable true Backdash diff --git a/tests/Backdash.Tests/Backdash.Tests.csproj b/tests/Backdash.Tests/Backdash.Tests.csproj index c2cb2a16..e124c7b4 100644 --- a/tests/Backdash.Tests/Backdash.Tests.csproj +++ b/tests/Backdash.Tests/Backdash.Tests.csproj @@ -1,6 +1,6 @@ - net8.0 + net9.0 enable enable false From b75dd7256e4b76e1e457b3513ae9c6fdf18836d6 Mon Sep 17 00:00:00 2001 From: Getup98 <98alino98@gmail.com> Date: Sun, 19 Jan 2025 19:46:48 +0100 Subject: [PATCH 02/12] Backdash.Analyzers visual studio support by targeting .netstandard2.0 --- src/Backdash.Analyzers/Backdash.Analyzers.csproj | 3 ++- src/Backdash.Analyzers/IsExternalInit.cs | 7 +++++++ src/Backdash.Analyzers/SourceGenerationHelper.cs | 2 +- src/Backdash/Backdash.csproj | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 src/Backdash.Analyzers/IsExternalInit.cs diff --git a/src/Backdash.Analyzers/Backdash.Analyzers.csproj b/src/Backdash.Analyzers/Backdash.Analyzers.csproj index dcb7665e..c66946e5 100644 --- a/src/Backdash.Analyzers/Backdash.Analyzers.csproj +++ b/src/Backdash.Analyzers/Backdash.Analyzers.csproj @@ -1,6 +1,7 @@ - net9.0 + netstandard2.0 + preview true diff --git a/src/Backdash.Analyzers/IsExternalInit.cs b/src/Backdash.Analyzers/IsExternalInit.cs new file mode 100644 index 00000000..ded2fabc --- /dev/null +++ b/src/Backdash.Analyzers/IsExternalInit.cs @@ -0,0 +1,7 @@ +namespace System.Runtime.CompilerServices +{ + /// + /// This is needed to make the analyzers work in .netstandard2.0 (so they are compatible with visual studio) + /// + internal static class IsExternalInit { } +} diff --git a/src/Backdash.Analyzers/SourceGenerationHelper.cs b/src/Backdash.Analyzers/SourceGenerationHelper.cs index c50c1139..fcbc1e17 100644 --- a/src/Backdash.Analyzers/SourceGenerationHelper.cs +++ b/src/Backdash.Analyzers/SourceGenerationHelper.cs @@ -189,7 +189,7 @@ static bool IsTypeArrayCopiable(ITypeSymbol type) { Debug.Assert(type != null); - if (!type.IsUnmanagedType || type is INamedTypeSymbol { EnumUnderlyingType: not null }) + if (!(type?.IsUnmanagedType ?? false) || type is INamedTypeSymbol { EnumUnderlyingType: not null }) return false; return type.SpecialType switch diff --git a/src/Backdash/Backdash.csproj b/src/Backdash/Backdash.csproj index 0c7926c4..61a072fc 100644 --- a/src/Backdash/Backdash.csproj +++ b/src/Backdash/Backdash.csproj @@ -21,7 +21,7 @@ - + From 84dfba4a374d371191290eff6cc9d30f4868c0af Mon Sep 17 00:00:00 2001 From: Getup98 <98alino98@gmail.com> Date: Sun, 19 Jan 2025 20:09:57 +0100 Subject: [PATCH 03/12] Override Equals and GetHashCode on InlineArrays --- src/Backdash/Core/LogInterpolatedStringHandler.cs | 13 +++++++++++++ .../Input/Confirmed/ConfirmedInputs.cs | 13 +++++++++++++ tests/Backdash.Tests/TestUtils/TestInput.cs | 13 +++++++++++++ 3 files changed, 39 insertions(+) diff --git a/src/Backdash/Core/LogInterpolatedStringHandler.cs b/src/Backdash/Core/LogInterpolatedStringHandler.cs index 3caee2d7..93b4fafb 100644 --- a/src/Backdash/Core/LogInterpolatedStringHandler.cs +++ b/src/Backdash/Core/LogInterpolatedStringHandler.cs @@ -124,4 +124,17 @@ struct LogStringBuffer #endif byte elemenet0; + + /// + public override readonly int GetHashCode() => Mem.GetHashCode(this); + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The to compare with the current object. + /// true if the specified object is equal to the current object; otherwise, false. + public readonly bool Equals(LogStringBuffer other) => this[..].SequenceEqual(other); + + /// + public override readonly bool Equals(object? obj) => obj is LogStringBuffer other && Equals(other); } diff --git a/src/Backdash/Synchronizing/Input/Confirmed/ConfirmedInputs.cs b/src/Backdash/Synchronizing/Input/Confirmed/ConfirmedInputs.cs index f99c874a..74d6cc6f 100644 --- a/src/Backdash/Synchronizing/Input/Confirmed/ConfirmedInputs.cs +++ b/src/Backdash/Synchronizing/Input/Confirmed/ConfirmedInputs.cs @@ -48,4 +48,17 @@ public struct InputArray where TInput : unmanaged public const int Capacity = Max.NumberOfPlayers; TInput element0; + + /// + public override readonly int GetHashCode() => Mem.GetHashCode(this); + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The to compare with the current object. + /// true if the specified object is equal to the current object; otherwise, false. + public readonly bool Equals(InputArray other) => this[..].SequenceEqual(other); + + /// + public override readonly bool Equals(object? obj) => obj is InputArray other && Equals(other); } diff --git a/tests/Backdash.Tests/TestUtils/TestInput.cs b/tests/Backdash.Tests/TestUtils/TestInput.cs index 1bd8d904..9f3f060f 100644 --- a/tests/Backdash.Tests/TestUtils/TestInput.cs +++ b/tests/Backdash.Tests/TestUtils/TestInput.cs @@ -12,6 +12,19 @@ public struct TestInputBuffer public readonly string ToString(bool trimZeros) => Mem.GetBitString(this, trimRightZeros: trimZeros); public override readonly string ToString() => ToString(trimZeros: true); + + /// + public override readonly int GetHashCode() => Mem.GetHashCode(this); + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The to compare with the current object. + /// true if the specified object is equal to the current object; otherwise, false. + public readonly bool Equals(TestInputBuffer other) => this[..].SequenceEqual(other); + + /// + public override readonly bool Equals(object? obj) => obj is TestInputBuffer other && Equals(other); } [Serializable] public record struct TestInput From f301f168ed243d4f28612db8c38a4a74125a9f6f Mon Sep 17 00:00:00 2001 From: Getup98 <98alino98@gmail.com> Date: Sun, 19 Jan 2025 20:36:38 +0100 Subject: [PATCH 04/12] NativeAot compatibility annotations and workarounds --- src/Backdash.Utils/Backdash.Utils.csproj | 1 + src/Backdash/Backdash.csproj | 1 + src/Backdash/Backends/BackendServices.cs | 5 +- src/Backdash/Backends/SyncTestBackend.cs | 63 +++++++++++++------ src/Backdash/Core/TypeHelpers.cs | 25 +++++--- .../Network/Client/PeerClientFactory.cs | 6 +- src/Backdash/RollbackNetcode.cs | 46 +++++++++++++- .../Serialization/BinarySerializerFactory.cs | 13 ++-- .../Synchronizing/State/ChecksumProvider.cs | 6 +- tests/Backdash.Tests/Backdash.Tests.csproj | 1 + .../Unit/Serialization/SerializersTests.cs | 5 +- .../Unit/Utils/JsonIPAddressConverterTests.cs | 19 +++--- .../Utils/JsonIPEndpointConverterTests.cs | 20 +++--- .../TestUtils/JsonSourceGenerationContext.cs | 12 ++++ 14 files changed, 150 insertions(+), 73 deletions(-) create mode 100644 tests/Backdash.Tests/TestUtils/JsonSourceGenerationContext.cs diff --git a/src/Backdash.Utils/Backdash.Utils.csproj b/src/Backdash.Utils/Backdash.Utils.csproj index eaa0af83..97cd3b3e 100644 --- a/src/Backdash.Utils/Backdash.Utils.csproj +++ b/src/Backdash.Utils/Backdash.Utils.csproj @@ -9,6 +9,7 @@ Backdash network utilities network, endpoint, multiplayer, json, input CS1591 + true diff --git a/src/Backdash/Backdash.csproj b/src/Backdash/Backdash.csproj index 61a072fc..a0c35268 100644 --- a/src/Backdash/Backdash.csproj +++ b/src/Backdash/Backdash.csproj @@ -6,6 +6,7 @@ Backdash Rollback netcode library rollback, netcode, network, peer to peer, online, game, multiplayer + true diff --git a/src/Backdash/Backends/BackendServices.cs b/src/Backdash/Backends/BackendServices.cs index 7257cd58..f03e26a4 100644 --- a/src/Backdash/Backends/BackendServices.cs +++ b/src/Backdash/Backends/BackendServices.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using Backdash.Core; using Backdash.Network; using Backdash.Network.Client; @@ -11,7 +12,7 @@ namespace Backdash.Backends; -sealed class BackendServices +sealed class BackendServices<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput, TGameState> where TInput : unmanaged where TGameState : notnull, new() { @@ -56,7 +57,7 @@ public BackendServices(RollbackOptions options, SessionServices Create( + public static BackendServices Create<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput, TGameState>( RollbackOptions options, SessionServices? services ) diff --git a/src/Backdash/Backends/SyncTestBackend.cs b/src/Backdash/Backends/SyncTestBackend.cs index 941168a8..6f476633 100644 --- a/src/Backdash/Backends/SyncTestBackend.cs +++ b/src/Backdash/Backends/SyncTestBackend.cs @@ -1,4 +1,6 @@ +using System.Diagnostics.CodeAnalysis; using System.Text.Json; +using System.Text.Json.Serialization.Metadata; using Backdash.Core; using Backdash.Data; using Backdash.Network; @@ -39,6 +41,9 @@ GameInput Input IncludeFields = true, }; + readonly JsonTypeInfo? gameStateJsonTypeInfo; + readonly JsonTypeInfo? inputJsonTypeInfo; + IRollbackHandler callbacks; bool inRollback; bool running; @@ -47,6 +52,8 @@ GameInput Input GameInput lastInput; Frame lastVerified = Frame.Zero; + [RequiresUnreferencedCode("Use the constructor which provides JsonTypeInfo(s) instead")] + [RequiresDynamicCode("Use the constructor which provides JsonTypeInfo(s) instead")] public SyncTestBackend( RollbackOptions options, FrameSpan checkDistance, @@ -78,6 +85,29 @@ BackendServices services }; currentInput = new(); lastInput = new(); + jsonOptions.TypeInfoResolver = new DefaultJsonTypeInfoResolver(); + } + + [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "gameStateJsonTypeInfo and inputJsonTypeInfo are set.")] + [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "gameStateJsonTypeInfo and inputJsonTypeInfo are set.")] + public SyncTestBackend( + RollbackOptions options, + FrameSpan checkDistance, + bool throwError, + BackendServices services, + JsonTypeInfo gameStateJsonTypeInfo, + JsonTypeInfo inputJsonTypeInfo + ) : this( + options, + checkDistance, + throwError, + services + ) + { + ArgumentNullException.ThrowIfNull(gameStateJsonTypeInfo); + ArgumentNullException.ThrowIfNull(inputJsonTypeInfo); + this.gameStateJsonTypeInfo = gameStateJsonTypeInfo; + this.inputJsonTypeInfo = inputJsonTypeInfo; } public void Dispose() => tsc.SetResult(); @@ -210,11 +240,7 @@ public void AdvanceFrame() savedFrames.Enqueue(new( Frame: frame, Input: lastInput, -#if AOT_ENABLED - State: lastSaved.GameState.ToString() ?? string.Empty, -#else - State: JsonSerializer.Serialize(lastSaved.GameState, jsonOptions), -#endif + State: JsonSerializer.Serialize(lastSaved.GameState, gameStateJsonTypeInfo ?? jsonOptions.TypeInfoResolver?.GetTypeInfo(typeof(TGameState), jsonOptions) ?? throw new InvalidOperationException("Could not get JsonTypeInfo")), Checksum: lastSaved.Checksum )); if (frame - lastVerified != checkDistance.FrameValue) @@ -259,13 +285,9 @@ void LogSaveState(SavedFrame info, string description) const LogLevel level = LogLevel.Information; logger.Write(level, $"=== SAVED STATE [{description.ToUpper()}] ({info.Frame}) ===\n"); logger.Write(level, $"INPUT FRAME {info.Input.Frame}:"); -#if AOT_ENABLED - logger.Write(level, info.Input.Data.ToString() ?? string.Empty); -#else - logger.Write(level, JsonSerializer.Serialize(info.Input.Data, jsonOptions)); -#endif + logger.Write(level, JsonSerializer.Serialize(info.Input.Data, inputJsonTypeInfo ?? jsonOptions.TypeInfoResolver?.GetTypeInfo(typeof(TInput), jsonOptions) as JsonTypeInfo ?? throw new InvalidOperationException("Could not get JsonTypeInfo"))); logger.Write(level, $"GAME STATE #{info.Checksum}:"); - LogJson(level, info.State); + LogStringChunked(level, info.State); logger.Write(level, "===================================="); } @@ -274,18 +296,23 @@ void LogSaveState(SavedFrame info, string description) const LogLevel level = LogLevel.Information; logger.Write(level, $"=== SAVED STATE [{description.ToUpper()}] ({info.Frame}) ===\n"); logger.Write(level, $"GAME STATE #{info.Checksum}:"); - LogJson(level, info.GameState); + LogJson(level, info.GameState, gameStateJsonTypeInfo ?? jsonOptions.TypeInfoResolver?.GetTypeInfo(typeof(TGameState), jsonOptions) as JsonTypeInfo ?? throw new InvalidOperationException("Could not get JsonTypeInfo")); logger.Write(level, "===================================="); } - void LogJson(LogLevel level, TValue value) + void LogStringChunked(LogLevel level, string value) + { + var chunks = value + .Chunk(LogStringBuffer.Capacity / 2) + .Select(x => new string(x)); + foreach (var chunk in chunks) + logger.Write(level, chunk); + } + + void LogJson(LogLevel level, TValue value, JsonTypeInfo jsonTypeInfo) { var jsonChunks = -#if AOT_ENABLED - (value?.ToString() ?? string.Empty) -#else - JsonSerializer.Serialize(value, jsonOptions) -#endif + JsonSerializer.Serialize(value, jsonTypeInfo) .Chunk(LogStringBuffer.Capacity / 2) .Select(x => new string(x)); foreach (var chunk in jsonChunks) diff --git a/src/Backdash/Core/TypeHelpers.cs b/src/Backdash/Core/TypeHelpers.cs index e34db1f5..391ba266 100644 --- a/src/Backdash/Core/TypeHelpers.cs +++ b/src/Backdash/Core/TypeHelpers.cs @@ -1,27 +1,37 @@ -#if !AOT_ENABLED +using System.Diagnostics.CodeAnalysis; using System.Reflection; namespace Backdash.Core; static class TypeHelpers { - public static T? Instantiate(bool allowPrivateConstructor = true) where T : notnull + public static T? Instantiate<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T>(bool allowPrivateConstructor = true) where T : notnull { var type = typeof(T); if (type.IsValueType) return default; var flags = BindingFlags.Instance | BindingFlags.Public; - if (allowPrivateConstructor) -#pragma warning disable S3011 - flags |= BindingFlags.NonPublic; -#pragma warning restore S3011 var ctor = type.GetConstructor(flags, null, Type.EmptyTypes, null); if (ctor is not null) return (T)ctor.Invoke([]); return default; } - public static bool HasInvariantHashCode() where T : notnull + public static T? InstantiateWithNonPublicConstructor<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>(bool allowPrivateConstructor = true) where T : notnull + { + var type = typeof(T); + if (type.IsValueType) + return default; +#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields + var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; +#pragma warning restore S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields + var ctor = type.GetConstructor(flags, null, Type.EmptyTypes, null); + if (ctor is not null) + return (T)ctor.Invoke([]); + return default; + } + + public static bool HasInvariantHashCode<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T>() where T : notnull { var comparer = EqualityComparer.Default; return Instantiate() is { } state1 @@ -31,4 +41,3 @@ public static bool HasInvariantHashCode() where T : notnull && comparer.GetHashCode(state1) == comparer.GetHashCode(state3); } } -#endif diff --git a/src/Backdash/Network/Client/PeerClientFactory.cs b/src/Backdash/Network/Client/PeerClientFactory.cs index ecd115e8..b4bc43f5 100644 --- a/src/Backdash/Network/Client/PeerClientFactory.cs +++ b/src/Backdash/Network/Client/PeerClientFactory.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using Backdash.Core; using Backdash.Serialization; @@ -30,11 +31,11 @@ public static IPeerClient Create( maxPacketSize ); -#if !AOT_ENABLED /// /// Creates new /// - public static IPeerClient Create( + /// Prefer using the overload in NativeAoT/Trimmed applications + public static IPeerClient Create<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] T>( IPeerSocket socket, IPeerObserver observer, int maxPacketSize = Max.UdpPacketSize, @@ -52,5 +53,4 @@ public static IPeerClient Create( delayStrategy, random ); -#endif } diff --git a/src/Backdash/RollbackNetcode.cs b/src/Backdash/RollbackNetcode.cs index 00a02886..02414914 100644 --- a/src/Backdash/RollbackNetcode.cs +++ b/src/Backdash/RollbackNetcode.cs @@ -1,4 +1,6 @@ +using System.Diagnostics.CodeAnalysis; using System.Net; +using System.Text.Json.Serialization.Metadata; using Backdash.Backends; using Backdash.Core; using Backdash.Data; @@ -23,7 +25,7 @@ public static class RollbackNetcode /// Session customizable dependencies /// Game input type /// Game state type - public static IRollbackSession CreateSession( + public static IRollbackSession CreateSession<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput, TGameState>( int port, RollbackOptions? options = null, SessionServices? services = null @@ -45,7 +47,7 @@ public static IRollbackSession CreateSessionSession customizable dependencies /// Game input type /// Game state type - public static IRollbackSession CreateSpectatorSession( + public static IRollbackSession CreateSpectatorSession<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput, TGameState>( int port, IPEndPoint host, int numberOfPlayers, @@ -70,7 +72,7 @@ public static IRollbackSession CreateSpectatorSession /// Game input type /// Game state type - public static IRollbackSession CreateReplaySession( + public static IRollbackSession CreateReplaySession<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput, TGameState>( int numberOfPlayers, IReadOnlyList> inputs, SessionServices? services = null, @@ -92,6 +94,8 @@ public static IRollbackSession CreateReplaySessionIf true, throws on state de-synchronization. /// Game input type /// Game state type + [RequiresUnreferencedCode("Use the CreateSyncTestSession overload which provides JsonTypeInfo(s) instead")] + [RequiresDynamicCode("Use the CreateSyncTestSession overload which provides JsonTypeInfo(s) instead")] public static IRollbackSession CreateSyncTestSession( FrameSpan? checkDistance = null, RollbackOptions? options = null, @@ -112,4 +116,40 @@ public static IRollbackSession CreateSyncTestSession + /// Initializes new sync test session. + /// + /// json serialization information + /// json serialization information + /// Total forced rollback frames. + /// Session configuration + /// Session customizable dependencies + /// If true, throws on state de-synchronization. + /// Game input type + /// Game state type + public static IRollbackSession CreateSyncTestSession<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput, TGameState>( + JsonTypeInfo gameStateJsonTypeInfo, + JsonTypeInfo inputJsonTypeInfo, + FrameSpan? checkDistance = null, + RollbackOptions? options = null, + SessionServices? services = null, + bool throwException = true + ) + where TInput : unmanaged + where TGameState : notnull, new() + { + options ??= new() + { + // ReSharper disable once RedundantArgumentDefaultValue + Log = new(LogLevel.Information), + }; + checkDistance ??= FrameSpan.One; + return new SyncTestBackend( + options, checkDistance.Value, throwException, + BackendServices.Create(options, services), + gameStateJsonTypeInfo, + inputJsonTypeInfo + ); + } } diff --git a/src/Backdash/Serialization/BinarySerializerFactory.cs b/src/Backdash/Serialization/BinarySerializerFactory.cs index 9a625e04..13ab981f 100644 --- a/src/Backdash/Serialization/BinarySerializerFactory.cs +++ b/src/Backdash/Serialization/BinarySerializerFactory.cs @@ -1,10 +1,8 @@ using System.Numerics; using Backdash.Core; using Backdash.Network; - -#if !AOT_ENABLED +using System.Diagnostics.CodeAnalysis; using System.Reflection; -#endif namespace Backdash.Serialization; @@ -39,12 +37,10 @@ public static IBinarySerializer ForStruct() where TInput : struc return new StructBinarySerializer(); } - public static IBinarySerializer? Get(bool networkEndianness = true) + + public static IBinarySerializer? Get<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput>(bool networkEndianness = true) where TInput : unmanaged { -#if AOT_ENABLED - return null; -#else var inputType = typeof(TInput); Type[] integerInterfaces = [typeof(IBinaryInteger<>), typeof(IMinMaxValue<>)]; return inputType switch @@ -73,10 +69,9 @@ public static IBinarySerializer ForStruct() where TInput : struc .Invoke(null, []) as IBinarySerializer, _ => null, }; -#endif } - public static IBinarySerializer FindOrThrow(bool networkEndianness = true) + public static IBinarySerializer FindOrThrow<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput>(bool networkEndianness = true) where TInput : unmanaged => Get(networkEndianness) ?? throw new InvalidOperationException($"Unable to infer serializer for type {typeof(TInput).FullName}"); diff --git a/src/Backdash/Synchronizing/State/ChecksumProvider.cs b/src/Backdash/Synchronizing/State/ChecksumProvider.cs index bb2cb392..c6018cc1 100644 --- a/src/Backdash/Synchronizing/State/ChecksumProvider.cs +++ b/src/Backdash/Synchronizing/State/ChecksumProvider.cs @@ -1,3 +1,5 @@ +using System.Diagnostics.CodeAnalysis; + namespace Backdash.Synchronizing.State; /// @@ -33,12 +35,10 @@ sealed class EmptyChecksumProvider : IChecksumProvider where T : notnull static class ChecksumProviderFactory { - public static IChecksumProvider Create() where T : notnull + public static IChecksumProvider Create<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T>() where T : notnull { -#if !AOT_ENABLED if (Core.TypeHelpers.HasInvariantHashCode()) return new HashCodeChecksumProvider(); -#endif return new EmptyChecksumProvider(); } diff --git a/tests/Backdash.Tests/Backdash.Tests.csproj b/tests/Backdash.Tests/Backdash.Tests.csproj index e124c7b4..33aa7fd5 100644 --- a/tests/Backdash.Tests/Backdash.Tests.csproj +++ b/tests/Backdash.Tests/Backdash.Tests.csproj @@ -7,6 +7,7 @@ true false CS1591;NU1903 + true diff --git a/tests/Backdash.Tests/Specs/Unit/Serialization/SerializersTests.cs b/tests/Backdash.Tests/Specs/Unit/Serialization/SerializersTests.cs index 26a0c7a8..7dbfe236 100644 --- a/tests/Backdash.Tests/Specs/Unit/Serialization/SerializersTests.cs +++ b/tests/Backdash.Tests/Specs/Unit/Serialization/SerializersTests.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Runtime.CompilerServices; using Backdash.Serialization; @@ -233,13 +234,13 @@ public void ShouldReturnCorrectSerializerForStruct() serializer.Should().BeOfType>(); } - static void AssertIntegerSerializer() where T : unmanaged, IBinaryInteger, IMinMaxValue + static void AssertIntegerSerializer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] T>() where T : unmanaged, IBinaryInteger, IMinMaxValue { var serializer = BinarySerializerFactory.Get(); serializer.Should().BeOfType>(); } - static void AssertEnumSerializer() where T : unmanaged, Enum + static void AssertEnumSerializer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] T>() where T : unmanaged, Enum { var serializer = BinarySerializerFactory.Get(); serializer.Should().BeOfType>(); diff --git a/tests/Backdash.Tests/Specs/Unit/Utils/JsonIPAddressConverterTests.cs b/tests/Backdash.Tests/Specs/Unit/Utils/JsonIPAddressConverterTests.cs index 9345dd72..d752b54e 100644 --- a/tests/Backdash.Tests/Specs/Unit/Utils/JsonIPAddressConverterTests.cs +++ b/tests/Backdash.Tests/Specs/Unit/Utils/JsonIPAddressConverterTests.cs @@ -1,6 +1,8 @@ using System.Net; using System.Text.Json; +using System.Text.Json.Serialization; using Backdash.JsonConverters; +using Backdash.Tests.TestUtils; namespace Backdash.Tests.Specs.Unit.Utils; @@ -10,21 +12,14 @@ public class JsonIPAddressConverterTests { static readonly Faker faker = new(); - static readonly JsonSerializerOptions options = new() - { - Converters = - { - new JsonIPAddressConverter(), - }, - }; - record TestType(IPAddress Data); + public record IpAddressTestType([property: JsonConverter(typeof(JsonIPAddressConverter))] IPAddress Data); [Fact] public void ShouldParseIPv4() { var expected = faker.Internet.IpAddress(); - var value = Deserialize($$"""{"Data": "{{expected.ToString()}}"}""", options); + var value = Deserialize($$"""{"Data": "{{expected.ToString()}}"}""", JsonSourceGenerationContext.Default.IpAddressTestType); value!.Data.Should().Be(expected); } @@ -32,7 +27,7 @@ public void ShouldParseIPv4() public void ShouldParseIPv6() { var expected = faker.Internet.Ipv6Address(); - var value = Deserialize($$"""{"Data": "{{expected.ToString()}}"}""", options); + var value = Deserialize($$"""{"Data": "{{expected.ToString()}}"}""", JsonSourceGenerationContext.Default.IpAddressTestType); value!.Data.Should().Be(expected); } @@ -40,7 +35,7 @@ public void ShouldParseIPv6() public void ShouldSerializeIPv4() { var value = faker.Internet.IpAddress(); - var result = Serialize(new TestType(value), options); + var result = Serialize(new IpAddressTestType(value), JsonSourceGenerationContext.Default.IpAddressTestType); var expected = $$"""{"Data":"{{value}}"}"""; result.Should().Be(expected); } @@ -49,7 +44,7 @@ public void ShouldSerializeIPv4() public void ShouldSerializeIPv6() { var value = faker.Internet.Ipv6Address(); - var result = Serialize(new TestType(value), options); + var result = Serialize(new IpAddressTestType(value), JsonSourceGenerationContext.Default.IpAddressTestType); var expected = $$"""{"Data":"{{value}}"}"""; result.Should().Be(expected); } diff --git a/tests/Backdash.Tests/Specs/Unit/Utils/JsonIPEndpointConverterTests.cs b/tests/Backdash.Tests/Specs/Unit/Utils/JsonIPEndpointConverterTests.cs index 54f5dd4f..6b006600 100644 --- a/tests/Backdash.Tests/Specs/Unit/Utils/JsonIPEndpointConverterTests.cs +++ b/tests/Backdash.Tests/Specs/Unit/Utils/JsonIPEndpointConverterTests.cs @@ -1,6 +1,8 @@ using System.Net; using System.Text.Json; +using System.Text.Json.Serialization; using Backdash.JsonConverters; +using Backdash.Tests.TestUtils; namespace Backdash.Tests.Specs.Unit.Utils; @@ -10,21 +12,13 @@ public class JsonIPEndpointConverterTests { static readonly Faker faker = new(); - static readonly JsonSerializerOptions options = new() - { - Converters = - { - new JsonIPEndPointConverter(), - }, - }; - - record TestType(IPEndPoint Data); + public record IpEndPointTestType([property: JsonConverter(typeof(JsonIPEndPointConverter))] IPEndPoint Data); [Fact] public void ShouldParseIPv4() { var expected = faker.Internet.IpEndPoint(); - var value = Deserialize($$"""{"Data": "{{expected}}"}""", options); + var value = Deserialize($$"""{"Data": "{{expected}}"}""", JsonSourceGenerationContext.Default.IpEndPointTestType); value!.Data.Should().Be(expected); } @@ -32,7 +26,7 @@ public void ShouldParseIPv4() public void ShouldParseIPv6() { var expected = faker.Internet.Ipv6EndPoint(); - var value = Deserialize($$"""{"Data": "{{expected}}"}""", options); + var value = Deserialize($$"""{"Data": "{{expected}}"}""", JsonSourceGenerationContext.Default.IpEndPointTestType); value!.Data.Should().Be(expected); } @@ -40,7 +34,7 @@ public void ShouldParseIPv6() public void ShouldSerializeIPv4() { var value = faker.Internet.IpEndPoint(); - var result = Serialize(new TestType(value), options); + var result = Serialize(new IpEndPointTestType(value), JsonSourceGenerationContext.Default.IpEndPointTestType); var expected = $$"""{"Data":"{{value}}"}"""; result.Should().Be(expected); } @@ -49,7 +43,7 @@ public void ShouldSerializeIPv4() public void ShouldSerializeIPv6() { var value = faker.Internet.Ipv6EndPoint(); - var result = Serialize(new TestType(value), options); + var result = Serialize(new IpEndPointTestType(value), JsonSourceGenerationContext.Default.IpEndPointTestType); var expected = $$"""{"Data":"{{value}}"}"""; result.Should().Be(expected); } diff --git a/tests/Backdash.Tests/TestUtils/JsonSourceGenerationContext.cs b/tests/Backdash.Tests/TestUtils/JsonSourceGenerationContext.cs new file mode 100644 index 00000000..b5c0395e --- /dev/null +++ b/tests/Backdash.Tests/TestUtils/JsonSourceGenerationContext.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; +using Backdash.Tests.Specs.Unit.Utils; + +namespace Backdash.Tests.TestUtils +{ + + [JsonSerializable(typeof(JsonIPAddressConverterTests.IpAddressTestType))] + [JsonSerializable(typeof(JsonIPEndpointConverterTests.IpEndPointTestType))] + partial class JsonSourceGenerationContext : JsonSerializerContext + { + } +} From 7a28e454a7968197afcf8157c4049b2ff8640580 Mon Sep 17 00:00:00 2001 From: Getup98 <98alino98@gmail.com> Date: Sun, 19 Jan 2025 20:40:19 +0100 Subject: [PATCH 05/12] Enabled all SerializersTests even when AOT_ENABLED is true as they are now properly annotated --- .../Backdash.Tests/Specs/Unit/Serialization/SerializersTests.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/Backdash.Tests/Specs/Unit/Serialization/SerializersTests.cs b/tests/Backdash.Tests/Specs/Unit/Serialization/SerializersTests.cs index 7dbfe236..0a62341c 100644 --- a/tests/Backdash.Tests/Specs/Unit/Serialization/SerializersTests.cs +++ b/tests/Backdash.Tests/Specs/Unit/Serialization/SerializersTests.cs @@ -226,7 +226,6 @@ static void AssertBaseSerializer(IBinarySerializer serializer) .And .BeOfType>(); -#if !AOT_ENABLED [Fact] public void ShouldReturnCorrectSerializerForStruct() { @@ -266,5 +265,4 @@ public void ShouldReturnCorrectSerializerForStruct() [Fact] public void AssertSerializerLongEnum() => AssertEnumSerializer(); [Fact] public void AssertSerializerULongEnum() => AssertEnumSerializer(); -#endif } From 5fd5e296e4e56919882e2f4fb83eaa1b073783d0 Mon Sep 17 00:00:00 2001 From: Getup98 <98alino98@gmail.com> Date: Sun, 9 Mar 2025 17:10:18 +0100 Subject: [PATCH 06/12] NativeAot compatibility annotations and workarounds - For .net8.0 --- src/Backdash/Backends/BackendServices.cs | 6 + src/Backdash/Backends/LocalBackend.cs | 3 +- src/Backdash/Backends/RemoteBackend.cs | 3 +- src/Backdash/Backends/ReplayBackend.cs | 3 +- src/Backdash/Backends/SpectatorBackend.cs | 3 +- src/Backdash/Backends/SyncTestBackend.cs | 3 +- .../Network/Client/PeerClientFactory.cs | 3 + src/Backdash/RollbackNetcode.cs | 15 ++ .../Internal/BinarySerializerFactory.cs | 7 +- .../Protocol/ProtocolInputBufferTests.cs | 9 +- .../Unit/Serialization/SerializersTests.cs | 174 ++++++++++++++++-- .../TestUtils/DynamicFactAttribute.cs | 22 +++ 12 files changed, 222 insertions(+), 29 deletions(-) create mode 100644 tests/Backdash.Tests/TestUtils/DynamicFactAttribute.cs diff --git a/src/Backdash/Backends/BackendServices.cs b/src/Backdash/Backends/BackendServices.cs index 6abd9761..eae32b5b 100644 --- a/src/Backdash/Backends/BackendServices.cs +++ b/src/Backdash/Backends/BackendServices.cs @@ -29,6 +29,9 @@ sealed class BackendServices<[DynamicallyAccessedMembers(DynamicallyAccessedMemb public EqualityComparer InputComparer { get; } +#if !NET9_0_OR_GREATER + [RequiresDynamicCode("Requires dynamic code unless " + nameof(services) + "." + nameof(SessionServices.InputSerializer) + " is valorized. If so, suppress this warning.")] +#endif public BackendServices(NetcodeOptions options, SessionServices? services) { ChecksumProvider = services?.ChecksumProvider ?? new Fletcher32ChecksumProvider(); @@ -58,6 +61,9 @@ public BackendServices(NetcodeOptions options, SessionServices? services static class BackendServices { +#if !NET9_0_OR_GREATER + [RequiresDynamicCode("Requires dynamic code unless " + nameof(services) + "." + nameof(SessionServices.InputSerializer) + " is valorized. If so, suppress this warning.")] +#endif public static BackendServices Create<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput>(NetcodeOptions options, SessionServices? services) where TInput : unmanaged => new(options, services); diff --git a/src/Backdash/Backends/LocalBackend.cs b/src/Backdash/Backends/LocalBackend.cs index 9b6a19af..2a0c69eb 100644 --- a/src/Backdash/Backends/LocalBackend.cs +++ b/src/Backdash/Backends/LocalBackend.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using Backdash.Core; using Backdash.Data; using Backdash.Network; @@ -7,7 +8,7 @@ namespace Backdash.Backends; -sealed class LocalBackend : INetcodeSession where TInput : unmanaged +sealed class LocalBackend<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput> : INetcodeSession where TInput : unmanaged { readonly TaskCompletionSource tsc = new(); readonly Logger logger; diff --git a/src/Backdash/Backends/RemoteBackend.cs b/src/Backdash/Backends/RemoteBackend.cs index 93ee2c69..2f93493a 100644 --- a/src/Backdash/Backends/RemoteBackend.cs +++ b/src/Backdash/Backends/RemoteBackend.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Backdash.Core; @@ -15,7 +16,7 @@ namespace Backdash.Backends; -sealed class RemoteBackend : INetcodeSession, IProtocolNetworkEventHandler +sealed class RemoteBackend<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput> : INetcodeSession, IProtocolNetworkEventHandler where TInput : unmanaged { readonly NetcodeOptions options; diff --git a/src/Backdash/Backends/ReplayBackend.cs b/src/Backdash/Backends/ReplayBackend.cs index df542a86..60bc2b10 100644 --- a/src/Backdash/Backends/ReplayBackend.cs +++ b/src/Backdash/Backends/ReplayBackend.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using Backdash.Core; using Backdash.Data; using Backdash.Network; @@ -9,7 +10,7 @@ namespace Backdash.Backends; -sealed class ReplayBackend : INetcodeSession where TInput : unmanaged +sealed class ReplayBackend<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput> : INetcodeSession where TInput : unmanaged { readonly Logger logger; readonly PlayerHandle[] fakePlayers; diff --git a/src/Backdash/Backends/SpectatorBackend.cs b/src/Backdash/Backends/SpectatorBackend.cs index 7465a39c..c816a295 100644 --- a/src/Backdash/Backends/SpectatorBackend.cs +++ b/src/Backdash/Backends/SpectatorBackend.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.Net; using Backdash.Core; using Backdash.Data; @@ -13,7 +14,7 @@ namespace Backdash.Backends; -sealed class SpectatorBackend : +sealed class SpectatorBackend<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput> : INetcodeSession, IProtocolNetworkEventHandler, IProtocolInputEventPublisher> diff --git a/src/Backdash/Backends/SyncTestBackend.cs b/src/Backdash/Backends/SyncTestBackend.cs index 62f85e02..975fdd40 100644 --- a/src/Backdash/Backends/SyncTestBackend.cs +++ b/src/Backdash/Backends/SyncTestBackend.cs @@ -1,4 +1,5 @@ using System.Buffers; +using System.Diagnostics.CodeAnalysis; using Backdash.Core; using Backdash.Data; using Backdash.Network; @@ -9,7 +10,7 @@ namespace Backdash.Backends; -sealed class SyncTestBackend : INetcodeSession +sealed class SyncTestBackend<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput> : INetcodeSession where TInput : unmanaged { readonly record struct SavedFrameBytes( diff --git a/src/Backdash/Network/Client/PeerClientFactory.cs b/src/Backdash/Network/Client/PeerClientFactory.cs index 2f044d99..bced8857 100644 --- a/src/Backdash/Network/Client/PeerClientFactory.cs +++ b/src/Backdash/Network/Client/PeerClientFactory.cs @@ -36,6 +36,9 @@ public static IPeerClient Create( /// Creates new /// /// Prefer using the overload in NativeAoT/Trimmed applications +#if !NET9_0_OR_GREATER + [RequiresDynamicCode("The native code for this instantiation might not be available at runtime.")] +#endif public static IPeerClient Create<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] T>( IPeerSocket socket, IPeerObserver observer, diff --git a/src/Backdash/RollbackNetcode.cs b/src/Backdash/RollbackNetcode.cs index 35e38ced..82c54b36 100644 --- a/src/Backdash/RollbackNetcode.cs +++ b/src/Backdash/RollbackNetcode.cs @@ -23,6 +23,9 @@ public static class RollbackNetcode /// Session configuration /// Session customizable dependencies /// Game input type +#if !NET9_0_OR_GREATER + [RequiresDynamicCode("Requires dynamic code unless " + nameof(services) + "." + nameof(SessionServices.InputSerializer) + " is valorized. If so, suppress this warning.")] +#endif public static INetcodeSession CreateSession<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput>( int port, NetcodeOptions? options = null, @@ -43,6 +46,9 @@ public static class RollbackNetcode /// Session configuration /// Session customizable dependencies /// Game input type +#if !NET9_0_OR_GREATER + [RequiresDynamicCode("Requires dynamic code unless " + nameof(services) + "." + nameof(SessionServices.InputSerializer) + " is valorized. If so, suppress this warning.")] +#endif public static INetcodeSession CreateSpectatorSession<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput>( int port, IPEndPoint host, @@ -62,6 +68,9 @@ public static class RollbackNetcode /// Session configuration /// Session customizable dependencies /// Game input type +#if !NET9_0_OR_GREATER + [RequiresDynamicCode("Requires dynamic code unless " + nameof(services) + "." + nameof(SessionServices.InputSerializer) + " is valorized. If so, suppress this warning.")] +#endif public static INetcodeSession CreateLocalSession<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput>( NetcodeOptions? options = null, SessionServices? services = null @@ -81,6 +90,9 @@ public static class RollbackNetcode /// replay control /// Session configuration /// Game input type +#if !NET9_0_OR_GREATER + [RequiresDynamicCode("Requires dynamic code unless " + nameof(services) + "." + nameof(SessionServices.InputSerializer) + " is valorized. If so, suppress this warning.")] +#endif public static INetcodeSession CreateReplaySession<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput>( int numberOfPlayers, IReadOnlyList> inputs, @@ -106,6 +118,9 @@ public static class RollbackNetcode /// State de-sync handler /// If true, throws on state de-synchronization. /// Game input type +#if !NET9_0_OR_GREATER + [RequiresDynamicCode("Requires dynamic code unless " + nameof(services) + "." + nameof(SessionServices.InputSerializer) + " is valorized. If so, suppress this warning.")] +#endif public static INetcodeSession CreateSyncTestSession<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput>( FrameSpan? checkDistance = null, NetcodeOptions? options = null, diff --git a/src/Backdash/Serialization/Internal/BinarySerializerFactory.cs b/src/Backdash/Serialization/Internal/BinarySerializerFactory.cs index 7c2d2027..184d532b 100644 --- a/src/Backdash/Serialization/Internal/BinarySerializerFactory.cs +++ b/src/Backdash/Serialization/Internal/BinarySerializerFactory.cs @@ -33,7 +33,9 @@ public static IBinarySerializer ForStruct() where TInput : struc return new StructBinarySerializer(); } - +#if !NET9_0_OR_GREATER + [RequiresDynamicCode("The native code for this instantiation might not be available at runtime.")] +#endif public static IBinarySerializer? Get<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput>(bool networkEndianness = true) where TInput : unmanaged { @@ -67,6 +69,9 @@ public static IBinarySerializer ForStruct() where TInput : struc }; } +#if !NET9_0_OR_GREATER + [RequiresDynamicCode("The native code for this instantiation might not be available at runtime.")] +#endif public static IBinarySerializer FindOrThrow<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput>(bool networkEndianness = true) where TInput : unmanaged => Get(networkEndianness) diff --git a/tests/Backdash.Tests/Specs/Unit/Network/Protocol/ProtocolInputBufferTests.cs b/tests/Backdash.Tests/Specs/Unit/Network/Protocol/ProtocolInputBufferTests.cs index 5082a39b..9944c9b7 100644 --- a/tests/Backdash.Tests/Specs/Unit/Network/Protocol/ProtocolInputBufferTests.cs +++ b/tests/Backdash.Tests/Specs/Unit/Network/Protocol/ProtocolInputBufferTests.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using Backdash.Core; using Backdash.Data; using Backdash.Network; @@ -42,7 +43,7 @@ public void ValidateTestSampleSerialization() decompressedInput.Should().BeEquivalentTo(GetSampleInputs()); } - [Fact] + [DynamicFact(nameof(AutoFakeIt) + " requires dynamic code, even if the library is not annotated with " + nameof(RequiresDynamicCodeAttribute))] public void ShouldSendSingleInput() { var faker = GetFaker(); @@ -68,7 +69,7 @@ public void ShouldSendSingleInput() queue.SendInput(input).Should().Be(SendInputResult.Ok); } - [Fact] + [DynamicFact(nameof(AutoFakeIt) + " requires dynamic code, even if the library is not annotated with " + nameof(RequiresDynamicCodeAttribute))] public void ShouldCompressMultipleBufferedInputs() { var faker = GetFakerWithSender(); @@ -85,7 +86,7 @@ public void ShouldCompressMultipleBufferedInputs() lastMessageSent.Should().BeEquivalentTo(GetSampleMessage()); } - [Fact] + [DynamicFact(nameof(AutoFakeIt) + " requires dynamic code, even if the library is not annotated with " + nameof(RequiresDynamicCodeAttribute))] public void ShouldSkipAckedInputs() { var faker = GetFakerWithSender(); @@ -115,7 +116,7 @@ public void ShouldSkipAckedInputs() lastSentMessage.Should().BeEquivalentTo(expectedMessage); } - [Fact] + [DynamicFact(nameof(AutoFakeIt) + " requires dynamic code, even if the library is not annotated with " + nameof(RequiresDynamicCodeAttribute))] public void ShouldHandleWhenMaxInputSizeReached() { var faker = GetFakerWithSender(); diff --git a/tests/Backdash.Tests/Specs/Unit/Serialization/SerializersTests.cs b/tests/Backdash.Tests/Specs/Unit/Serialization/SerializersTests.cs index 9c80210a..69d107ca 100644 --- a/tests/Backdash.Tests/Specs/Unit/Serialization/SerializersTests.cs +++ b/tests/Backdash.Tests/Specs/Unit/Serialization/SerializersTests.cs @@ -228,19 +228,30 @@ static void AssertBaseSerializer(IBinarySerializer serializer) .And .BeOfType>(); +#if NET9_0_OR_GREATER [Fact] +#else + [DynamicFact] + [RequiresDynamicCode("Calls Backdash.Serialization.Internal.BinarySerializerFactory.Get(Boolean)")] +#endif public void ShouldReturnCorrectSerializerForStruct() { var serializer = BinarySerializerFactory.Get(); serializer.Should().BeOfType>(); } +#if !NET9_0_OR_GREATER + [RequiresDynamicCode("Calls Backdash.Serialization.Internal.BinarySerializerFactory.Get(Boolean)")] +#endif static void AssertIntegerSerializer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] T>() where T : unmanaged, IBinaryInteger, IMinMaxValue { var serializer = BinarySerializerFactory.Get(); serializer.Should().BeOfType>(); } +#if !NET9_0_OR_GREATER + [RequiresDynamicCode("Calls Backdash.Serialization.Internal.BinarySerializerFactory.Get(Boolean)")] +#endif static void AssertEnumSerializer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] T, TInt>() where T : unmanaged, Enum where TInt : unmanaged, IBinaryInteger, IMinMaxValue @@ -249,25 +260,150 @@ public void ShouldReturnCorrectSerializerForStruct() serializer.Should().BeOfType>(); } - [Fact] public void AssertSerializerByte() => AssertIntegerSerializer(); - [Fact] public void AssertSerializerSByte() => AssertIntegerSerializer(); - [Fact] public void AssertSerializerShort() => AssertIntegerSerializer(); - [Fact] public void AssertSerializerUShort() => AssertIntegerSerializer(); - [Fact] public void AssertSerializerInt() => AssertIntegerSerializer(); - [Fact] public void AssertSerializerUInt() => AssertIntegerSerializer(); - [Fact] public void AssertSerializerLong() => AssertIntegerSerializer(); - [Fact] public void AssertSerializerULong() => AssertIntegerSerializer(); - [Fact] public void AssertSerializerInt128() => AssertIntegerSerializer(); - [Fact] public void AssertSerializerUInt128() => AssertIntegerSerializer(); - - [Fact] public void AssertSerializerByteEnum() => AssertEnumSerializer(); - [Fact] public void AssertSerializerSByteEnum() => AssertEnumSerializer(); - [Fact] public void AssertSerializerShortEnum() => AssertEnumSerializer(); - [Fact] public void AssertSerializerUShortEnum() => AssertEnumSerializer(); - [Fact] public void AssertSerializerIntEnum() => AssertEnumSerializer(); - [Fact] public void AssertSerializerUIntEnum() => AssertEnumSerializer(); - [Fact] public void AssertSerializerLongEnum() => AssertEnumSerializer(); - [Fact] public void AssertSerializerULongEnum() => AssertEnumSerializer(); +#if NET9_0_OR_GREATER + [Fact] +#else + [DynamicFact] + [RequiresDynamicCode("Calls Backdash.Tests.Specs.Unit.Serialization.SerializersTests.AssertIntegerSerializer()")] +#endif + public void AssertSerializerByte() => AssertIntegerSerializer(); + +#if NET9_0_OR_GREATER + [Fact] +#else + [DynamicFact] + [RequiresDynamicCode("Calls Backdash.Tests.Specs.Unit.Serialization.SerializersTests.AssertIntegerSerializer()")] +#endif + public void AssertSerializerSByte() => AssertIntegerSerializer(); + +#if NET9_0_OR_GREATER + [Fact] +#else + [DynamicFact] + [RequiresDynamicCode("Calls Backdash.Tests.Specs.Unit.Serialization.SerializersTests.AssertIntegerSerializer()")] +#endif + public void AssertSerializerShort() => AssertIntegerSerializer(); + +#if NET9_0_OR_GREATER + [Fact] +#else + [DynamicFact] + [RequiresDynamicCode("Calls Backdash.Tests.Specs.Unit.Serialization.SerializersTests.AssertIntegerSerializer()")] +#endif + public void AssertSerializerUShort() => AssertIntegerSerializer(); + +#if NET9_0_OR_GREATER + [Fact] +#else + [DynamicFact] + [RequiresDynamicCode("Calls Backdash.Tests.Specs.Unit.Serialization.SerializersTests.AssertIntegerSerializer()")] +#endif + public void AssertSerializerInt() => AssertIntegerSerializer(); + +#if NET9_0_OR_GREATER + [Fact] +#else + [DynamicFact] + [RequiresDynamicCode("Calls Backdash.Tests.Specs.Unit.Serialization.SerializersTests.AssertIntegerSerializer()")] +#endif + public void AssertSerializerUInt() => AssertIntegerSerializer(); + +#if NET9_0_OR_GREATER + [Fact] +#else + [DynamicFact] + [RequiresDynamicCode("Calls Backdash.Tests.Specs.Unit.Serialization.SerializersTests.AssertIntegerSerializer()")] +#endif + public void AssertSerializerLong() => AssertIntegerSerializer(); + +#if NET9_0_OR_GREATER + [Fact] +#else + [DynamicFact] + [RequiresDynamicCode("Calls Backdash.Tests.Specs.Unit.Serialization.SerializersTests.AssertIntegerSerializer()")] +#endif + public void AssertSerializerULong() => AssertIntegerSerializer(); + +#if NET9_0_OR_GREATER + [Fact] +#else + [DynamicFact] + [RequiresDynamicCode("Calls Backdash.Tests.Specs.Unit.Serialization.SerializersTests.AssertIntegerSerializer()")] +#endif + public void AssertSerializerInt128() => AssertIntegerSerializer(); + +#if NET9_0_OR_GREATER + [Fact] +#else + [DynamicFact] + [RequiresDynamicCode("Calls Backdash.Tests.Specs.Unit.Serialization.SerializersTests.AssertIntegerSerializer()")] +#endif + public void AssertSerializerUInt128() => AssertIntegerSerializer(); + + +#if NET9_0_OR_GREATER + [Fact] +#else + [DynamicFact] + [RequiresDynamicCode("Calls Backdash.Tests.Specs.Unit.Serialization.SerializersTests.AssertEnumSerializer()")] +#endif + public void AssertSerializerByteEnum() => AssertEnumSerializer(); + +#if NET9_0_OR_GREATER + [Fact] +#else + [DynamicFact] + [RequiresDynamicCode("Calls Backdash.Tests.Specs.Unit.Serialization.SerializersTests.AssertEnumSerializer()")] +#endif + public void AssertSerializerSByteEnum() => AssertEnumSerializer(); + +#if NET9_0_OR_GREATER + [Fact] +#else + [DynamicFact] + [RequiresDynamicCode("Calls Backdash.Tests.Specs.Unit.Serialization.SerializersTests.AssertEnumSerializer()")] +#endif + public void AssertSerializerShortEnum() => AssertEnumSerializer(); + +#if NET9_0_OR_GREATER + [Fact] +#else + [DynamicFact] + [RequiresDynamicCode("Calls Backdash.Tests.Specs.Unit.Serialization.SerializersTests.AssertEnumSerializer()")] +#endif + public void AssertSerializerUShortEnum() => AssertEnumSerializer(); + +#if NET9_0_OR_GREATER + [Fact] +#else + [DynamicFact] + [RequiresDynamicCode("Calls Backdash.Tests.Specs.Unit.Serialization.SerializersTests.AssertEnumSerializer()")] +#endif + public void AssertSerializerIntEnum() => AssertEnumSerializer(); + +#if NET9_0_OR_GREATER + [Fact] +#else + [DynamicFact] + [RequiresDynamicCode("Calls Backdash.Tests.Specs.Unit.Serialization.SerializersTests.AssertEnumSerializer()")] +#endif + public void AssertSerializerUIntEnum() => AssertEnumSerializer(); + +#if NET9_0_OR_GREATER + [Fact] +#else + [DynamicFact] + [RequiresDynamicCode("Calls Backdash.Tests.Specs.Unit.Serialization.SerializersTests.AssertEnumSerializer()")] +#endif + public void AssertSerializerLongEnum() => AssertEnumSerializer(); + +#if NET9_0_OR_GREATER + [Fact] +#else + [DynamicFact] + [RequiresDynamicCode("Calls Backdash.Tests.Specs.Unit.Serialization.SerializersTests.AssertEnumSerializer()")] +#endif + public void AssertSerializerULongEnum() => AssertEnumSerializer(); } diff --git a/tests/Backdash.Tests/TestUtils/DynamicFactAttribute.cs b/tests/Backdash.Tests/TestUtils/DynamicFactAttribute.cs new file mode 100644 index 00000000..98613113 --- /dev/null +++ b/tests/Backdash.Tests/TestUtils/DynamicFactAttribute.cs @@ -0,0 +1,22 @@ +using System.Runtime.CompilerServices; + +namespace Backdash.Tests.TestUtils +{ + /// + /// Attribute that is applied to a method to indicate that it is a fact that should be run + /// by the test runner only when dynamic code is supported. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + class DynamicFactAttribute : FactAttribute + { + public DynamicFactAttribute(string? message = null) + { + if (!RuntimeFeature.IsDynamicCodeSupported) + { + Skip = "Skipped due to no dynamic code support"; + if (message != null) + Skip += ": " + message; + } + } + } +} From 1e56a4e2dcf29cb664d63d6de9e0292bd1b1bf00 Mon Sep 17 00:00:00 2001 From: Getup98 <98alino98@gmail.com> Date: Sun, 9 Mar 2025 17:10:56 +0100 Subject: [PATCH 07/12] Downgrade to .net8.0 --- _build/_build.csproj | 2 +- .../Backdash.Benchmarks.Ping/Backdash.Benchmarks.Ping.csproj | 2 +- benchmarks/Backdash.Benchmarks/Backdash.Benchmarks.csproj | 2 +- src/Backdash.Utils/Backdash.Utils.csproj | 2 +- src/Backdash/Backdash.csproj | 2 +- tests/Backdash.Tests/Backdash.Tests.csproj | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/_build/_build.csproj b/_build/_build.csproj index 5120474d..a454e10d 100644 --- a/_build/_build.csproj +++ b/_build/_build.csproj @@ -1,7 +1,7 @@ Exe - net9.0 + net8.0 $(NoWarn);CS0649;CS0169;CS1591;CS1573 disable diff --git a/benchmarks/Backdash.Benchmarks.Ping/Backdash.Benchmarks.Ping.csproj b/benchmarks/Backdash.Benchmarks.Ping/Backdash.Benchmarks.Ping.csproj index 426457ba..efd3474d 100644 --- a/benchmarks/Backdash.Benchmarks.Ping/Backdash.Benchmarks.Ping.csproj +++ b/benchmarks/Backdash.Benchmarks.Ping/Backdash.Benchmarks.Ping.csproj @@ -1,7 +1,7 @@ Exe - net9.0 + net8.0 enable enable CS1591,S125,S1199,CS016,S1144,S3903,CS159,S1104 diff --git a/benchmarks/Backdash.Benchmarks/Backdash.Benchmarks.csproj b/benchmarks/Backdash.Benchmarks/Backdash.Benchmarks.csproj index d1706384..f147c9a6 100644 --- a/benchmarks/Backdash.Benchmarks/Backdash.Benchmarks.csproj +++ b/benchmarks/Backdash.Benchmarks/Backdash.Benchmarks.csproj @@ -1,7 +1,7 @@ Exe - net9.0 + net8.0 enable enable CS1591;S125;S1199;CS016;S1144;S3903;CS159;S1104;MSB3277 diff --git a/src/Backdash.Utils/Backdash.Utils.csproj b/src/Backdash.Utils/Backdash.Utils.csproj index 97cd3b3e..fc0b4e0a 100644 --- a/src/Backdash.Utils/Backdash.Utils.csproj +++ b/src/Backdash.Utils/Backdash.Utils.csproj @@ -1,7 +1,7 @@ - net9.0 + net8.0 enable true Backdash diff --git a/src/Backdash/Backdash.csproj b/src/Backdash/Backdash.csproj index a0c35268..34e81bf7 100644 --- a/src/Backdash/Backdash.csproj +++ b/src/Backdash/Backdash.csproj @@ -1,6 +1,6 @@ - net9.0 + net8.0 enable true Backdash diff --git a/tests/Backdash.Tests/Backdash.Tests.csproj b/tests/Backdash.Tests/Backdash.Tests.csproj index 831dd05e..6ca1d589 100644 --- a/tests/Backdash.Tests/Backdash.Tests.csproj +++ b/tests/Backdash.Tests/Backdash.Tests.csproj @@ -1,6 +1,6 @@ - net9.0 + net8.0 enable enable false From c2b2a8725e412859f73c0805a8e3a1717348b619 Mon Sep 17 00:00:00 2001 From: Getup98 <98alino98@gmail.com> Date: Mon, 10 Mar 2025 19:11:27 +0100 Subject: [PATCH 08/12] Downgrade to .net8.0 - set sdk version to 8.0.404 in global.json --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index aeceea9d..d47cb3fb 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "9.0.101", + "version": "8.0.404", "rollForward": "latestMinor", "allowPrerelease": false } From 1ddb4a538618fd32c3e765ef1eb137a33d1de240 Mon Sep 17 00:00:00 2001 From: Getup98 <98alino98@gmail.com> Date: Wed, 12 Mar 2025 10:10:03 +0100 Subject: [PATCH 09/12] Fix IDE0040 in IsExternalInit --- src/Backdash.Analyzers/IsExternalInit.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Backdash.Analyzers/IsExternalInit.cs b/src/Backdash.Analyzers/IsExternalInit.cs index ded2fabc..3ae28b70 100644 --- a/src/Backdash.Analyzers/IsExternalInit.cs +++ b/src/Backdash.Analyzers/IsExternalInit.cs @@ -3,5 +3,5 @@ namespace System.Runtime.CompilerServices /// /// This is needed to make the analyzers work in .netstandard2.0 (so they are compatible with visual studio) /// - internal static class IsExternalInit { } + static class IsExternalInit { } } From a6f987fc451184a33f3e027e7b5fb13a4942f9b7 Mon Sep 17 00:00:00 2001 From: Getup98 <98alino98@gmail.com> Date: Tue, 18 Mar 2025 20:12:41 +0100 Subject: [PATCH 10/12] Fix SerializerTests --- .../Specs/Unit/Serialization/SerializersTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Backdash.Tests/Specs/Unit/Serialization/SerializersTests.cs b/tests/Backdash.Tests/Specs/Unit/Serialization/SerializersTests.cs index 7c57b8b0..b3025f95 100644 --- a/tests/Backdash.Tests/Specs/Unit/Serialization/SerializersTests.cs +++ b/tests/Backdash.Tests/Specs/Unit/Serialization/SerializersTests.cs @@ -1,4 +1,3 @@ -using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Runtime.CompilerServices; using Backdash.Network; @@ -361,19 +360,20 @@ static void AssertBaseSerializer(IBinarySerializer serializer) } } - static void ShouldReturnCorrectSerializerForStruct() + [Fact] + public void ShouldReturnCorrectSerializerForStruct() { var serializer = BinarySerializerFactory.ForStruct(); serializer.Should().BeOfType>(); } - static void AssertIntegerSerializer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] T>() where T : unmanaged, IBinaryInteger, IMinMaxValue + static void AssertIntegerSerializer() where T : unmanaged, IBinaryInteger, IMinMaxValue { var serializer = BinarySerializerFactory.ForInteger(Endianness.BigEndian); serializer.Should().BeOfType>(); } - static void AssertEnumSerializer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] T, TInt>() + static void AssertEnumSerializer() where T : unmanaged, Enum where TInt : unmanaged, IBinaryInteger, IMinMaxValue { From 640375354cad96019d79a19da5db12ae32e03d90 Mon Sep 17 00:00:00 2001 From: Getup98 <98alino98@gmail.com> Date: Tue, 18 Mar 2025 20:13:56 +0100 Subject: [PATCH 11/12] Remove unnecessary Backdash.Analyzers reference from Backdash.csproj --- src/Backdash/Backdash.csproj | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/Backdash/Backdash.csproj b/src/Backdash/Backdash.csproj index 34e81bf7..5e3b0fd9 100644 --- a/src/Backdash/Backdash.csproj +++ b/src/Backdash/Backdash.csproj @@ -20,16 +20,4 @@ - - - - - - - - - - - - From 44eab4f6cadb607a50a38d118c20cf658d431094 Mon Sep 17 00:00:00 2001 From: Getup98 <98alino98@gmail.com> Date: Tue, 18 Mar 2025 20:15:07 +0100 Subject: [PATCH 12/12] Repurpose AOT_ENABLED to enable PublishAoT, keeping IsAoTCompatible analyzers always on --- src/Backdash/Backdash.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Backdash/Backdash.csproj b/src/Backdash/Backdash.csproj index 5e3b0fd9..f569d9fc 100644 --- a/src/Backdash/Backdash.csproj +++ b/src/Backdash/Backdash.csproj @@ -7,11 +7,11 @@ Rollback netcode library rollback, netcode, network, peer to peer, online, game, multiplayer true + Speed - true - Speed + true