Skip to content

Commit

Permalink
Optimizing Huffman tree
Browse files Browse the repository at this point in the history
  • Loading branch information
jtmueller committed Dec 31, 2021
1 parent 7e68c50 commit 0dd264b
Show file tree
Hide file tree
Showing 10 changed files with 88 additions and 138 deletions.
9 changes: 7 additions & 2 deletions D2SLib.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "D2SLib", "src\D2SLib.csproj
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "D2SLibTests", "test\D2SLibTests.csproj", "{F2F223AD-BCE4-47B3-BAF8-DA2D533FD05F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "D2SLib_Local.Benchmark", "benchmarks\D2SLib_Local.Benchmark\D2SLib_Local.Benchmark.csproj", "{3F72860B-D08C-45EE-9E1B-17888E103B62}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "D2SLib_Local.Benchmark", "benchmarks\D2SLib_Local.Benchmark\D2SLib_Local.Benchmark.csproj", "{3F72860B-D08C-45EE-9E1B-17888E103B62}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "D2SLib_Nuget.Benchmark", "benchmarks\D2SLib_Nuget.Benchmark\D2SLib_Nuget.Benchmark.csproj", "{7D3FEEAB-7752-4753-9EC0-EF4E7F72EB74}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "D2SLib_Nuget.Benchmark", "benchmarks\D2SLib_Nuget.Benchmark\D2SLib_Nuget.Benchmark.csproj", "{7D3FEEAB-7752-4753-9EC0-EF4E7F72EB74}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FCAB7929-60A6-4DA7-9C96-0BE97B160694}"
ProjectSection(SolutionItems) = preProject
benchmarks\Results.txt = benchmarks\Results.txt
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
10 changes: 10 additions & 0 deletions benchmarks/D2SLib_Local.Benchmark/Program.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);

// When running memory profiler, comment out the above and uncomment the following.

//var lg = new D2SLib_Benchmark.LoadGame();
//lg.GlobalSetup();

//for (int i = 0; i < 10_000; i++)
//{
// lg.SaveOnly();
//}
6 changes: 6 additions & 0 deletions src/IO/InternalBitArray.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,12 @@ public void Set(int index, bool value)
_version++;
}

public void Add(bool value)
{
int idx = Length++;
Set(idx, value);
}

/*=========================================================================
** Sets all the bit values to value.
=========================================================================*/
Expand Down
105 changes: 23 additions & 82 deletions src/Model/Data/DataFile.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,38 @@
using Microsoft.Toolkit.HighPerformance;
using Microsoft.Toolkit.HighPerformance.Buffers;
using System.Collections;
using System.Diagnostics;

namespace D2SLib.Model.Data;

public abstract class DataFile : IList<DataRow>
public abstract class DataFile
{
private DataColumn[] _columns = Array.Empty<DataColumn>();
private readonly Dictionary<string, int> _columnsLookup = new();

public IReadOnlyDictionary<string, int> ColumnNames => _columnsLookup;
public ReadOnlySpan<DataColumn> Columns => _columns;
public IList<DataRow> Rows => this;
public int Count => _columns.Length == 0 ? 0 : _columns[0].Count;

public DataRow this[int rowIndex]
{
get
{
if ((uint)rowIndex >= (uint)(_columns.Length == 0 ? 0 : _columns[0].Count))
throw new ArgumentOutOfRangeException(nameof(rowIndex));
return new DataRow(this, rowIndex);
}
}

public IEnumerable<DataRow> GetRows()
{
if (_columns.Length > 0)
{
for (int i = 0, len = _columns[0].Count; i < len; i++)
{
yield return new DataRow(this, i);
}
}
}

protected void ReadData(Stream data)
{
Expand Down Expand Up @@ -45,44 +65,6 @@ protected void ReadData(Stream data)
continue;
}

//if (_columns.Length == 0)
//{
// // infer column types, create columns

// foreach (var value in curLine.Tokenize('\t'))
// {
// if (ushort.TryParse(value, out var ushortVal))
// {
// var col = new UInt16DataColumn(colNames[colIndex]);
// col.AddValue(ushortVal);
// _columns[colIndex] = col;
// }
// else if (int.TryParse(value, out var intVal))
// {
// var col = new Int32DataColumn(colNames[colIndex]);
// col.AddValue(intVal);
// _columns[colIndex] = col;
// }
// else
// {
// var col = new StringDataColumn(colNames[colIndex]);
// col.AddValue(StringPool.Shared.GetOrAdd(value));
// _columns[colIndex] = col;
// }

// colIndex++;
// }

// while (colIndex < colNames.Count)
// {
// var col = new StringDataColumn(colNames[colIndex]);
// col.AddEmptyValue();
// _columns[colIndex++] = col;
// }

// continue;
//}

// add data to existing columns
foreach (var value in curLine.Tokenize('\t'))
{
Expand Down Expand Up @@ -230,47 +212,6 @@ protected void ReadData(Stream data)
}
return null;
}

