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
65 changes: 65 additions & 0 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Build & Test

on:
push:
branches: [robust]
merge_group:
pull_request:
types: [ opened, reopened, synchronize, ready_for_review ]
branches: [robust]
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name != 'merge_group' }}

env:
DOTNET_NOLOGO: true

jobs:
build:
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]

runs-on: ${{ matrix.os }}
timeout-minutes: 30

steps:
- name: Checkout
uses: actions/checkout@v4.2.2

- name: Setup .NET
uses: actions/setup-dotnet@v4.1.0
with:
dotnet-version: 10.0.x

- name: Restore
run: dotnet restore NetSerializer.sln

- name: Build
run: dotnet build NetSerializer.sln --no-restore /p:WarningsAsErrors=nullable

- name: Run unit tests
shell: pwsh
run: dotnet test NetSerializer.UnitTests/NetSerializer.UnitTests.csproj --no-build -- NUnit.ConsoleOut=0 NUnit.TestOutputXml="logs" NUnit.WorkDirectory="${{ github.workspace }}/test_results"

- name: Archive NUnit3 test results
if: always()
uses: actions/upload-artifact@v4
with:
name: nunit3-results-${{ matrix.os }}
path: test_results/*
retention-days: 7
compression-level: 9

ci-success:
name: Build & Test
needs:
- build
runs-on: ubuntu-latest
steps:
- name: CI succeeded
run: exit 0
133 changes: 133 additions & 0 deletions NetSerializer.Benchmarks/BenchmarkMessages.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace NetSerializer.Benchmarks;

[Serializable]
internal struct BenchStruct
{
public int Id;
public long Tick;
public float X;
public float Y;
public double Rotation;

public static BenchStruct Create()
{
return new BenchStruct
{
Id = 42,
Tick = 123456789,
X = 12.5f,
Y = -8.25f,
Rotation = 1.25,
};
}

public static void AssertEqual(BenchStruct actual, BenchStruct expected)
{
if (actual.Id != expected.Id ||
actual.Tick != expected.Tick ||
actual.X != expected.X ||
actual.Y != expected.Y ||
actual.Rotation != expected.Rotation)
{
throw new InvalidOperationException("BenchStruct round-trip mismatch.");
}
}
}

[Serializable]
internal sealed class BenchMessage
{
public int Sequence;
public BenchStruct Coordinates;
public int[] EntityIds = [];
public string[] Names = [];
public Dictionary<int, BenchStruct> Components = new();

public static BenchMessage Create(int elementCount)
{
var message = new BenchMessage
{
Sequence = 123,
Coordinates = BenchStruct.Create(),
EntityIds = new int[elementCount],
Names = new string[elementCount / 8],
};

for (var i = 0; i < message.EntityIds.Length; i++)
{
message.EntityIds[i] = i * 3;
}

for (var i = 0; i < message.Names.Length; i++)
{
message.Names[i] = $"entity-{i}";
}

for (var i = 0; i < elementCount / 16; i++)
{
message.Components[i] = new BenchStruct
{
Id = i,
Tick = i * 1000L,
X = i + 0.25f,
Y = -i - 0.5f,
Rotation = i * 0.125,
};
}

return message;
}

public static void AssertEqual(BenchMessage actual, BenchMessage expected)
{
if (actual.Sequence != expected.Sequence)
throw new InvalidOperationException("BenchMessage sequence mismatch.");

BenchStruct.AssertEqual(actual.Coordinates, expected.Coordinates);

if (!actual.EntityIds.SequenceEqual(expected.EntityIds))
throw new InvalidOperationException("BenchMessage entity-id array mismatch.");

if (!actual.Names.SequenceEqual(expected.Names))
throw new InvalidOperationException("BenchMessage names array mismatch.");

if (actual.Components.Count != expected.Components.Count)
throw new InvalidOperationException("BenchMessage component count mismatch.");

foreach (var (key, value) in expected.Components)
{
BenchStruct.AssertEqual(actual.Components[key], value);
}
}
}

[Serializable]
internal sealed class IntArrayMessage
{
public int[] Values = [];

public static IntArrayMessage Create(int elementCount)
{
var message = new IntArrayMessage
{
Values = new int[elementCount],
};

for (var i = 0; i < message.Values.Length; i++)
{
message.Values[i] = i * 7;
}

return message;
}

public static void AssertEqual(IntArrayMessage actual, IntArrayMessage expected)
{
if (!actual.Values.SequenceEqual(expected.Values))
throw new InvalidOperationException("IntArrayMessage array mismatch.");
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<OutputType>Exe</OutputType>
<IsPackable>false</IsPackable>
<Optimize>true</Optimize>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
Expand Down
107 changes: 107 additions & 0 deletions NetSerializer.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using NS = NetSerializer;

namespace NetSerializer.Benchmarks;

internal static class Program
{
private const int PayloadElementCount = 512;

public static void Main(string[] args)
{
var iterations = args.Contains("--quick") ? 5_000 : 100_000;
var serializer = new NS.Serializer(new[] { typeof(BenchStruct), typeof(BenchMessage), typeof(IntArrayMessage) });
var benchStruct = BenchStruct.Create();
var benchMessage = BenchMessage.Create(PayloadElementCount);
var intArrayMessage = IntArrayMessage.Create(PayloadElementCount);

AssertRoundTrip(serializer, benchStruct, BenchStruct.AssertEqual);
AssertRoundTrip(serializer, benchMessage, BenchMessage.AssertEqual);
AssertRoundTrip(serializer, intArrayMessage, IntArrayMessage.AssertEqual);

Console.WriteLine($"NetSerializer runtime benchmarks ({iterations:N0} iterations)");
Console.WriteLine("Lower is better. Round-trip correctness is checked before timing.");
Console.WriteLine();
Console.WriteLine($"{"Scenario",-40} {"Elapsed",12}");
Console.WriteLine(new string('-', 53));

Run("BenchStruct direct serialize", iterations, () => SerializeDirect(serializer, benchStruct));
Run("BenchStruct direct deserialize", iterations, () => DeserializeDirect<BenchStruct>(serializer, benchStruct));
Run("BenchMessage object serialize", iterations, () => SerializeObject(serializer, benchMessage));
Run("BenchMessage object deserialize", iterations, () => DeserializeObject(serializer, benchMessage));
Run("BenchMessage direct serialize", iterations, () => SerializeDirect(serializer, benchMessage));
Run("BenchMessage direct deserialize", iterations, () => DeserializeDirect<BenchMessage>(serializer, benchMessage));
Run("IntArrayMessage object serialize", iterations, () => SerializeObject(serializer, intArrayMessage));
Run("IntArrayMessage object deserialize", iterations, () => DeserializeObject(serializer, intArrayMessage));
}

private static void Run(string name, int iterations, Action action)
{
action();
var stopwatch = Stopwatch.StartNew();
for (var i = 0; i < iterations; i++)
{
action();
}

stopwatch.Stop();
Console.WriteLine($"{name,-40} {Format(stopwatch.Elapsed),12}");
}

private static string Format(TimeSpan elapsed)
{
if (elapsed.TotalMilliseconds >= 1)
return $"{elapsed.TotalMilliseconds:N2} ms";

return $"{elapsed.TotalMicroseconds:N2} us";
}

private static void AssertRoundTrip<T>(NS.Serializer serializer, T value, Action<T, T> assertEqual)
{
var objectBytes = SerializeObject(serializer, value!);
using (var stream = new MemoryStream(objectBytes))
{
var read = (T) serializer.Deserialize(stream);
assertEqual(read, value);
}

var directBytes = SerializeDirect(serializer, value);
using (var stream = new MemoryStream(directBytes))
{
serializer.DeserializeDirect(stream, out T read);
assertEqual(read, value);
}
}

private static byte[] SerializeObject(NS.Serializer serializer, object value)
{
using var stream = new MemoryStream();
serializer.Serialize(stream, value);
return stream.ToArray();
}

private static object DeserializeObject<T>(NS.Serializer serializer, T value)
{
var bytes = SerializeObject(serializer, value!);
using var stream = new MemoryStream(bytes);
return serializer.Deserialize(stream);
}

private static byte[] SerializeDirect<T>(NS.Serializer serializer, T value)
{
using var stream = new MemoryStream();
serializer.SerializeDirect(stream, value);
return stream.ToArray();
}

private static T DeserializeDirect<T>(NS.Serializer serializer, T value)
{
var bytes = SerializeDirect(serializer, value);
using var stream = new MemoryStream(bytes);
serializer.DeserializeDirect(stream, out T read);
return read;
}
}
2 changes: 1 addition & 1 deletion NetSerializer.UnitTests/HalfTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#if NET5_0
#if NET5_0_OR_GREATER
using System;
using System.IO;
using NUnit.Framework;
Expand Down
17 changes: 5 additions & 12 deletions NetSerializer.UnitTests/NetSerializer.UnitTests.csproj
Original file line number Diff line number Diff line change
@@ -1,26 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net45;netcoreapp3.1;net5.0</TargetFrameworks>
<TargetFramework>net10.0</TargetFramework>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.16.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="NUnit.Analyzers" Version="0.5.0" />
<PackageReference Include="NUnit" Version="4.4.0" />
<PackageReference Include="NUnit3TestAdapter" Version="5.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="NUnit.Analyzers" Version="4.11.2" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\NetSerializer\NetSerializer.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion NetSerializer.UnitTests/PrimitivesTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace NetSerializer.UnitTests
[Parallelizable(ParallelScope.All)]
public class PrimitivesTest
{
#if NET5_0
#if NET5_0_OR_GREATER
#if NO_UNSAFE
[Ignore("Float and half tests are inacurrate due to rounding when NO_UNSAFE is enabled.")]
#endif
Expand Down
Loading