Skip to content

Commit 77cfffd

Browse files
committed
[size-opt] prefix tree double check; zip level 9
1 parent d71485b commit 77cfffd

4 files changed

Lines changed: 128 additions & 17 deletions

File tree

FreeMote/Consts.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public static class Consts
6161

6262
/// <summary>
6363
/// (bool)
64-
/// <para>Fast: 0x9C BestCompression: 0xDA NoCompression/Low: 0x01</para>
64+
/// <para>Fast: 0x9C; BestCompression: 0xDA; NoCompression/Low: 0x01</para>
6565
/// </summary>
6666
public const string Context_PsbZlibFastCompress = "PsbZlibFastCompress";
6767

FreeMote/FreeMote.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream">
2828
<Version>2.3.2</Version>
2929
</PackageReference>
30+
<PackageReference Include="SharpZipLib" Version="1.4.2" />
3031
<PackageReference Include="System.Memory">
3132
<Version>4.5.5</Version>
3233
</PackageReference>

FreeMote/PrefixTree.cs

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using System.Diagnostics;
44
using System.Linq;
@@ -124,11 +124,54 @@ internal void InsertTree(string value)
124124
public static PrefixTree Build(List<string> namesList, bool optimize, out List<uint> names, out List<uint> tree, out List<uint> offsets)
125125
{
126126
//namesList.Sort((s1, s2) => s1.Length - s2.Length);
127-
var trie = new PrefixTree(namesList, optimize);
128-
names = trie._names;
129-
tree = trie._tree;
130-
offsets = trie._offsets;
131-
return trie;
127+
if (!optimize)
128+
{
129+
var trie = new PrefixTree(namesList, false);
130+
names = trie._names;
131+
tree = trie._tree;
132+
offsets = trie._offsets;
133+
return trie;
134+
}
135+
136+
// Some datasets are much denser with the legacy allocator.
137+
// Keep OptimizeMode stable by choosing whichever layout serializes smaller.
138+
var optimizedTrie = new PrefixTree(namesList, true);
139+
var legacyTrie = new PrefixTree(namesList, false);
140+
var optimizedSize = EstimateSerializedSize(optimizedTrie._names, optimizedTrie._tree, optimizedTrie._offsets);
141+
var legacySize = EstimateSerializedSize(legacyTrie._names, legacyTrie._tree, legacyTrie._offsets);
142+
143+
var result = optimizedSize <= legacySize ? optimizedTrie : legacyTrie;
144+
names = result._names;
145+
tree = result._tree;
146+
offsets = result._offsets;
147+
return result;
148+
}
149+
150+
private static int EstimateSerializedSize(List<uint> names, List<uint> tree, List<uint> offsets)
151+
=> EstimateArraySize(offsets) + EstimateArraySize(tree) + EstimateArraySize(names);
152+
153+
private static int EstimateArraySize(List<uint> values)
154+
{
155+
var countSize = GetUIntByteSize((uint)values.Count);
156+
uint maxValue = 0;
157+
for (var i = 0; i < values.Count; i++)
158+
{
159+
if (values[i] > maxValue)
160+
{
161+
maxValue = values[i];
162+
}
163+
}
164+
165+
var entrySize = values.Count == 0 ? 1 : GetUIntByteSize(maxValue);
166+
return 1 + countSize + 1 + values.Count * entrySize;
167+
}
168+
169+
private static int GetUIntByteSize(uint value)
170+
{
171+
if (value <= 0xFF) return 1;
172+
if (value <= 0xFFFF) return 2;
173+
if (value <= 0xFFFFFF) return 3;
174+
return 4;
132175
}
133176

134177
private TrieNode GetNode(TrieNode node, char c, bool isEnd = false)

FreeMote/ZlibCompress.cs

Lines changed: 77 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
using System.IO;
1+
using System.IO;
22
using System.IO.Compression;
3+
using ICSharpCode.SharpZipLib.Zip.Compression;
4+
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
35