#region IList<DataRow> implementation

int ICollection<DataRow>.Count => _columns.Length == 0 ? 0 : _columns[0].Count;

bool ICollection<DataRow>.IsReadOnly => true;

DataRow IList<DataRow>.this[int index]
{
get
{
if ((uint)index >= (uint)(_columns.Length == 0 ? 0 : _columns[0].Count))
throw new ArgumentOutOfRangeException(nameof(index));
return new DataRow(this, index);
}
set => throw new NotSupportedException();
}

private IEnumerable<DataRow> GetRows()
{
if (_columns.Length > 0)
{
for (int i = 0, len = _columns[0].Count; i < len; i++)
{
yield return new DataRow(this, i);
}
}
}

int IList<DataRow>.IndexOf(DataRow item) => throw new NotSupportedException();
void IList<DataRow>.Insert(int index, DataRow item) => throw new NotSupportedException();
void IList<DataRow>.RemoveAt(int index) => throw new NotSupportedException();
void ICollection<DataRow>.Add(DataRow item) => throw new NotSupportedException();
void ICollection<DataRow>.Clear() => throw new NotSupportedException();
bool ICollection<DataRow>.Contains(DataRow item) => throw new NotSupportedException();
void ICollection<DataRow>.CopyTo(DataRow[] array, int arrayIndex) => throw new NotImplementedException();
bool ICollection<DataRow>.Remove(DataRow item) => throw new NotImplementedException();
IEnumerator<DataRow> IEnumerable<DataRow>.GetEnumerator() => GetRows().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetRows().GetEnumerator();

#endregion
}

[DebuggerDisplay("Row {RowIndex}")]
Expand Down
4 changes: 2 additions & 2 deletions src/Model/Data/ItemStatCostData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

public sealed class ItemStatCostData : DataFile
{
public DataRow? this[int id] => GetByColumnAndValue("ID", id);
public DataRow? this[string stat] => GetByColumnAndValue("Stat", stat);
public DataRow? GetById(int id) => GetByColumnAndValue("ID", id);
public DataRow? GetByStat(string stat) => GetByColumnAndValue("Stat", stat);

public static ItemStatCostData Read(Stream data)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Model/Data/ItemsData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ private HuffmanTree InitializeHuffmanTree()
}
*/
var itemCodeTree = new HuffmanTree();
itemCodeTree.Build(new List<string>());
itemCodeTree.Build();
return itemCodeTree;
}
}
Expand Down
10 changes: 5 additions & 5 deletions src/Model/Huffman/HuffmanTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@ public class HuffmanTree
};

