Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/Backdash.Benchmarks/Backdash.Benchmarks.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
Expand Down
5 changes: 3 additions & 2 deletions src/Backdash.Analyzers/Backdash.Analyzers.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>preview</LangVersion>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>

Expand Down
7 changes: 7 additions & 0 deletions src/Backdash.Analyzers/IsExternalInit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace System.Runtime.CompilerServices
{
/// <summary>
/// This is needed to make the analyzers work in .netstandard2.0 (so they are compatible with visual studio)
/// </summary>
static class IsExternalInit { }
}
2 changes: 1 addition & 1 deletion src/Backdash.Analyzers/SourceGenerationHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,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
Expand Down
3 changes: 2 additions & 1 deletion src/Backdash.Utils/Backdash.Utils.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
Expand All @@ -9,6 +9,7 @@
<Description>Backdash network utilities</Description>
<PackageTags>network, endpoint, multiplayer, json, input</PackageTags>
<NoWarn>CS1591</NoWarn>
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>

<ItemGroup>
Expand Down
15 changes: 14 additions & 1 deletion src/Backdash/Backdash.csproj
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PackageId>Backdash</PackageId>
<Description>Rollback netcode library</Description>
<PackageTags>rollback, netcode, network, peer to peer, online, game, multiplayer</PackageTags>
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>

<PropertyGroup Condition="$([System.Text.RegularExpressions.Regex]::IsMatch($(DefineConstants), '^(.*;)*AOT_ENABLED(;.*)*$'))">
Expand All @@ -19,4 +20,16 @@
<InternalsVisibleTo Include="$(AssemblyName).Benchmarks"/>
<InternalsVisibleTo Include="$(AssemblyName).Benchmarks.Ping"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Backdash.Analyzers\Backdash.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
</ItemGroup>

<ItemGroup>
<None Include="$(OutputPath)\Backdash.Analyzers.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false"/>
</ItemGroup>

<ItemGroup>

</ItemGroup>
</Project>
11 changes: 9 additions & 2 deletions src/Backdash/Backends/BackendServices.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using Backdash.Core;
using Backdash.Network;
using Backdash.Network.Client;
Expand All @@ -11,7 +12,7 @@

namespace Backdash.Backends;

