Skip to content

Commit

Permalink
populate string table dynamically + remove likely premature optimization
Browse files Browse the repository at this point in the history
  • Loading branch information
yaakov-h committed Jul 2, 2024
1 parent e48345d commit d94d639
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 37 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Linq;

namespace ValveKeyValue.Test
{
class StringTableFromScratchTestCase
{
[Test]
public void PopulatesStringTableDuringSerialization()
{
var kv = new KVObject("root",
[
new KVObject("key", "value"),
new KVObject("child", [
new KVObject("key", 123),
]),
]);

var stringTable = new StringTable();

var serializer = KVSerializer.Create(KVSerializationFormat.KeyValues1Binary);

using var ms = new MemoryStream();
serializer.Serialize(ms, kv, new KVSerializerOptions { StringTable = stringTable });

var strings = stringTable.ToArray();
Assert.That(strings, Is.EqualTo(new[] { "root", "key", "child" }));
}
}
}
15 changes: 5 additions & 10 deletions ValveKeyValue/ValveKeyValue/KVSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,18 +125,13 @@ IVisitationListener MakeSerializer(Stream stream, KVSerializerOptions options)
Require.NotNull(stream, nameof(stream));
Require.NotNull(options, nameof(options));

switch (format)
return format switch
{
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.");
KVSerializationFormat.KeyValues1Text => new KV1TextSerializer(stream, options),
KVSerializationFormat.KeyValues1Binary => new KV1BinarySerializer(stream, options.StringTable),
_ => throw new ArgumentOutOfRangeException(nameof(format), format, "Invalid serialization format."),
};
;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ void WriteKeyForNextValue(string name)
{
if (stringTable is not null)
{
writer.Write(stringTable[name]);
writer.Write(stringTable.GetOrAdd(name));
}
else
{
Expand Down
74 changes: 48 additions & 26 deletions ValveKeyValue/ValveKeyValue/StringTable.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,42 @@
namespace ValveKeyValue
using System.Linq;

namespace ValveKeyValue
{
public sealed class StringTable
{
public StringTable(Memory<string> values)
public StringTable()
: this(new List<string>(), writable: true)
{
}

public StringTable(int capacity)
: this(new List<string>(capacity), writable: true)
{
}

public StringTable(IList<string> values)
: this(values, writable: !values.IsReadOnly)
{
}

StringTable(IList<string> values, bool writable)
{
this.lookup = values;
this.writable = writable;

reverse = new Dictionary<string, int>(capacity: lookup.Count, StringComparer.Ordinal);

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

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

readonly IList<string> lookup;
readonly bool writable;
readonly Dictionary<string, int> reverse;

public string this[int index]
{
Expand All @@ -19,43 +47,37 @@ public string this[int index]
throw new ArgumentOutOfRangeException(nameof(index), "Index must be non-negative.");
}

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

return lookup.Span[index];
return lookup[index];
}
}

public int this[string value]
public void Add(string value)
{
get
if (!writable)
{
if (reverse is null)
{
throw new InvalidOperationException("String table has not been prepared for serialization.");
}

return reverse[value];
throw new InvalidOperationException("Unable to add to read-only string table.");
}

lookup.Add(value);
reverse.TryAdd(value, lookup.Count - 1);
}

public void PrepareForSerialization()
public int GetOrAdd(string value)
{
if (reverse is not null)
if (!reverse.TryGetValue(value, out var index))
{
return;
Add(value);
index = lookup.Count - 1;
}

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;
}
return index;
}

public string[] ToArray() => lookup.ToArray();
}
}

0 comments on commit d94d639

Please sign in to comment.