using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Text.Json; using SideScrollerGame.Sim.Math; using SideScrollerGame.Sim.Runtime; namespace SideScrollerGame.Sim.Serialization; [ExcludeFromCodeCoverage] internal static class SimulationStateSerializer { [ExcludeFromCodeCoverage] private sealed record SimulationStateDocument { public int Version { get; init; } public int Tick { get; init; } public int Seed { get; init; } public ulong RandomState { get; init; } public ulong LastRandomValue { get; init; } public ImmutableArray Players { get; init; } public ImmutableArray ActivatedTriggerIds { get; init; } } [ExcludeFromCodeCoverage] private sealed record PlayerStateDocument { public int PlayerId { get; init; } public int PositionX { get; init; } public int PositionY { get; init; } public sbyte MoveAxisX { get; init; } public sbyte MoveAxisY { get; init; } public short AimAxisX { get; init; } public short AimAxisY { get; init; } public int SelectedWeaponSlot { get; init; } public int ButtonMask { get; init; } public int Health { get; init; } } public static byte[] Serialize(SimulationState state) { List players = new(state.Players.Length); foreach (var player in state.Players) { players.Add(new() { PlayerId = player.PlayerId.Value, PositionX = player.Position.m_X.m_Value, PositionY = player.Position.m_Y.m_Value, MoveAxisX = player.MoveAxisX, MoveAxisY = player.MoveAxisY, AimAxisX = player.AimAxisX, AimAxisY = player.AimAxisY, SelectedWeaponSlot = player.SelectedWeaponSlot, ButtonMask = player.ButtonMask, Health = player.Health }); } return JsonSerializer.SerializeToUtf8Bytes(new SimulationStateDocument { Version = SimulationDefaults.StateFormatVersion, Tick = state.Tick, Seed = state.Seed, RandomState = state.RandomState, LastRandomValue = state.LastRandomValue, Players = players.ToImmutableArray(), ActivatedTriggerIds = state.ActivatedTriggerIds.Order(StringComparer.Ordinal).ToImmutableArray() }); } public static SimulationState Deserialize(byte[] data) { var document = JsonSerializer.Deserialize(data) ?? throw new InvalidOperationException("Simulation state payload was empty."); if (document.Version != SimulationDefaults.StateFormatVersion) throw new NotSupportedException($"Unsupported simulation state version {document.Version}."); var players = ImmutableArray.CreateBuilder(document.Players.Length); foreach (var player in document.Players) players.Add(new(new(player.PlayerId), new(new() { m_Value = player.PositionX }, new FixPoint16 { m_Value = player.PositionY }), player.MoveAxisX, player.MoveAxisY, player.AimAxisX, player.AimAxisY, player.SelectedWeaponSlot, player.ButtonMask, player.Health)); return new(document.Tick, document.Seed, document.RandomState, document.LastRandomValue, players.MoveToImmutable(), document.ActivatedTriggerIds.ToImmutableHashSet(StringComparer.Ordinal)); } }