Skip to content

Commit

Permalink
Implement binary string table support
Browse files Browse the repository at this point in the history
  • Loading branch information
yaakov-h committed Jul 2, 2024
1 parent 119dd3f commit e0b92f4
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 10 deletions.
15 changes: 15 additions & 0 deletions ValveKeyValue/ValveKeyValue.Test/Test Data/apisurface.txt
Original file line number Diff line number Diff line change
Expand Up @@ -207,12 +207,14 @@ public sealed class ValveKeyValue.KVSerializerOptions
public bool get_EnableValveNullByteBugBehavior();
public ValveKeyValue.IIncludedFileLoader get_FileLoader();
public bool get_HasEscapeSequences();
public ValveKeyValue.StringTable get_StringTable();
public int GetHashCode();
public Type GetType();
protected object MemberwiseClone();
public void set_EnableValveNullByteBugBehavior(bool value);
public void set_FileLoader(ValveKeyValue.IIncludedFileLoader value);
public void set_HasEscapeSequences(bool value);
public void set_StringTable(ValveKeyValue.StringTable value);
public string ToString();
}

Expand Down Expand Up @@ -295,3 +297,16 @@ public sealed enum ValveKeyValue.KVValueType
public string ToString(string format, IFormatProvider provider);
}

public sealed class ValveKeyValue.StringTable
{
public bool Equals(object obj);
protected void Finalize();
public string get_Item(int index);
public int GetHashCode();
public Type GetType();
public int IndexOf(string value);
protected object MemberwiseClone();
public void PrepareForSerialization();
public string ToString();
}

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class KV1BinaryReader : IVisitingReader
{
public const int BinaryMagicHeader = 0x564B4256; // VBKV

public KV1BinaryReader(Stream stream, IVisitationListener listener)
public KV1BinaryReader(Stream stream, IVisitationListener listener, StringTable stringTable)
{
Require.NotNull(stream, nameof(stream));
Require.NotNull(listener, nameof(listener));
Expand All @@ -20,12 +20,14 @@ public KV1BinaryReader(Stream stream, IVisitationListener listener)

this.stream = stream;
this.listener = listener;
this.stringTable = stringTable;
reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true);
}

readonly Stream stream;
readonly BinaryReader reader;
readonly IVisitationListener listener;
readonly StringTable stringTable;
bool disposed;
KV1BinaryNodeType endMarker = KV1BinaryNodeType.End;

Expand Down Expand Up @@ -74,9 +76,20 @@ void ReadObjectCore()
}
}

string ReadKeyForNextValue()
{
if (stringTable is not null)
{
var index = reader.ReadInt32();
return stringTable[index];
}

return Encoding.UTF8.GetString(ReadNullTerminatedBytes());
}

void ReadValue(KV1BinaryNodeType type)
{
var name = Encoding.UTF8.GetString(ReadNullTerminatedBytes());
var name = ReadKeyForNextValue();
KVValue value;

switch (type)
Expand Down
16 changes: 11 additions & 5 deletions ValveKeyValue/ValveKeyValue/KVSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ IVisitingReader MakeReader(Stream stream, IParsingVisitationListener listener, K
return format switch
{
KVSerializationFormat.KeyValues1Text => new KV1TextReader(new StreamReader(stream), listener, options),
KVSerializationFormat.KeyValues1Binary => new KV1BinaryReader(stream, listener),
KVSerializationFormat.KeyValues1Binary => new KV1BinaryReader(stream, listener, options.StringTable),
_ => throw new ArgumentOutOfRangeException(nameof(format), format, "Invalid serialization format."),
};
}
Expand All @@ -125,11 +125,17 @@ IVisitationListener MakeSerializer(Stream stream, KVSerializerOptions options)
Require.NotNull(stream, nameof(stream));
Require.NotNull(options, nameof(options));

return format switch
switch (format)
{
KVSerializationFormat.KeyValues1Text => new KV1TextSerializer(stream, options),
KVSerializationFormat.KeyValues1Binary => new KV1BinarySerializer(stream),
_ => throw new ArgumentOutOfRangeException(nameof(format), format, "Invalid serialization format."),
case KVSerializationFormat.KeyValues1Text:
return new KV1TextSerializer(stream, options);

case KVSerializationFormat.KeyValues1Binary:
options.StringTable?.PrepareForSerialization();
return new KV1BinarySerializer(stream, options.StringTable);

default:
throw new ArgumentOutOfRangeException(nameof(format), format, "Invalid serialization format.");
};
}
}
Expand Down
6 changes: 6 additions & 0 deletions ValveKeyValue/ValveKeyValue/KVSerializerOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ public sealed class KVSerializerOptions
/// </summary>
public IIncludedFileLoader FileLoader { get; set; }


