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:
Jonas KamskerandWeirdo authored and GitHub committed 2020-10-22 00:08:14 +02:00
1 parent 647672c40e
commit 7a210393ee
12 files changed
+227 -80

No files matched your search

+1
View File
@@ -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;
}
}
}
+8
View File
@@ -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" />
+61 -59
View File
@@ -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();
}
}
}
+2 -2
View File
@@ -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;
+21 -7
View File
@@ -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++)
+4 -3
View File
@@ -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);
}
}
}
}