diff --git a/ValveKeyValue/ValveKeyValue.Test/Test Data/apisurface.txt b/ValveKeyValue/ValveKeyValue.Test/Test Data/apisurface.txt
index b05adec7..a5367b4f 100644
--- a/ValveKeyValue/ValveKeyValue.Test/Test Data/apisurface.txt
+++ b/ValveKeyValue/ValveKeyValue.Test/Test Data/apisurface.txt
@@ -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();
}
@@ -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();
+}
+
diff --git a/ValveKeyValue/ValveKeyValue/Deserialization/KeyValues1/KV1BinaryReader.cs b/ValveKeyValue/ValveKeyValue/Deserialization/KeyValues1/KV1BinaryReader.cs
index 7c4be940..d987ca9d 100644
--- a/ValveKeyValue/ValveKeyValue/Deserialization/KeyValues1/KV1BinaryReader.cs
+++ b/ValveKeyValue/ValveKeyValue/Deserialization/KeyValues1/KV1BinaryReader.cs
@@ -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));
@@ -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;
@@ -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)
diff --git a/ValveKeyValue/ValveKeyValue/KVSerializer.cs b/ValveKeyValue/ValveKeyValue/KVSerializer.cs
index 965765d8..3a1998b7 100644
--- a/ValveKeyValue/ValveKeyValue/KVSerializer.cs
+++ b/ValveKeyValue/ValveKeyValue/KVSerializer.cs
@@ -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."),
};
}
@@ -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.");
};
}
}
diff --git a/ValveKeyValue/ValveKeyValue/KVSerializerOptions.cs b/ValveKeyValue/ValveKeyValue/KVSerializerOptions.cs
index d3b3c8d6..f58367e5 100644
--- a/ValveKeyValue/ValveKeyValue/KVSerializerOptions.cs
+++ b/ValveKeyValue/ValveKeyValue/KVSerializerOptions.cs
@@ -27,6 +27,12 @@ public sealed class KVSerializerOptions
///
public IIncludedFileLoader FileLoader { get; set; }
+
+ ///
+ /// Gets or sets the string table used for smaller binary serialization.
+ ///
+ public StringTable StringTable { get; set; }
+
///
/// Gets the default options (used when none are specified).
///
diff --git a/ValveKeyValue/ValveKeyValue/Serialization/KeyValues1/KV1BinarySerializer.cs b/ValveKeyValue/ValveKeyValue/Serialization/KeyValues1/KV1BinarySerializer.cs
index 6c2dc155..0b2b6307 100644
--- a/ValveKeyValue/ValveKeyValue/Serialization/KeyValues1/KV1BinarySerializer.cs
+++ b/ValveKeyValue/ValveKeyValue/Serialization/KeyValues1/KV1BinarySerializer.cs
@@ -1,3 +1,4 @@
+using System.Linq;
using System.Text;
using ValveKeyValue.Abstraction;
using ValveKeyValue.KeyValues1;
@@ -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()
@@ -25,7 +28,7 @@ public void OnObjectStart(string name)
{
objectDepth++;
Write(KV1BinaryNodeType.ChildObject);
- WriteNullTerminatedBytes(Encoding.UTF8.GetBytes(name));
+ WriteKeyForNextValue(name);
}
public void OnObjectEnd()
@@ -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)
{
@@ -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
diff --git a/ValveKeyValue/ValveKeyValue/StringTable.cs b/ValveKeyValue/ValveKeyValue/StringTable.cs
new file mode 100644
index 00000000..0c030056
--- /dev/null
+++ b/ValveKeyValue/ValveKeyValue/StringTable.cs
@@ -0,0 +1,58 @@
+namespace ValveKeyValue
+{
+ public sealed class StringTable
+ {
+ public StringTable(Memory values)
+ {
+ this.lookup = values;
+ }
+
+ readonly Memory lookup;
+ Dictionary 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(capacity: lookup.Length, StringComparer.Ordinal);
+ var span = lookup.Span;
+
+ for (var i = 0; i < span.Length; i++)
+ {
+ var value = span[i];
+ reverse[value] = i;
+ }
+ }
+ }
+}