46
namespace FreeMote
57
{
@@ -53,11 +55,23 @@ public static byte[] Compress(Stream input, bool fast = false)
5355
using var ms = new MemoryStream();
5456
ms.WriteByte(0x78);
5557
ms.WriteByte((byte) (fast ? 0x9C : 0xDA));
56-
using (DeflateStream deflateStream =
57-
new DeflateStream(ms, Consts.ForceCompressionLevel != null ? Consts.ForceCompressionLevel.Value :
58-
fast ? CompressionLevel.Fastest : CompressionLevel.Optimal))
58+
if (UseSharpZipLib(fast))
5959
{
60-
input.CopyTo(deflateStream);
60+
var deflaterLevel = GetDeflaterLevel(fast);
61+
var deflater = new Deflater(deflaterLevel, true);
62+
using (var deflateStream = new DeflaterOutputStream(ms, deflater))
63+
{
64+
deflateStream.IsStreamOwner = false;
65+
input.CopyTo(deflateStream);
66+
deflateStream.Finish();
67+
}
68+
}
69+
else
70+
{
71+
using (DeflateStream deflateStream = new DeflateStream(ms, GetSystemCompressionLevel(fast), true))
72+
{
73+
input.CopyTo(deflateStream);
74+
}
6175
}
6276

6377
//input.Dispose();
@@ -81,17 +95,70 @@ public static MemoryStream CompressToStream(Stream input, bool fast = false)
8195
MemoryStream ms = new MemoryStream();
8296
ms.WriteByte(0x78);
8397
ms.WriteByte((byte) (fast ? 0x9C : 0xDA));
84-
using (DeflateStream deflateStream =
85-
new DeflateStream(ms,
86-
Consts.ForceCompressionLevel != null ? Consts.ForceCompressionLevel.Value :
87-
fast ? CompressionLevel.Fastest : CompressionLevel.Optimal, true))
98+
if (UseSharpZipLib(fast))
99+
{
100+
var deflaterLevel = GetDeflaterLevel(fast);
101+
var deflater = new Deflater(deflaterLevel, true);
102+
using (var deflateStream = new DeflaterOutputStream(ms, deflater))
103+
{
104+
deflateStream.IsStreamOwner = false;
105+
input.CopyTo(deflateStream);
106+
deflateStream.Finish();
107+
}
108+
}
109+
else
88110
{
89-
input.CopyTo(deflateStream);
111+
using (DeflateStream deflateStream = new DeflateStream(ms, GetSystemCompressionLevel(fast), true))
112+
{
113+
input.CopyTo(deflateStream);
114+
}
90115
}
91116

92117
//input.Dispose(); //DO NOT dispose
93118
ms.Position = 0;
94119
return ms;
95120
}
121+
122+
private static int GetDeflaterLevel(bool fast)
123+
{
124+
if (Consts.ForceCompressionLevel != null)
125+
{
126+
switch (Consts.ForceCompressionLevel.Value)
127+
{
128+
case CompressionLevel.NoCompression:
129+
return Deflater.NO_COMPRESSION;
130+
case CompressionLevel.Fastest:
131+
return Deflater.BEST_SPEED;
132+
case CompressionLevel.Optimal:
133+
return Deflater.BEST_COMPRESSION;
134+
default:
135+
return fast ? Deflater.BEST_SPEED : Deflater.BEST_COMPRESSION;
136+
}
137+
}
138+
139+
// MDF body in real games usually uses max deflate level (0xDA).
140+
return fast ? Deflater.BEST_SPEED : Deflater.BEST_COMPRESSION;
141+
}
142+
143+
private static CompressionLevel GetSystemCompressionLevel(bool fast)
144+
{
145+
if (Consts.ForceCompressionLevel != null)
146+
{
147+
return Consts.ForceCompressionLevel.Value;
148+
}
149+
150+
return fast ? CompressionLevel.Fastest : CompressionLevel.Optimal;
151+
}
152+
153+
private static bool UseSharpZipLib(bool fast)
154+
{
155+
if (fast)
156+
{
157+
return false;
158+
}
159+
160+
// Only use SharpZipLib in optimize mode, so normal mode keeps .NET Deflate speed.
161+
return Consts.OptimizeMode;
162+
}
96163
}
97164
}

0 commit comments

Comments
 (0)