mirror of
https://github.com/Impostor/Impostor.git
synced 2026-10-20 01:46:11 +00:00
Solved GameOptionsData TODO (#64)
* Ignoring .Vs folder * GameOptionsData deserialization requires x16 less time & no extra alloc * Super-micro optimisation (variable localization) * JoinGame probably faster * Added Tests * Renamed swap -> advance * Removed submodule Hazel * Removed launchsettings Co-authored-by: Weirdo <git.jonas@kamsker.at>
This commit is contained in:
1 parent
647672c40e
commit
7a210393ee
12 files changed
+227
-80
No files matched your search
@@ -2,3 +2,4 @@
|
||||
/build
|
||||
/.vs
|
||||
/src/Impostor.Plugins.Debugger/Properties/launchSettings.json
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Impostor.Api.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Priovides a StreamReader-like api throught extensions
|
||||
/// </summary>
|
||||
public static class SpanReaderExtensions
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static byte ReadByte(this ref ReadOnlySpan<byte> input)
|
||||
{
|
||||
var original = Advance<byte>(ref input);
|
||||
return original[0];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int ReadInt32(this ref ReadOnlySpan<byte> input)
|
||||
{
|
||||
var original = Advance<int>(ref input);
|
||||
return BinaryPrimitives.ReadInt32LittleEndian(original);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint ReadUInt32(this ref ReadOnlySpan<byte> input)
|
||||
{
|
||||
var original = Advance<uint>(ref input);
|
||||
return BinaryPrimitives.ReadUInt32LittleEndian(original);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float ReadSingle(this ref ReadOnlySpan<byte> input)
|
||||
{
|
||||
var original = Advance<float>(ref input);
|
||||
|
||||
// BitConverter.Int32BitsToSingle
|
||||
// Doesn't exist in net 2.0 for some reason
|
||||
return Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(original));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool ReadBoolean(this ref ReadOnlySpan<byte> input)
|
||||
{
|
||||
return input.ReadByte() != 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe float Int32BitsToSingle(int value)
|
||||
{
|
||||
return *((float*)&value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Advances the position of <see cref="input"/> by the size of <see cref="T"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type that will be read.</typeparam>
|
||||
/// <param name="input">input "stream"/span.</param>
|
||||
/// <returns>The original input</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe ReadOnlySpan<byte> Advance<T>(ref ReadOnlySpan<byte> input)
|
||||
where T : unmanaged
|
||||
{
|
||||
var original = input;
|
||||
input = input.Slice(sizeof(T));
|
||||
return original;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,14 @@
|
||||
<LangVersion>9</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0-rc.1.20451.14" />
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Impostor.Api.Extensions;
|
||||
using Impostor.Api.Innersloth.Data;
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Impostor.Api.Innersloth
|
||||
{
|
||||
public class GameOptionsData
|
||||
@@ -29,78 +31,78 @@ namespace Impostor.Api.Innersloth
|
||||
public bool ConfirmImpostor { get; set; }
|
||||
public bool VisualTasks { get; set; }
|
||||
public bool IsDefaults { get; set; }
|
||||
|
||||
|
||||
public void Serialize(BinaryWriter writer, byte version)
|
||||
{
|
||||
writer.Write((byte) version);
|
||||
writer.Write((byte) MaxPlayers);
|
||||
writer.Write((uint) Keywords);
|
||||
writer.Write((byte) MapId);
|
||||
writer.Write((float) PlayerSpeedMod);
|
||||
writer.Write((float) CrewLightMod);
|
||||
writer.Write((float) ImpostorLightMod);
|
||||
writer.Write((float) KillCooldown);
|
||||
writer.Write((byte) NumCommonTasks);
|
||||
writer.Write((byte) NumLongTasks);
|
||||
writer.Write((byte) NumShortTasks);
|
||||
writer.Write((int) NumEmergencyMeetings);
|
||||
writer.Write((byte) NumImpostors);
|
||||
writer.Write((byte) KillDistance);
|
||||
writer.Write((uint) DiscussionTime);
|
||||
writer.Write((uint) VotingTime);
|
||||
writer.Write((bool) IsDefaults);
|
||||
writer.Write((byte)version);
|
||||
writer.Write((byte)MaxPlayers);
|
||||
writer.Write((uint)Keywords);
|
||||
writer.Write((byte)MapId);
|
||||
writer.Write((float)PlayerSpeedMod);
|
||||
writer.Write((float)CrewLightMod);
|
||||
writer.Write((float)ImpostorLightMod);
|
||||
writer.Write((float)KillCooldown);
|
||||
writer.Write((byte)NumCommonTasks);
|
||||
writer.Write((byte)NumLongTasks);
|
||||
writer.Write((byte)NumShortTasks);
|
||||
writer.Write((int)NumEmergencyMeetings);
|
||||
writer.Write((byte)NumImpostors);
|
||||
writer.Write((byte)KillDistance);
|
||||
writer.Write((uint)DiscussionTime);
|
||||
writer.Write((uint)VotingTime);
|
||||
writer.Write((bool)IsDefaults);
|
||||
if (version > 1)
|
||||
{
|
||||
writer.Write((byte) EmergencyCooldown);
|
||||
writer.Write((byte)EmergencyCooldown);
|
||||
}
|
||||
|
||||
if (version > 2)
|
||||
{
|
||||
writer.Write((bool) ConfirmImpostor);
|
||||
writer.Write((bool) VisualTasks);
|
||||
writer.Write((bool)ConfirmImpostor);
|
||||
writer.Write((bool)VisualTasks);
|
||||
}
|
||||
}
|
||||
|
||||
public static GameOptionsData Deserialize(ReadOnlyMemory<byte> bytes)
|
||||
public static GameOptionsData Deserialize(ReadOnlyMemory<byte> memory)
|
||||
{
|
||||
// TODO: Remove memory allocation.
|
||||
var bytes = memory.Span;
|
||||
|
||||
using (var stream = new MemoryStream(bytes.ToArray()))
|
||||
using (var reader = new BinaryReader(stream))
|
||||
var result = new GameOptionsData();
|
||||
result.Version = bytes.ReadByte();
|
||||
result.MaxPlayers = bytes.ReadByte();
|
||||
result.Keywords = (GameKeywords)bytes.ReadUInt32();
|
||||
result.MapId = bytes.ReadByte();
|
||||
result.PlayerSpeedMod = bytes.ReadSingle();
|
||||
|
||||
result.CrewLightMod = bytes.ReadSingle();
|
||||
result.ImpostorLightMod = bytes.ReadSingle();
|
||||
result.KillCooldown = bytes.ReadSingle();
|
||||
|
||||
result.NumCommonTasks = bytes.ReadByte();
|
||||
result.NumLongTasks = bytes.ReadByte();
|
||||
result.NumShortTasks = bytes.ReadByte();
|
||||
|
||||
result.NumEmergencyMeetings = bytes.ReadInt32();
|
||||
|
||||
result.NumImpostors = bytes.ReadByte();
|
||||
result.KillDistance = bytes.ReadByte();
|
||||
result.DiscussionTime = bytes.ReadInt32();
|
||||
result.VotingTime = bytes.ReadInt32();
|
||||
|
||||
result.IsDefaults = bytes.ReadBoolean();
|
||||
|
||||
if (result.Version > 1)
|
||||
{
|
||||
var result = new GameOptionsData();
|
||||
|
||||
result.Version = reader.ReadByte();
|
||||
result.MaxPlayers = reader.ReadByte();
|
||||
result.Keywords = (GameKeywords) reader.ReadUInt32();
|
||||
result.MapId = reader.ReadByte();
|
||||
result.PlayerSpeedMod = reader.ReadSingle();
|
||||
result.CrewLightMod = reader.ReadSingle();
|
||||
result.ImpostorLightMod = reader.ReadSingle();
|
||||
result.KillCooldown = reader.ReadSingle();
|
||||
result.NumCommonTasks = reader.ReadByte();
|
||||
result.NumLongTasks = reader.ReadByte();
|
||||
result.NumShortTasks = reader.ReadByte();
|
||||
result.NumEmergencyMeetings = reader.ReadInt32();
|
||||
result.NumImpostors = reader.ReadByte();
|
||||
result.KillDistance = reader.ReadByte();
|
||||
result.DiscussionTime = reader.ReadInt32();
|
||||
result.VotingTime = reader.ReadInt32();
|
||||
result.IsDefaults = reader.ReadBoolean();
|
||||
|
||||
if (result.Version > 1)
|
||||
{
|
||||
result.EmergencyCooldown = reader.ReadByte();
|
||||
}
|
||||
|
||||
if (result.Version > 2)
|
||||
{
|
||||
result.ConfirmImpostor = reader.ReadBoolean();
|
||||
result.VisualTasks = reader.ReadBoolean();
|
||||
}
|
||||
|
||||
return result;
|
||||
result.EmergencyCooldown = bytes.ReadByte();
|
||||
}
|
||||
|
||||
if (result.Version > 2)
|
||||
{
|
||||
result.ConfirmImpostor = bytes.ReadBoolean();
|
||||
result.VisualTasks = bytes.ReadBoolean();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
namespace Impostor.Api.Net.Messages.C2S
|
||||
using System;
|
||||
using Impostor.Api.Extensions;
|
||||
|
||||
namespace Impostor.Api.Net.Messages.C2S
|
||||
{
|
||||
public static class Message01JoinGameC2S
|
||||
{
|
||||
@@ -9,8 +12,10 @@
|
||||
|
||||
public static void Deserialize(IMessageReader reader, out int gameCode, out byte unknown)
|
||||
{
|
||||
gameCode = reader.ReadInt32();
|
||||
unknown = reader.ReadByte();
|
||||
var slice = reader.ReadBytes(sizeof(Int32) + sizeof(byte)).Span;
|
||||
|
||||
gameCode = slice.ReadInt32();
|
||||
unknown = slice.ReadByte();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Impostor.Api.Innersloth.Data;
|
||||
using Impostor.Api.Extensions;
|
||||
using Impostor.Api.Innersloth.Data;
|
||||
|
||||
namespace Impostor.Api.Net.Messages.C2S
|
||||
{
|
||||
@@ -11,8 +12,10 @@ namespace Impostor.Api.Net.Messages.C2S
|
||||
|
||||
public static void Deserialize(IMessageReader reader, out AlterGameTags gameTag, out bool isPublic)
|
||||
{
|
||||
gameTag = (AlterGameTags)reader.ReadByte();
|
||||
isPublic = reader.ReadBoolean();
|
||||
var slice = reader.ReadBytes(sizeof(byte) + sizeof(byte)).Span;
|
||||
|
||||
gameTag = (AlterGameTags)slice.ReadByte();
|
||||
isPublic = slice.ReadBoolean();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ namespace Impostor.Hazel
|
||||
public IMessageReader ReadMessage()
|
||||
{
|
||||
var length = ReadUInt16();
|
||||
var tag = ReadByte();
|
||||
var tag = FastByte();
|
||||
var pos = Position;
|
||||
|
||||
Position += length;
|
||||
@@ -121,7 +121,7 @@ namespace Impostor.Hazel
|
||||
|
||||
while (readMore)
|
||||
{
|
||||
byte b = ReadByte();
|
||||
byte b = FastByte();
|
||||
if (b >= 0x80)
|
||||
{
|
||||
readMore = true;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using System;
|
||||
using Impostor.Api.Games;
|
||||
using Impostor.Api.Net.Messages;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using Impostor.Api.Games;
|
||||
using Impostor.Api.Net.Messages;
|
||||
|
||||
namespace Impostor.Hazel
|
||||
{
|
||||
@@ -16,7 +17,7 @@ namespace Impostor.Hazel
|
||||
public MessageType SendOption { get; private set; }
|
||||
|
||||
private Stack<int> messageStarts = new Stack<int>();
|
||||
|
||||
|
||||
public MessageWriter(byte[] buffer)
|
||||
{
|
||||
this.Buffer = buffer;
|
||||
@@ -56,6 +57,8 @@ namespace Impostor.Hazel
|
||||
System.Buffer.BlockCopy(this.Buffer, 1, output, 0, this.Length - 1);
|
||||
return output;
|
||||
}
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +125,7 @@ namespace Impostor.Hazel
|
||||
case MessageType.Unreliable:
|
||||
this.Length = this.Position = 1;
|
||||
break;
|
||||
|
||||
case MessageType.Reliable:
|
||||
this.Length = this.Position = 3;
|
||||
break;
|
||||
@@ -235,7 +239,15 @@ namespace Impostor.Hazel
|
||||
|
||||
public void Write(ReadOnlyMemory<byte> data)
|
||||
{
|
||||
this.Write(data.ToArray()); // TODO: Fix memory allocation.
|
||||
Write(data.Span);
|
||||
}
|
||||
|
||||
public void Write(ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
bytes.CopyTo(this.Buffer.AsSpan(this.Position, bytes.Length));
|
||||
|
||||
this.Position += bytes.Length;
|
||||
if (this.Position > this.Length) this.Length = this.Position;
|
||||
}
|
||||
|
||||
public void Write(byte[] bytes)
|
||||
@@ -280,7 +292,8 @@ namespace Impostor.Hazel
|
||||
value >>= 7;
|
||||
} while (value > 0);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#endregion WriteMethods
|
||||
|
||||
public void Write(MessageWriter msg, bool includeHeader)
|
||||
{
|
||||
@@ -292,6 +305,7 @@ namespace Impostor.Hazel
|
||||
case MessageType.Unreliable:
|
||||
offset = 1;
|
||||
break;
|
||||
|
||||
case MessageType.Reliable:
|
||||
offset = 3;
|
||||
break;
|
||||
@@ -319,4 +333,4 @@ namespace Impostor.Hazel
|
||||
Recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
@@ -9,4 +9,8 @@
|
||||
<ProjectReference Include="..\Impostor.Api\Impostor.Api.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Properties\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -25,6 +25,7 @@ namespace Impostor.Server.GameData.Objects.Components
|
||||
|
||||
public override void Deserialize(IMessageReader reader, bool initialState)
|
||||
{
|
||||
var votes = _votes;
|
||||
var unknown = reader.ReadBoolean();
|
||||
if (unknown)
|
||||
{
|
||||
@@ -32,10 +33,10 @@ namespace Impostor.Server.GameData.Objects.Components
|
||||
{
|
||||
var v4 = reader.ReadInt32();
|
||||
|
||||
if (!_votes.TryGetValue(v4, out var v12))
|
||||
if (!votes.TryGetValue(v4, out var v12))
|
||||
{
|
||||
v12 = new int[3];
|
||||
_votes[v4] = v12;
|
||||
votes[v4] = v12;
|
||||
}
|
||||
|
||||
for (var i = 0; i < 3; i++)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Impostor.Api.Innersloth;
|
||||
|
||||
using Xunit;
|
||||
|
||||
namespace Impostor.Tests
|
||||
@@ -10,17 +11,17 @@ namespace Impostor.Tests
|
||||
{
|
||||
const string code = "ABCD";
|
||||
const int codeInt = 0x44434241;
|
||||
|
||||
|
||||
Assert.Equal(code, GameCodeParser.IntToGameName(codeInt));
|
||||
Assert.Equal(codeInt, GameCodeParser.GameNameToInt(code));
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void CodeV2()
|
||||
{
|
||||
const string code = "ABCDEF";
|
||||
const int codeInt = -1943683525;
|
||||
|
||||
|
||||
Assert.Equal(code, GameCodeParser.IntToGameName(codeInt));
|
||||
Assert.Equal(codeInt, GameCodeParser.GameNameToInt(code));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Impostor.Hazel;
|
||||
using Xunit;
|
||||
|
||||
namespace Impostor.Tests.Hazel
|
||||
{
|
||||
public class MessageWriterTests
|
||||
{
|
||||
[Fact]
|
||||
public void ReadOnlyMemoryWriteWorksTheSameAsArray()
|
||||
{
|
||||
var oldVer = new MessageWriter(1024);
|
||||
var newVer = new MessageWriter(1024);
|
||||
|
||||
var data = Enumerable.Repeat
|
||||
(
|
||||
Enumerable.Range(0, byte.MaxValue)
|
||||
.Select(x => (byte)x),
|
||||
2
|
||||
).SelectMany(x => x).ToArray();
|
||||
|
||||
WriteSomeData(oldVer);
|
||||
WriteSomeData(newVer);
|
||||
|
||||
oldVer.Write(data);
|
||||
newVer.Write(data.AsMemory());
|
||||
|
||||
Assert.True(oldVer.Buffer.AsSpan().SequenceEqual(newVer.Buffer.AsSpan()));
|
||||
|
||||
static void WriteSomeData(MessageWriter oldVer)
|
||||
{
|
||||
oldVer.WritePacked(99);
|
||||
oldVer.WritePacked(101);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user