/// <summary>
/// Gets or sets the string table used for smaller binary serialization.
/// </summary>
public StringTable StringTable { get; set; }

/// <summary>
/// Gets the default options (used when none are specified).
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Linq;
using System.Text;
using ValveKeyValue.Abstraction;
using ValveKeyValue.KeyValues1;
Expand All @@ -6,14 +7,16 @@ namespace ValveKeyValue.Serialization.KeyValues1
{
sealed class KV1BinarySerializer : IVisitationListener, IDisposable
{
public KV1BinarySerializer(Stream stream)
public KV1BinarySerializer(Stream stream, StringTable stringTable)
{
Require.NotNull(stream, nameof(stream));

writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true);
this.stringTable = stringTable;
}

readonly BinaryWriter writer;
readonly StringTable stringTable;
int objectDepth;

public void Dispose()
Expand All @@ -25,7 +28,7 @@ public void OnObjectStart(string name)
{
objectDepth++;
Write(KV1BinaryNodeType.ChildObject);
WriteNullTerminatedBytes(Encoding.UTF8.GetBytes(name));
WriteKeyForNextValue(name);
}

public void OnObjectEnd()
Expand All @@ -42,7 +45,7 @@ public void OnObjectEnd()
public void OnKeyValuePair(string name, KVValue value)
{
Write(GetNodeType(value.ValueType));
WriteNullTerminatedBytes(Encoding.UTF8.GetBytes(name));
WriteKeyForNextValue(name);

switch (value.ValueType)
{
Expand Down Expand Up @@ -83,6 +86,18 @@ void WriteNullTerminatedBytes(byte[] value)
writer.Write((byte)0);
}

void WriteKeyForNextValue(string name)
{
if (stringTable is not null)
{
writer.Write(stringTable.IndexOf(name));
}
else
{
WriteNullTerminatedBytes(Encoding.UTF8.GetBytes(name));
}
}

static KV1BinaryNodeType GetNodeType(KVValueType type)
{
return type switch
Expand Down
58 changes: 58 additions & 0 deletions ValveKeyValue/ValveKeyValue/StringTable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
namespace ValveKeyValue
{
public sealed class StringTable
{
public StringTable(Memory<string> values)
{
this.lookup = values;
}

readonly Memory<string> lookup;
Dictionary<string, int> reverse;

public string this[int index]
{
get
{
if (index < 0)
{
throw new ArgumentOutOfRangeException(nameof(index), "Index must be non-negative.");
}

if (index >= lookup.Length)
{
throw new ArgumentOutOfRangeException(nameof(index), index, "Index must be less than the number of strings in the table.");
}

return lookup.Span[index];
}
}

public int IndexOf(string value)
{
if (reverse is null)
{
throw new InvalidOperationException("String table has not been prepared for serialization.");
}

return reverse[value];
}

public void PrepareForSerialization()
{
if (reverse is not null)
{
return;
}

reverse = new Dictionary<string, int>(capacity: lookup.Length, StringComparer.Ordinal);
var span = lookup.Span;

for (var i = 0; i < span.Length; i++)
{
var value = span[i];
reverse[value] = i;
}
}
}
}

0 comments on commit e0b92f4

Please sign in to comment.