sealed class BackendServices<TInput> where TInput : unmanaged
sealed class BackendServices<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput> where TInput : unmanaged
{
public IBinarySerializer<TInput> InputSerializer { get; }
public IChecksumProvider ChecksumProvider { get; }
Expand All @@ -27,6 +28,9 @@ sealed class BackendServices<TInput> where TInput : unmanaged

public EqualityComparer<TInput> InputComparer { get; }

#if !NET9_0_OR_GREATER
[RequiresDynamicCode("Requires dynamic code unless " + nameof(services) + "." + nameof(SessionServices<TInput>.InputSerializer) + " is valorized. If so, suppress this warning.")]
#endif
public BackendServices(NetcodeOptions options, SessionServices<TInput>? services)
{
ChecksumProvider = services?.ChecksumProvider ?? new Fletcher32ChecksumProvider();
Expand Down Expand Up @@ -55,7 +59,10 @@ public BackendServices(NetcodeOptions options, SessionServices<TInput>? services

static class BackendServices
{
public static BackendServices<TInput> Create<TInput>(NetcodeOptions options, SessionServices<TInput>? services)
#if !NET9_0_OR_GREATER
[RequiresDynamicCode("Requires dynamic code unless " + nameof(services) + "." + nameof(SessionServices<TInput>.InputSerializer) + " is valorized. If so, suppress this warning.")]
#endif
public static BackendServices<TInput> Create<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput>(NetcodeOptions options, SessionServices<TInput>? services)
where TInput : unmanaged =>
new(options, services);
}
3 changes: 2 additions & 1 deletion src/Backdash/Backends/LocalBackend.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using Backdash.Core;
using Backdash.Data;
using Backdash.Network;
Expand All @@ -7,7 +8,7 @@

namespace Backdash.Backends;

sealed class LocalBackend<TInput> : INetcodeSession<TInput> where TInput : unmanaged
sealed class LocalBackend<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput> : INetcodeSession<TInput> where TInput : unmanaged
{
readonly TaskCompletionSource tsc = new();
readonly Logger logger;
Expand Down
3 changes: 2 additions & 1 deletion src/Backdash/Backends/RemoteBackend.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Backdash.Core;
Expand All @@ -15,7 +16,7 @@

namespace Backdash.Backends;

sealed class RemoteBackend<TInput> : INetcodeSession<TInput>, IProtocolNetworkEventHandler
sealed class RemoteBackend<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput> : INetcodeSession<TInput>, IProtocolNetworkEventHandler
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why Interfaces? usually, the input type is a raw unmanaged structure.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In BinarySerializerFactory.Get there is a call to typeof(TInput).GetInterfaces(), which requires the annotation, and it bubbles up from there.
The call is there to check whether the type can be serialized by an IntegerBinarySerializer.

I agree it makes little sense for the whole backend's input to be something serializable as an integer and it's for sure an edge case, but removing the edge case would lead to restricting functionality, which I'm not comfortable doing as my first PR to a repo.
Especially when you consider that the SpaceWar demo uses an enum as TInput, which is close enough to an integer for me to consider that edge case not so "edge" after all.

In theory when TInput is a raw unmanaged struct without any interfaces that annotation does nothing but clutter the source code a bit, no performance or trimmed size impact. (I say in theory because I have not personally tested it to be true, just relying on documentation)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I look at the latest changes, it seems like you removed BinarySerializerFactory.Get, I'll update the branch and see if these annotations are at all necessary anymore.

where TInput : unmanaged
{
readonly NetcodeOptions options;
Expand Down
3 changes: 2 additions & 1 deletion src/Backdash/Backends/ReplayBackend.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using Backdash.Core;
using Backdash.Data;
using Backdash.Network;
Expand All @@ -9,7 +10,7 @@

namespace Backdash.Backends;

sealed class ReplayBackend<TInput> : INetcodeSession<TInput> where TInput : unmanaged
sealed class ReplayBackend<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput> : INetcodeSession<TInput> where TInput : unmanaged
{
readonly Logger logger;
readonly PlayerHandle[] fakePlayers;
Expand Down
3 changes: 2 additions & 1 deletion src/Backdash/Backends/SpectatorBackend.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics;
using System.Net;
using Backdash.Core;
Expand All @@ -14,7 +15,7 @@

namespace Backdash.Backends;

sealed class SpectatorBackend<TInput> :
sealed class SpectatorBackend<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput> :
INetcodeSession<TInput>,
IProtocolNetworkEventHandler,
IProtocolInputEventPublisher<ConfirmedInputs<TInput>>
Expand Down
3 changes: 2 additions & 1 deletion src/Backdash/Backends/SyncTestBackend.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using Backdash.Core;
using Backdash.Data;
using Backdash.Network;
Expand All @@ -9,7 +10,7 @@

namespace Backdash.Backends;

sealed class SyncTestBackend<TInput> : INetcodeSession<TInput>
sealed class SyncTestBackend<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput> : INetcodeSession<TInput>
where TInput : unmanaged
{
readonly record struct SavedFrameBytes(
Expand Down
13 changes: 13 additions & 0 deletions src/Backdash/Core/LogInterpolatedStringHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,17 @@ struct LogStringBuffer
#endif

byte elemenet0;

///<inheritdoc/>
public override readonly int GetHashCode() => Mem.GetHashCode<byte>(this);

/// <summary>
/// Determines whether the specified object is equal to the current object.
/// </summary>
/// <param name="other">The <see cref="LogStringBuffer"/> to compare with the current object.</param>
/// <returns>true if the specified object is equal to the current object; otherwise, false.</returns>
public readonly bool Equals(LogStringBuffer other) => this[..].SequenceEqual(other);

///<inheritdoc/>
public override readonly bool Equals(object? obj) => obj is LogStringBuffer other && Equals(other);
}
9 changes: 6 additions & 3 deletions src/Backdash/Network/Client/PeerClientFactory.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using Backdash.Core;
using Backdash.Serialization;
using Backdash.Serialization.Internal;
Expand Down Expand Up @@ -30,11 +31,14 @@ public static IPeerClient<T> Create<T>(
maxPacketSize
);

#if !AOT_ENABLED
/// <summary>
/// Creates new <see cref="IPeerClient{T}"/>
/// </summary>
public static IPeerClient<T> Create<T>(
/// <remarks>Prefer using the <see cref="Create{T}(IPeerSocket, IBinarySerializer{T}, IPeerObserver{T}, int, LogLevel, ILogWriter?, DelayStrategy, Random?)"/> overload in NativeAoT/Trimmed applications</remarks>
#if !NET9_0_OR_GREATER
[RequiresDynamicCode("The native code for this instantiation might not be available at runtime.")]
#endif
public static IPeerClient<T> Create<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] T>(
IPeerSocket socket,
IPeerObserver<T> observer,
int maxPacketSize = Max.UdpPacketSize,
Expand All @@ -52,5 +56,4 @@ public static IPeerClient<T> Create<T>(
delayStrategy,
random
);
#endif
}
26 changes: 21 additions & 5 deletions src/Backdash/RollbackNetcode.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Net;
using Backdash.Backends;
using Backdash.Core;
Expand All @@ -22,7 +23,10 @@ public static class RollbackNetcode
/// <param name="options">Session configuration</param>
/// <param name="services">Session customizable dependencies</param>
/// <typeparam name="TInput">Game input type</typeparam>
public static INetcodeSession<TInput> CreateSession<TInput>(
#if !NET9_0_OR_GREATER
[RequiresDynamicCode("Requires dynamic code unless " + nameof(services) + "." + nameof(SessionServices<TInput>.InputSerializer) + " is valorized. If so, suppress this warning.")]
#endif
public static INetcodeSession<TInput> CreateSession<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput>(
int port,
NetcodeOptions? options = null,
SessionServices<TInput>? services = null
Expand All @@ -42,7 +46,10 @@ public static INetcodeSession<TInput> CreateSession<TInput>(
/// <param name="options">Session configuration</param>
/// <param name="services">Session customizable dependencies</param>
/// <typeparam name="TInput">Game input type</typeparam>
public static INetcodeSession<TInput> CreateSpectatorSession<TInput>(
#if !NET9_0_OR_GREATER
[RequiresDynamicCode("Requires dynamic code unless " + nameof(services) + "." + nameof(SessionServices<TInput>.InputSerializer) + " is valorized. If so, suppress this warning.")]
#endif
public static INetcodeSession<TInput> CreateSpectatorSession<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput>(
int port,
IPEndPoint host,
int numberOfPlayers,
Expand All @@ -61,7 +68,10 @@ public static INetcodeSession<TInput> CreateSpectatorSession<TInput>(
/// <param name="options">Session configuration</param>
/// <param name="services">Session customizable dependencies</param>
/// <typeparam name="TInput">Game input type</typeparam>
public static INetcodeSession<TInput> CreateLocalSession<TInput>(
#if !NET9_0_OR_GREATER
[RequiresDynamicCode("Requires dynamic code unless " + nameof(services) + "." + nameof(SessionServices<TInput>.InputSerializer) + " is valorized. If so, suppress this warning.")]
#endif
public static INetcodeSession<TInput> CreateLocalSession<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput>(
NetcodeOptions? options = null,
SessionServices<TInput>? services = null
)
Expand All @@ -80,7 +90,10 @@ public static INetcodeSession<TInput> CreateLocalSession<TInput>(
/// <param name="controls">replay control</param>
/// <param name="options">Session configuration</param>
/// <typeparam name="TInput">Game input type</typeparam>
public static INetcodeSession<TInput> CreateReplaySession<TInput>(
#if !NET9_0_OR_GREATER
[RequiresDynamicCode("Requires dynamic code unless " + nameof(services) + "." + nameof(SessionServices<TInput>.InputSerializer) + " is valorized. If so, suppress this warning.")]
#endif
public static INetcodeSession<TInput> CreateReplaySession<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput>(
int numberOfPlayers,
IReadOnlyList<ConfirmedInputs<TInput>> inputs,
SessionServices<TInput>? services = null,
Expand All @@ -105,7 +118,10 @@ public static INetcodeSession<TInput> CreateReplaySession<TInput>(
/// <param name="desyncHandler">State de-sync handler</param>
/// <param name="throwException">If true, throws on state de-synchronization.</param>
/// <typeparam name="TInput">Game input type</typeparam>
public static INetcodeSession<TInput> CreateSyncTestSession<TInput>(
#if !NET9_0_OR_GREATER
[RequiresDynamicCode("Requires dynamic code unless " + nameof(services) + "." + nameof(SessionServices<TInput>.InputSerializer) + " is valorized. If so, suppress this warning.")]
#endif
public static INetcodeSession<TInput> CreateSyncTestSession<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput>(
FrameSpan? checkDistance = null,
NetcodeOptions? options = null,
SessionServices<TInput>? services = null,
Expand Down
15 changes: 9 additions & 6 deletions src/Backdash/Serialization/Internal/BinarySerializerFactory.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Reflection;
using Backdash.Core;
Expand Down Expand Up @@ -32,12 +33,12 @@ public static IBinarySerializer<TInput> ForStruct<TInput>() where TInput : unman
return new StructBinarySerializer<TInput>();
}

public static IBinarySerializer<TInput>? Get<TInput>(bool networkEndianness = true)
#if !NET9_0_OR_GREATER
[RequiresDynamicCode("The native code for this instantiation might not be available at runtime.")]
#endif
public static IBinarySerializer<TInput>? 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
Expand All @@ -62,10 +63,12 @@ public static IBinarySerializer<TInput> ForStruct<TInput>() where TInput : unman
.Invoke(null, []) as IBinarySerializer<TInput>,
_ => null,
};
#endif
}

public static IBinarySerializer<TInput> FindOrThrow<TInput>(bool networkEndianness = true)
#if !NET9_0_OR_GREATER
[RequiresDynamicCode("The native code for this instantiation might not be available at runtime.")]
#endif
public static IBinarySerializer<TInput> FindOrThrow<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput>(bool networkEndianness = true)
where TInput : unmanaged =>
Get<TInput>(networkEndianness)
?? throw new InvalidOperationException($"Unable to infer serializer for type {typeof(TInput).FullName}");
Expand Down
1 change: 1 addition & 0 deletions tests/Backdash.Tests/Backdash.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<IsTestProject>true</IsTestProject>
<IsPackable>false</IsPackable>
<NoWarn>CS1591;NU1903</NoWarn>
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Bogus" Version="35.6.2" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using Backdash.Core;
using Backdash.Data;
using Backdash.Network;
Expand Down Expand Up @@ -45,7 +46,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();
Expand All @@ -71,7 +72,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();
Expand All @@ -88,7 +89,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();
Expand Down Expand Up @@ -118,7 +119,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();
Expand Down
Loading