A game about forced loneliness, made by TACStudios
at master 290 lines 14 kB view raw
1using System; 2using System.Diagnostics; 3using Unity.Burst; 4using Unity.Mathematics; 5 6namespace Unity.Collections 7{ 8 /// <summary> 9 /// A type that uses Huffman encoding to encode values in a lossless manner. 10 /// </summary> 11 /// <remarks> 12 /// This type puts values into a manageable number of power-of-two-sized buckets. 13 /// It codes the bucket index with Huffman, and uses several raw bits that correspond 14 /// to the size of the bucket to code the position in the bucket. 15 /// 16 /// For example, if you want to send a 32-bit integer over the network, it's 17 /// impractical to create a Huffman tree that encompasses every value the integer 18 /// can take because it requires a tree with 2^32 leaves. This type manages that situation. 19 /// 20 /// The buckets are small, around 0, and become progressively larger as the data moves away from zero. 21 /// Because most data is deltas against predictions, most values are small and most of the redundancy 22 /// is in the error's size and not in the values of that size we end up hitting. 23 /// 24 /// The context is as a sub-model that has its own statistics and uses its own Huffman tree. 25 /// When using the context to read and write a specific value, the context must always be the same. 26 /// The benefit of using multiple contexts is that it allows you to separate the statistics of things that have 27 /// different expected distributions, which leads to more precise statistics, which again yields better compression. 28 /// More contexts does, however, result in a marginal cost of a slightly larger model. 29 /// </remarks> 30 [GenerateTestsForBurstCompatibility] 31 public unsafe struct StreamCompressionModel 32 { 33 internal static readonly byte[] k_BucketSizes = 34 { 35 0, 0, 1, 2, 3, 4, 6, 8, 10, 12, 15, 18, 21, 24, 27, 32 36 }; 37 38 internal static readonly uint[] k_BucketOffsets = 39 { 40 0, 1, 2, 4, 8, 16, 32, 96, 352, 1376, 5472, 38240, 300384, 2397536, 19174752, 153392480 41 }; 42 internal static readonly int[] k_FirstBucketCandidate = 43 { 44 // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 45 15, 15, 15, 15, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, 10, 10, 10, 9, 9, 8, 8, 7, 7, 6, 5, 4, 3, 2, 1, 1, 0 46 }; 47 internal static readonly byte[] k_DefaultModelData = { 16, // 16 symbols 48 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 6, 6, 6, 49 0, 0 }; // no contexts 50 internal const int k_AlphabetSize = 16; 51 internal const int k_MaxHuffmanSymbolLength = 6; 52 internal const int k_MaxContexts = 1; 53 byte m_Initialized; 54 55 static class SharedStaticCompressionModel 56 { 57 internal static readonly SharedStatic<StreamCompressionModel> Default = SharedStatic<StreamCompressionModel>.GetOrCreate<StreamCompressionModel>(); 58 } 59 60 /// <summary> 61 /// A shared singleton instance of <see cref="StreamCompressionModel"/>, this instance is initialized using 62 /// hardcoded bucket parameters and model. 63 /// </summary> 64 public static StreamCompressionModel Default { 65 get 66 { 67 if (SharedStaticCompressionModel.Default.Data.m_Initialized == 1) 68 { 69 return SharedStaticCompressionModel.Default.Data; 70 } 71 Initialize(); 72 SharedStaticCompressionModel.Default.Data.m_Initialized = 1; 73 74 return SharedStaticCompressionModel.Default.Data; 75 } 76 } 77 78 static void Initialize() 79 { 80 for (int i = 0; i < k_AlphabetSize; ++i) 81 { 82 SharedStaticCompressionModel.Default.Data.bucketSizes[i] = k_BucketSizes[i]; 83 SharedStaticCompressionModel.Default.Data.bucketOffsets[i] = k_BucketOffsets[i]; 84 } 85 var modelData = new NativeArray<byte>(k_DefaultModelData.Length, Allocator.Temp); 86 for (var index = 0; index < k_DefaultModelData.Length; index++) 87 { 88 modelData[index] = k_DefaultModelData[index]; 89 } 90 91 //int numContexts = NetworkConfig.maxContexts; 92 int numContexts = 1; 93 var symbolLengths = new NativeArray<byte>(numContexts * k_AlphabetSize, Allocator.Temp); 94 95 int readOffset = 0; 96 { 97 // default model 98 int defaultModelAlphabetSize = modelData[readOffset++]; 99 CheckAlphabetSize(defaultModelAlphabetSize); 100 101 for (int i = 0; i < k_AlphabetSize; i++) 102 { 103 byte length = modelData[readOffset++]; 104 for (int context = 0; context < numContexts; context++) 105 { 106 symbolLengths[numContexts * context + i] = length; 107 } 108 } 109 110 // other models 111 int numModels = modelData[readOffset] | (modelData[readOffset + 1] << 8); 112 readOffset += 2; 113 for (int model = 0; model < numModels; model++) 114 { 115 int context = modelData[readOffset] | (modelData[readOffset + 1] << 8); 116 readOffset += 2; 117 118 int modelAlphabetSize = modelData[readOffset++]; 119 CheckAlphabetSize(modelAlphabetSize); 120 for (int i = 0; i < k_AlphabetSize; i++) 121 { 122 byte length = modelData[readOffset++]; 123 symbolLengths[numContexts * context + i] = length; 124 } 125 } 126 } 127 128 // generate tables 129 var tmpSymbolLengths = new NativeArray<byte>(k_AlphabetSize, Allocator.Temp); 130 var tmpSymbolDecodeTable = new NativeArray<ushort>(1 << k_MaxHuffmanSymbolLength, Allocator.Temp); 131 var symbolCodes = new NativeArray<byte>(k_AlphabetSize, Allocator.Temp); 132 133 for (int context = 0; context < numContexts; context++) 134 { 135 for (int i = 0; i < k_AlphabetSize; i++) 136 tmpSymbolLengths[i] = symbolLengths[numContexts * context + i]; 137 138 GenerateHuffmanCodes(symbolCodes, 0, tmpSymbolLengths, 0, k_AlphabetSize, k_MaxHuffmanSymbolLength); 139 GenerateHuffmanDecodeTable(tmpSymbolDecodeTable, 0, tmpSymbolLengths, symbolCodes, k_AlphabetSize, k_MaxHuffmanSymbolLength); 140 for (int i = 0; i < k_AlphabetSize; i++) 141 { 142 SharedStaticCompressionModel.Default.Data.encodeTable[context * k_AlphabetSize + i] = (ushort)((symbolCodes[i] << 8) | symbolLengths[numContexts * context + i]); 143 } 144 for (int i = 0; i < (1 << k_MaxHuffmanSymbolLength); i++) 145 { 146 SharedStaticCompressionModel.Default.Data.decodeTable[context * (1 << k_MaxHuffmanSymbolLength) + i] = tmpSymbolDecodeTable[i]; 147 } 148 } 149 } 150 151 static void GenerateHuffmanCodes(NativeArray<byte> symbolCodes, int symbolCodesOffset, NativeArray<byte> symbolLengths, int symbolLengthsOffset, int alphabetSize, int maxCodeLength) 152 { 153 CheckAlphabetAndMaxCodeLength(alphabetSize, maxCodeLength); 154 155 var lengthCounts = new NativeArray<byte>(maxCodeLength + 1, Allocator.Temp); 156 var symbolList = new NativeArray<byte>((maxCodeLength + 1) * alphabetSize, Allocator.Temp); 157 158 //byte[] symbol_list[(MAX_HUFFMAN_CODE_LENGTH + 1u) * MAX_NUM_HUFFMAN_SYMBOLS]; 159 for (int symbol = 0; symbol < alphabetSize; symbol++) 160 { 161 int symbolLength = symbolLengths[symbol + symbolLengthsOffset]; 162 CheckExceedMaxCodeLength(symbolLength, maxCodeLength); 163 symbolList[(maxCodeLength + 1) * symbolLength + lengthCounts[symbolLength]++] = (byte)symbol; 164 } 165 166 uint nextCodeWord = 0; 167 for (int length = 1; length <= maxCodeLength; length++) 168 { 169 int length_count = lengthCounts[length]; 170 for (int i = 0; i < length_count; i++) 171 { 172 int symbol = symbolList[(maxCodeLength + 1) * length + i]; 173 CheckSymbolLength(symbolLengths, symbolLengthsOffset, symbol, length); 174 symbolCodes[symbol + symbolCodesOffset] = (byte)ReverseBits(nextCodeWord++, length); 175 } 176 nextCodeWord <<= 1; 177 } 178 } 179 180 static uint ReverseBits(uint value, int num_bits) 181 { 182 value = ((value & 0x55555555u) << 1) | ((value & 0xAAAAAAAAu) >> 1); 183 value = ((value & 0x33333333u) << 2) | ((value & 0xCCCCCCCCu) >> 2); 184 value = ((value & 0x0F0F0F0Fu) << 4) | ((value & 0xF0F0F0F0u) >> 4); 185 value = ((value & 0x00FF00FFu) << 8) | ((value & 0xFF00FF00u) >> 8); 186 value = (value << 16) | (value >> 16); 187 return value >> (32 - num_bits); 188 } 189 190 // decode table entries: (symbol << 8) | length 191 static void GenerateHuffmanDecodeTable(NativeArray<ushort> decodeTable, int decodeTableOffset, NativeArray<byte> symbolLengths, NativeArray<byte> symbolCodes, int alphabetSize, int maxCodeLength) 192 { 193 CheckAlphabetAndMaxCodeLength(alphabetSize, maxCodeLength); 194 195 uint maxCode = 1u << maxCodeLength; 196 for (int symbol = 0; symbol < alphabetSize; symbol++) 197 { 198 int length = symbolLengths[symbol]; 199 CheckExceedMaxCodeLength(length, maxCodeLength); 200 if (length > 0) 201 { 202 uint code = symbolCodes[symbol]; 203 uint step = 1u << length; 204 do 205 { 206 decodeTable[(int)(decodeTableOffset + code)] = (ushort)(symbol << 8 | length); 207 code += step; 208 } 209 while (code < maxCode); 210 } 211 } 212 } 213 214 /// <summary> 215 /// Bucket n starts at bucketOffsets[n] and ends at bucketOffsets[n] + (1 &lt;&lt; bucketSizes[n]). 216 /// (code &lt;&lt; 8) | length 217 /// </summary> 218 internal fixed ushort encodeTable[k_MaxContexts * k_AlphabetSize]; 219 /// <summary> 220 /// Bucket n starts at bucketOffsets[n] and ends at bucketOffsets[n] + (1 &lt;&lt; bucketSizes[n]). 221 /// (symbol &lt;&lt; 8) | length 222 /// </summary> 223 internal fixed ushort decodeTable[k_MaxContexts * (1 << k_MaxHuffmanSymbolLength)]; 224 /// <summary> 225 /// Specifies the sizes of the buckets in bits, so a bucket of n bits has 2^n values. 226 /// </summary> 227 internal fixed byte bucketSizes[k_AlphabetSize]; 228 /// <summary> 229 /// Specifies the starting positions of the bucket. 230 /// </summary> 231 internal fixed uint bucketOffsets[k_AlphabetSize]; 232 233 /// <summary> 234 /// Calculates the bucket index into the <see cref="encodeTable"/> where the specified value should be written. 235 /// </summary> 236 /// <param name="value">A 4-byte unsigned integer value to find a bucket for.</param> 237 /// <returns>The bucket index where to put the value.</returns> 238 readonly public int CalculateBucket(uint value) 239 { 240 int bucketIndex = k_FirstBucketCandidate[math.lzcnt(value)]; 241 if (bucketIndex + 1 < k_AlphabetSize && value >= bucketOffsets[bucketIndex + 1]) 242 bucketIndex++; 243 244 return bucketIndex; 245 } 246 247 /// <summary> 248 /// The compressed size in bits of the given unsigned integer value 249 /// </summary> 250 /// <param name="value">the unsigned int value you want to compress</param> 251 /// <returns></returns> 252 public readonly int GetCompressedSizeInBits(uint value) 253 { 254 int bucket = CalculateBucket(value); 255 int bits = bucketSizes[bucket]; 256 ushort encodeEntry = encodeTable[bucket]; 257 return (encodeEntry & 0xff) + bits; 258 } 259 260 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] 261 static void CheckAlphabetSize(int alphabetSize) 262 { 263 if (alphabetSize != k_AlphabetSize) 264 { 265 throw new InvalidOperationException("The alphabet size of compression models must be " + k_AlphabetSize); 266 } 267 } 268 269 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] 270 static void CheckSymbolLength(NativeArray<byte> symbolLengths, int symbolLengthsOffset, int symbol, int length) 271 { 272 if (symbolLengths[symbol + symbolLengthsOffset] != length) 273 throw new InvalidOperationException("Incorrect symbol length"); 274 } 275 276 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] 277 static void CheckAlphabetAndMaxCodeLength(int alphabetSize, int maxCodeLength) 278 { 279 if (alphabetSize > 256 || maxCodeLength > 8) 280 throw new InvalidOperationException("Can only generate huffman codes up to alphabet size 256 and maximum code length 8"); 281 } 282 283 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] 284 static void CheckExceedMaxCodeLength(int length, int maxCodeLength) 285 { 286 if (length > maxCodeLength) 287 throw new InvalidOperationException("Maximum code length exceeded"); 288 } 289 } 290}