//todo find a way to build this like d2?
public void Build(List<string> items)
public void Build()
{
Root = new Node();
foreach (var entry in TABLE)
{
var current = Root;
foreach (char bit in entry.Value)
foreach (char bit in entry.Value.AsSpan())
{
if (bit == '1')
{
Expand All @@ -79,12 +79,12 @@ public void Build(List<string> items)
}
}

public BitArray EncodeChar(char source)
public IEnumerable<bool> EncodeChar(char source)
{
var encodedSymbol = Root?.Traverse(source, new List<bool>());
var encodedSymbol = Root?.Traverse(source, new InternalBitArray(0));
if (encodedSymbol is null)
throw new InvalidOperationException("Could not encode with an empty tree.");
return new BitArray(encodedSymbol.ToArray());
return encodedSymbol;
}

public char DecodeChar(IBitReader reader)
Expand Down
45 changes: 19 additions & 26 deletions src/Model/Huffman/Node.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace D2SLib.Model.Huffman;
using D2SLib.IO;

namespace D2SLib.Model.Huffman;

public class Node
{
Expand All @@ -7,45 +9,36 @@ public class Node
public Node? Right { get; set; }
public Node? Left { get; set; }

public List<bool>? Traverse(char symbol, List<bool> data)
internal InternalBitArray? Traverse(char symbol, InternalBitArray data)
{
// Leaf
if (Right == null && Left == null)
if (IsLeaf())
{
if (symbol.Equals(Symbol))
{
return data;
}
else
{
return null;
}
return symbol.Equals(Symbol) ? data : null;
}
else
{
List<bool>? left = null;
List<bool>? right = null;

if (Left is not null)
{
var leftPath = new List<bool>(data) { false };
left = Left.Traverse(symbol, leftPath);
data.Add(false);
var left = Left.Traverse(symbol, data);
if (left is null)
data.Length--;
else
return data;
}

if (Right is not null)
{
var rightPath = new List<bool>(data) { true };
right = Right.Traverse(symbol, rightPath);
data.Add(true);
var right = Right.Traverse(symbol, data);
if (right is null)
data.Length--;
else
return data;
}

if (left != null)
{
return left;
}
else
{
return right;
}
return null;
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/Model/Save/Attributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public static Attributes Read(IBitReader reader)
ushort id = reader.ReadUInt16(9);
while (id != 0x1ff)
{
var property = itemStatCost[id];
var property = itemStatCost.GetById(id);
int attribute = reader.ReadInt32(property?["CSvBits"].ToInt32() ?? 0);
int valShift = property?["ValShift"].ToInt32() ?? 0;
if (valShift > 0)
Expand All @@ -38,7 +38,7 @@ public void Write(IBitWriter writer)
writer.WriteUInt16(Header ?? 0x6667);
foreach (var entry in Stats)
{
var property = itemStatCost[entry.Key];
var property = itemStatCost.GetByStat(entry.Key);
writer.WriteUInt16(property?["ID"].ToUInt16() ?? 0, 9);
int attribute = entry.Value;
int valShift = property?["ValShift"].ToInt32() ?? 0;
Expand Down
31 changes: 13 additions & 18 deletions src/Model/Save/Items.cs
Original file line number Diff line number Diff line change
Expand Up @@ -301,10 +301,11 @@ protected static void WriteCompact(IBitWriter writer, Item item, uint version)
}
else if (version >= 0x61)
{
var codeTree = Core.MetaData.ItemsData.ItemCodeTree;
for (int i = 0; i < 4; i++)
{
var bits = Core.MetaData.ItemsData.ItemCodeTree.EncodeChar((char)code[i]);
foreach (bool bit in bits.Cast<bool>())
var bits = codeTree.EncodeChar((char)code[i]);
foreach (bool bit in bits)
{
writer.WriteBit(bit);
}
Expand Down Expand Up @@ -391,13 +392,12 @@ protected static void ReadComplete(IBitReader reader, Item item, uint version)
bool isStackable = row?["stackable"].ToBool() ?? false;
if (isArmor)
{
//why do i need this cast?
item.Armor = (ushort)(reader.ReadUInt16(11) + itemStatCost["armorclass"]?["Save Add"].ToUInt16() ?? 0);
item.Armor = (ushort)(reader.ReadUInt16(11) + itemStatCost.GetByStat("armorclass")?["Save Add"].ToUInt16() ?? 0);
}
if (isArmor || isWeapon)
{
var maxDurabilityStat = itemStatCost["maxdurability"];
var durabilityStat = itemStatCost["maxdurability"];
var maxDurabilityStat = itemStatCost.GetByStat("maxdurability");
var durabilityStat = itemStatCost.GetByStat("maxdurability");
item.MaxDurability = (ushort)(reader.ReadUInt16(maxDurabilityStat?["Save Bits"].ToInt32() ?? 0) + maxDurabilityStat?["Save Add"].ToUInt16() ?? 0);
if (item.MaxDurability > 0)
{
Expand Down Expand Up @@ -510,12 +510,12 @@ protected static void WriteComplete(IBitWriter writer, Item item, uint version)
bool isStackable = row?["stackable"].ToBool() ?? false;
if (isArmor)
{
writer.WriteUInt16((ushort)(item.Armor - itemStatCost["armorclass"]?["Save Add"].ToUInt16() ?? 0), 11);
writer.WriteUInt16((ushort)(item.Armor - itemStatCost.GetByStat("armorclass")?["Save Add"].ToUInt16() ?? 0), 11);
}
if (isArmor || isWeapon)
{
var maxDurabilityStat = itemStatCost["maxdurability"];
var durabilityStat = itemStatCost["maxdurability"];
var maxDurabilityStat = itemStatCost.GetByStat("maxdurability");
var durabilityStat = itemStatCost.GetByStat("maxdurability");
writer.WriteUInt16((ushort)(item.MaxDurability - maxDurabilityStat?["Save Add"].ToUInt16() ?? 0), maxDurabilityStat?["Save Bits"].ToInt32() ?? 0);
if (item.MaxDurability > 0)
{
Expand Down Expand Up @@ -623,7 +623,7 @@ public class ItemStat
public static ItemStat Read(IBitReader reader, ushort id)
{
var itemStat = new ItemStat();
var property = Core.MetaData.ItemStatCostData[id];
var property = Core.MetaData.ItemStatCostData.GetById(id);
if (property == null)
{
throw new Exception($"No ItemStatCost record found for id: {id} at bit {reader.Position - 9}");
Expand Down Expand Up @@ -737,14 +737,9 @@ public static void Write(IBitWriter writer, ItemStat stat)

public static DataRow? GetStatRow(ItemStat stat)
{
if (stat.Id != null)
{
return Core.MetaData.ItemStatCostData[(ushort)stat.Id];
}
else
{
return Core.MetaData.ItemStatCostData[stat.Stat];
}
return stat.Id is ushort statId
? Core.MetaData.ItemStatCostData.GetById(statId)
: Core.MetaData.ItemStatCostData.GetByStat(stat.Stat);
}

}

0 comments on commit 0dd264b

Please sign in to comment.