Skip to content
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
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
7 changes: 4 additions & 3 deletions src/Backdash/Backdash.csproj
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
<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>
<OptimizationPreference>Speed</OptimizationPreference>
</PropertyGroup>

<PropertyGroup Condition="$([System.Text.RegularExpressions.Regex]::IsMatch($(DefineConstants), '^(.*;)*AOT_ENABLED(;.*)*$'))">
<IsAotCompatible>true</IsAotCompatible>
<OptimizationPreference>Speed</OptimizationPreference>
<PublishAot>true</PublishAot>
</PropertyGroup>

<ItemGroup>
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);
}
14 changes: 14 additions & 0 deletions src/Backdash/Options/SyncTestOptions.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.Synchronizing.Input;
using Backdash.Synchronizing.State;

Expand Down Expand Up @@ -50,12 +52,24 @@ public SyncTestOptions<TInput> UseRandomInputProvider()
/// <summary>
/// Use <see cref="JsonStateStringParser" /> as state viewer.
/// </summary>
/// <remarks>For AoT compatibility, use <see cref="UseJsonStateViewer(JsonTypeInfo)"/> instead.</remarks>
[RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo instead.")]
[RequiresDynamicCode("JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use System.Text.Json source generation for native AOT applications.")]
public SyncTestOptions<TInput> UseJsonStateViewer(JsonSerializerOptions? options = null)
{
StateStringParser = new JsonStateStringParser(options);
return this;
}

/// <summary>
/// Use <see cref="JsonStateStringParser" /> as state viewer.
/// </summary>
public SyncTestOptions<TInput> UseJsonStateViewer(JsonTypeInfo stateTypeInfo)
{
StateStringParser = new JsonStateStringParser(stateTypeInfo);
return this;
}

/// <inheritdoc cref="StateStringParser" />
public SyncTestOptions<TInput> UseStateViewer<T>() where T : IStateStringParser, new()
{
Expand Down
34 changes: 27 additions & 7 deletions src/Backdash/Synchronizing/State/IStateStringParser.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.Serialization;

Expand Down Expand Up @@ -34,17 +36,35 @@ public string GetStateString(in Frame frame, ref readonly BinaryBufferReader rea
/// Try to get the json string representation of the state.
/// </summary>
public sealed class JsonStateStringParser(
JsonSerializerOptions? options = null,
JsonTypeInfo stateTypeInfo,
IStateStringParser? stateStringFallback = null
) : IStateStringParser
{
/// <summary>
/// Try to get the json string representation of the state.
/// </summary>
/// <remarks>For AoT compatibility, use <see cref="JsonStateStringParser(JsonTypeInfo,IStateStringParser?)"/> instead.</remarks>
/// <param name="options"></param>
/// <param name="stateStringFallback"></param>
[RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo instead.")]
[RequiresDynamicCode("JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use System.Text.Json source generation for native AOT applications.")]
public JsonStateStringParser(
JsonSerializerOptions? options = null,
IStateStringParser? stateStringFallback = null
) : this(
(options ?? new()
{
WriteIndented = true,
IncludeFields = true,
}).GetTypeInfo(typeof(object)),
stateStringFallback)
{

}

internal Logger? Logger = null;

readonly JsonSerializerOptions jsonOptions = options ?? new()
{
WriteIndented = true,
IncludeFields = true,
};
readonly JsonTypeInfo typeInfo = stateTypeInfo ?? throw new ArgumentNullException(nameof(stateTypeInfo));

readonly IStateStringParser fallback = stateStringFallback ?? new HexStateStringParser();

Expand All @@ -56,7 +76,7 @@ public string GetStateString(in Frame frame, ref readonly BinaryBufferReader rea

try
{
return JsonSerializer.Serialize(currentState, jsonOptions);
return JsonSerializer.Serialize(currentState, typeInfo);
}
catch (Exception e)
{
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 @@ -46,7 +47,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 @@ -72,7 +73,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 @@ -89,7 +90,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 @@ -119,7 +120,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
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -10,37 +12,30 @@ 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<TestType>($$"""{"Data": "{{expected.ToString()}}"}""", options);
var value = Deserialize<IpAddressTestType>($$"""{"Data": "{{expected.ToString()}}"}""", JsonSourceGenerationContext.Default.IpAddressTestType);
value!.Data.Should().Be(expected);
}

[Fact]
public void ShouldParseIPv6()
{
var expected = faker.Internet.Ipv6Address();
var value = Deserialize<TestType>($$"""{"Data": "{{expected.ToString()}}"}""", options);
var value = Deserialize<IpAddressTestType>($$"""{"Data": "{{expected.ToString()}}"}""", JsonSourceGenerationContext.Default.IpAddressTestType);
value!.Data.Should().Be(expected);
}

[Fact]
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);
}
Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -10,37 +12,29 @@ 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<TestType>($$"""{"Data": "{{expected}}"}""", options);
var value = Deserialize<IpEndPointTestType>($$"""{"Data": "{{expected}}"}""", JsonSourceGenerationContext.Default.IpEndPointTestType);
value!.Data.Should().Be(expected);
}

[Fact]
public void ShouldParseIPv6()
{
var expected = faker.Internet.Ipv6EndPoint();
var value = Deserialize<TestType>($$"""{"Data": "{{expected}}"}""", options);
var value = Deserialize<IpEndPointTestType>($$"""{"Data": "{{expected}}"}""", JsonSourceGenerationContext.Default.IpEndPointTestType);
value!.Data.Should().Be(expected);
}

[Fact]
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);
}
Expand All @@ -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);
}
Expand Down
22 changes: 22 additions & 0 deletions tests/Backdash.Tests/TestUtils/DynamicFactAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Runtime.CompilerServices;

namespace Backdash.Tests.TestUtils
{
/// <summary>
/// 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.
/// </summary>
[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;
}
}
}
}
12 changes: 12 additions & 0 deletions tests/Backdash.Tests/TestUtils/JsonSourceGenerationContext.cs
Original file line number Diff line number Diff line change
@@ -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
{
}
}
Loading