Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NativeAoT support #107

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion _build/_build.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<RootNamespace></RootNamespace>
<NoWarn>$(NoWarn);CS0649;CS0169;CS1591;CS1573</NoWarn>
<Nullable>disable</Nullable>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>CS1591,S125,S1199,CS016,S1144,S3903,CS159,S1104</NoWarn>
Expand Down
4 changes: 2 additions & 2 deletions benchmarks/Backdash.Benchmarks/Backdash.Benchmarks.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>CS1591;S125;S1199;CS016;S1144;S3903;CS159;S1104;MSB3277</NoWarn>
Expand Down
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "8.0.404",
"version": "9.0.101",
"rollForward": "latestMinor",
"allowPrerelease": false
}
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>
internal 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 @@ -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
Expand Down
5 changes: 3 additions & 2 deletions src/Backdash.Utils/Backdash.Utils.csproj
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<RootNamespace>Backdash</RootNamespace>
<PackageId>Backdash.Utils</PackageId>
<Description>Backdash network utilities</Description>
<PackageTags>network, endpoint, multiplayer, json, input</PackageTags>
<NoWarn>CS1591</NoWarn>
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>

<ItemGroup>
Expand Down
7 changes: 4 additions & 3 deletions 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>
<TargetFramework>net9.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 @@ -21,7 +22,7 @@
</ItemGroup>

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

<ItemGroup>
Expand Down
5 changes: 3 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, TGameState>
sealed class BackendServices<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput, TGameState>
where TInput : unmanaged
where TGameState : notnull, new()
{
Expand Down Expand Up @@ -56,7 +57,7 @@ public BackendServices(RollbackOptions options, SessionServices<TInput, TGameSta

static class BackendServices
{
public static BackendServices<TInput, TGameState> Create<TInput, TGameState>(
public static BackendServices<TInput, TGameState> Create<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] TInput, TGameState>(
RollbackOptions options,
SessionServices<TInput, TGameState>? services
)
Expand Down
63 changes: 45 additions & 18 deletions src/Backdash/Backends/SyncTestBackend.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -39,6 +41,9 @@ GameInput<TInput> Input
IncludeFields = true,
};

readonly JsonTypeInfo<TGameState>? gameStateJsonTypeInfo;
readonly JsonTypeInfo<TInput>? inputJsonTypeInfo;

IRollbackHandler<TGameState> callbacks;
bool inRollback;
bool running;
Expand All @@ -47,6 +52,8 @@ GameInput<TInput> Input
GameInput<TInput> 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,
Expand Down Expand Up @@ -78,6 +85,29 @@ BackendServices<TInput, TGameState> 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<TInput, TGameState> services,
JsonTypeInfo<TGameState> gameStateJsonTypeInfo,
JsonTypeInfo<TInput> inputJsonTypeInfo
) : this(
options,
checkDistance,
throwError,
services
)
{
ArgumentNullException.ThrowIfNull(gameStateJsonTypeInfo);
ArgumentNullException.ThrowIfNull(inputJsonTypeInfo);
this.gameStateJsonTypeInfo = gameStateJsonTypeInfo;
this.inputJsonTypeInfo = inputJsonTypeInfo;
}

public void Dispose() => tsc.SetResult();
Expand Down Expand Up @@ -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<TGameState>")),
Checksum: lastSaved.Checksum
));
if (frame - lastVerified != checkDistance.FrameValue)
Expand Down Expand Up @@ -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<TInput> ?? throw new InvalidOperationException("Could not get JsonTypeInfo<TInput>")));
logger.Write(level, $"GAME STATE #{info.Checksum}:");
LogJson(level, info.State);
LogStringChunked(level, info.State);
logger.Write(level, "====================================");
}

Expand All @@ -274,18 +296,23 @@ void LogSaveState(SavedFrame<TGameState> 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<TGameState> ?? throw new InvalidOperationException("Could not get JsonTypeInfo<TGameState>"));
logger.Write(level, "====================================");
}

void LogJson<TValue>(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<TValue>(LogLevel level, TValue value, JsonTypeInfo<TValue> 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)
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);
}
25 changes: 17 additions & 8 deletions src/Backdash/Core/TypeHelpers.cs
Original file line number Diff line number Diff line change
@@ -1,27 +1,37 @@
#if !AOT_ENABLED
using System.Diagnostics.CodeAnalysis;
using System.Reflection;

namespace Backdash.Core;

static class TypeHelpers
{
public static T? Instantiate<T>(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<T>() 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<T>.Default;
return Instantiate<T>() is { } state1
Expand All @@ -31,4 +41,3 @@ public static bool HasInvariantHashCode<T>() where T : notnull
&& comparer.GetHashCode(state1) == comparer.GetHashCode(state3);
}
}
#endif
6 changes: 3 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;

Expand Down Expand Up @@ -30,11 +31,11 @@ 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>
public static IPeerClient<T> Create<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] T>(
IPeerSocket socket,
IPeerObserver<T> observer,
int maxPacketSize = Max.UdpPacketSize,
Expand All @@ -52,5 +53,4 @@ public static IPeerClient<T> Create<T>(
delayStrategy,
random
);
#endif
}
Loading