├── .github
└── FUNDING.yml
├── JamesFrowen.BitPacking
├── Source
│ ├── AssemblyInfo.cs
│ ├── JamesFrowen.BitPacking.asmdef.meta
│ ├── BitMask.cs.meta
│ ├── AssemblyInfo.cs.meta
│ ├── NetworkReader.cs.meta
│ ├── NetworkWriter.cs.meta
│ ├── Packers
│ │ ├── ZigZag.cs.meta
│ │ ├── BitHelper.cs.meta
│ │ ├── FloatPacker.cs.meta
│ │ ├── Vector2Packer.cs.meta
│ │ ├── Vector3Packer.cs.meta
│ │ ├── QuaternionPacker.cs.meta
│ │ ├── VarFloatPacker.cs.meta
│ │ ├── VarIntBlocksPacker.cs.meta
│ │ ├── VarVector3Packer.cs.meta
│ │ ├── VariableIntPacker.cs.meta
│ │ ├── ZigZag.cs
│ │ ├── VarFloatPacker.cs
│ │ ├── VarVector3Packer.cs
│ │ ├── Vector2Packer.cs
│ │ ├── VarIntBlocksPacker.cs
│ │ ├── Vector3Packer.cs
│ │ ├── BitHelper.cs
│ │ ├── VariableIntPacker.cs
│ │ ├── FloatPacker.cs
│ │ └── QuaternionPacker.cs
│ ├── Logging
│ │ ├── LogHelper.cs.meta
│ │ └── LogHelper.cs
│ ├── JamesFrowen.BitPacking.asmdef
│ ├── BitMask.cs
│ ├── NetworkReader.cs
│ └── NetworkWriter.cs
└── JamesFrowen.BitPacking.csproj
├── JamesFrowen.BitPacking.Tests
├── Tests
│ ├── Packers.meta
│ ├── Mirage.Tests.BitPacking.asmdef.meta
│ ├── TestRandom.cs.meta
│ ├── BitPackingTests.cs.meta
│ ├── BitMaskHelperTests.cs.meta
│ ├── BitPackingProperties.cs.meta
│ ├── Packers
│ │ ├── BitHelperTest.cs.meta
│ │ ├── FloatPackerTests.cs.meta
│ │ ├── PackerTestBase.cs.meta
│ │ ├── UintPackerTests.cs.meta
│ │ ├── Vector2PackerTests.cs.meta
│ │ ├── Vector3PackerTests.cs.meta
│ │ ├── FloatPackerCreateTest.cs.meta
│ │ ├── QuaternionPackerTests.cs.meta
│ │ ├── UintBlockPackerTests.cs.meta
│ │ ├── UintPackerCreateTest.cs.meta
│ │ ├── unsignedFloatPackerTests.cs.meta
│ │ ├── PackerTestBase.cs
│ │ ├── unsignedFloatPackerTests.cs
│ │ ├── BitHelperTest.cs
│ │ ├── UintPackerTests.cs
│ │ ├── FloatPackerCreateTest.cs
│ │ ├── UintBlockPackerTests.cs
│ │ ├── Vector2PackerTests.cs
│ │ ├── Vector3PackerTests.cs
│ │ ├── FloatPackerTests.cs
│ │ ├── UintPackerCreateTest.cs
│ │ └── QuaternionPackerTests.cs
│ ├── BitPackingCopyFromOtherTests.cs.meta
│ ├── BitPackingReusingWriterTests.cs.meta
│ ├── TestRandom.cs
│ ├── Mirage.Tests.BitPacking.asmdef
│ ├── BitMaskHelperTests.cs
│ ├── BitPackingProperties.cs
│ ├── BitPackingReusingWriterTests.cs
│ ├── BitPackingCopyFromOtherTests.cs
│ └── BitPackingTests.cs
└── JamesFrowen.BitPacking.Tests.csproj
├── README.md
├── LICENSE
├── BitPacking.sln
├── .gitignore
└── .editorconfig
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: James-Frowen
2 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 |
4 | [assembly: AssemblyVersion("1.0.0")]
5 |
6 | [assembly: InternalsVisibleTo("JamesFrowen.BitPacking.Tests")]
7 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/Packers.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 2629728c95c038a4984b7ab3d5bf9e84
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/JamesFrowen.BitPacking.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 2e1ab74547cbbc742a86c84cdf148326
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/Mirage.Tests.BitPacking.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 467e21328cb049d4bbcbbbf6adcc6d2f
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/BitMask.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e8ce6cd3bc8829743bc1b67cf937135d
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/TestRandom.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 0b44b3fdecf4fc14b85a016d9e1b1a8f
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/AssemblyInfo.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 3f5bdf9082a94734f8f84aac99bbe80f
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/NetworkReader.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 1610f05ec5bd14d6882e689f7372596a
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/NetworkWriter.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 48d2207bcef1f4477b624725f075f9bd
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/Packers/ZigZag.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: fced96ad9167cb14a875d6dace9f55bc
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/BitPackingTests.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ab6374cb6820dd4488f5fe62d1b816a7
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/Logging/LogHelper.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 67a0af8480afd5c49aba9e766b29fbb3
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/Packers/BitHelper.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 1ba9a01829f61d6428bb3e276ac9272a
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/Packers/FloatPacker.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: f44847fc059d5464eba7bd04088ddfa1
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/Packers/Vector2Packer.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: a26a59a805a1d6e47a9e41a9aa5e8108
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/Packers/Vector3Packer.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: d2f6b7a4b33422e47b03898a06a14c7d
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/BitMaskHelperTests.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 923082b03eea0934e972aade59852606
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/BitPackingProperties.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 5a96aaa0a5f3acb4a83b659c28316658
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/Packers/BitHelperTest.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 782d32bee66d34649b2d40b6a111d20d
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/Packers/QuaternionPacker.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 62c06dfd1d38c294badc710697aff611
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/Packers/VarFloatPacker.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 96a76eaf70b1dcd47b1cfda1c98d4420
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/Packers/VarIntBlocksPacker.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 124da2a56c14a924bb6bda5663c0c6f2
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/Packers/VarVector3Packer.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 14e1e42c2a63d324c83b7aaf67d324fa
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/Packers/VariableIntPacker.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 93822b4a356267942b8457394696ba08
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/Packers/FloatPackerTests.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e683c337f85dc4d418e6c71c6f8ac5c4
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/Packers/PackerTestBase.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 0d9edfce25a6a2d4bbd86782e43eae21
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/Packers/UintPackerTests.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 5bb3717be6b5a6143b7ac20ce5ddc740
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/Packers/Vector2PackerTests.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 95e840159b32e0347b99ff97e90d3e2b
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/Packers/Vector3PackerTests.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: cbc014213f9098643b1058db26baa5ee
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # BitPacking
2 | A c# Bit packing implementation for unity and [Mirage Networking](https://github.com/MirageNet/Mirage) and [PositionSyncSystem](https://github.com/James-Frowen/NetworkPositionSync)
3 |
4 | For an up-to-date version of this code see [this Mirage folder](https://github.com/MirageNet/Mirage/tree/main/Assets/Mirage/Runtime/Serialization)
5 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/BitPackingCopyFromOtherTests.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 0fbe9dbb117513140978987f0be0beda
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/BitPackingReusingWriterTests.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: d600aaf09129e234c9ccd9b98223cdc9
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/Packers/FloatPackerCreateTest.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: a14c571ce3156ac4f9fbc8629c2fabc0
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/Packers/QuaternionPackerTests.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 3a126e0a10498944d9b63c0c021fbd41
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/Packers/UintBlockPackerTests.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 043048420116fc54582eaf333e5bf6d8
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/Packers/UintPackerCreateTest.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ba07df19a3e3850409d63c96e0b3cab0
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/Packers/unsignedFloatPackerTests.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: f4c565f91c3e4874ea8f6fb642c0cd99
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/JamesFrowen.BitPacking.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "JamesFrowen.BitPacking",
3 | "references": [],
4 | "optionalUnityReferences": [],
5 | "includePlatforms": [],
6 | "excludePlatforms": [],
7 | "allowUnsafeCode": true,
8 | "overrideReferences": false,
9 | "precompiledReferences": [],
10 | "autoReferenced": true,
11 | "defineConstraints": []
12 | }
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/TestRandom.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Mirage.Tests.BitPacking
4 | {
5 | public static class TestRandom
6 | {
7 | static Random random = new Random();
8 | public static float Range(float a, float b)
9 | {
10 | return (float)((random.NextDouble() * (b - a)) + a);
11 | }
12 | public static int Range(int a, int b)
13 | {
14 | return random.Next(a, b);
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/Mirage.Tests.BitPacking.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Mirage.Tests.BitPacking",
3 | "rootNamespace": "",
4 | "references": [
5 | "Mirage",
6 | "UnityEngine.TestRunner",
7 | "UnityEditor.TestRunner"
8 | ],
9 | "includePlatforms": [
10 | "Editor"
11 | ],
12 | "excludePlatforms": [],
13 | "allowUnsafeCode": true,
14 | "overrideReferences": true,
15 | "precompiledReferences": [
16 | "nunit.framework.dll"
17 | ],
18 | "autoReferenced": false,
19 | "defineConstraints": [
20 | "UNITY_INCLUDE_TESTS"
21 | ],
22 | "versionDefines": [],
23 | "noEngineReferences": false
24 | }
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/Packers/PackerTestBase.cs:
--------------------------------------------------------------------------------
1 | using Mirage.Serialization;
2 | using NUnit.Framework;
3 |
4 | namespace Mirage.Tests.Runtime.Serialization.Packers
5 | {
6 | public class PackerTestBase
7 | {
8 | public readonly NetworkWriter writer = new NetworkWriter(1300);
9 | private readonly NetworkReader reader = new NetworkReader();
10 |
11 | [TearDown]
12 | public virtual void TearDown()
13 | {
14 | writer.Reset();
15 | reader.Dispose();
16 | }
17 |
18 | ///
19 | /// Gets Reader using the current data inside writer
20 | ///
21 | ///
22 | public NetworkReader GetReader()
23 | {
24 | reader.Reset(writer.ToArraySegment());
25 | return reader;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/JamesFrowen.BitPacking.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | false
6 |
7 |
8 |
9 | true
10 | TRACE;BIT_PACKING_NO_DEBUG
11 |
12 |
13 |
14 | true
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | ..\..\..\..\UnityEditors\2019.4.14f1\Editor\Data\Managed\UnityEngine.dll
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/Packers/ZigZag.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | namespace Mirage.Serialization
4 | {
5 | ///
6 | /// See zigzag encoding
7 | ///
8 | public static class ZigZag
9 | {
10 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
11 | public static uint Encode(int v)
12 | {
13 | return (uint)((v >> 31) ^ (v << 1));
14 | }
15 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
16 | public static ulong Encode(long v)
17 | {
18 | return (ulong)((v >> 63) ^ (v << 1));
19 | }
20 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
21 | public static int Decode(uint v)
22 | {
23 | return (int)((v >> 1) ^ -(v & 1));
24 | }
25 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
26 | public static long Decode(ulong v)
27 | {
28 | return ((long)(v >> 1)) ^ -((long)v & 1);
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 James Frowen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/Packers/unsignedFloatPackerTests.cs:
--------------------------------------------------------------------------------
1 | using Mirage.Serialization;
2 | using NUnit.Framework;
3 |
4 | namespace Mirage.Tests.Runtime.Serialization.Packers
5 | {
6 | public class UnsignedFloatPackerTests : PackerTestBase
7 | {
8 | private FloatPacker packer;
9 | private float max;
10 | private float precsion;
11 |
12 | [SetUp]
13 | public void Setup()
14 | {
15 | max = 100;
16 | precsion = 1 / 1000f;
17 | packer = new FloatPacker(max, precsion, false);
18 | }
19 |
20 | [Test]
21 | public void ClampsToZero()
22 | {
23 | packer.Pack(writer, -4.5f);
24 | var outValue = packer.Unpack(GetReader());
25 |
26 | Assert.That(outValue, Is.Zero);
27 | }
28 |
29 | [Test]
30 | public void CanWriteNearMax()
31 | {
32 | const float value = 99.5f;
33 | packer.Pack(writer, value);
34 | var outValue = packer.Unpack(GetReader());
35 |
36 | Assert.That(outValue, Is.EqualTo(value).Within(precsion));
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/JamesFrowen.BitPacking.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 |
6 | false
7 |
8 |
9 |
10 | false
11 | true
12 | TRACE;BIT_PACKING_NO_DEBUG
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | ..\..\..\..\UnityEditors\2019.4.14f1\Editor\Data\Managed\UnityEngine.dll
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/Logging/LogHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace JamesFrowen.BitPacking
4 | {
5 | public static class LogHelper
6 | {
7 | public static Action Log = Console.WriteLine;
8 |
9 | public static void LogBits(uint n)
10 | {
11 | var builder = new System.Text.StringBuilder();
12 | for (int i = sizeof(uint) * 8 - 1; i >= 0; i--)
13 | {
14 | uint shift = n >> i;
15 | uint masked = shift & 0b1;
16 | builder.Append(masked);
17 | }
18 |
19 | Log(builder.ToString());
20 | }
21 |
22 | public static void LogBits(ulong n)
23 | {
24 | var builder = new System.Text.StringBuilder();
25 | for (int i = sizeof(ulong) * 8 - 1; i >= 0; i--)
26 | {
27 | ulong shift = n >> i;
28 | ulong masked = shift & 0b1;
29 | builder.Append(masked);
30 | }
31 |
32 | Log(builder.ToString());
33 | }
34 |
35 | public static void LogHex(ArraySegment segment)
36 | => LogHex(segment.Array, segment.Offset, segment.Count);
37 |
38 | public static void LogHex(byte[] bytes, int? offset = null, int? count = null)
39 | {
40 | Log(BitConverter.ToString(bytes, offset ?? 0, count ?? bytes.Length));
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/BitMaskHelperTests.cs:
--------------------------------------------------------------------------------
1 | using Mirage.Serialization;
2 | using NUnit.Framework;
3 |
4 | namespace Mirage.Tests.Runtime.Serialization
5 | {
6 | public class BitMaskHelperTests
7 | {
8 | ///
9 | /// slow way of creating correct mask
10 | ///
11 | private static ulong slowMask(int bits)
12 | {
13 | ulong mask = 0;
14 | for (var i = 0; i < bits; i++)
15 | {
16 | mask |= 1ul << i;
17 | }
18 |
19 | return mask;
20 | }
21 |
22 | [Test]
23 | [Description("manually checking edge cases to be sure")]
24 | public void MaskValueIsCorrect0()
25 | {
26 | var mask = BitMask.Mask(0);
27 | Assert.That(mask, Is.EqualTo(0x0));
28 | }
29 |
30 | [Test]
31 | [Description("manually checking edge cases to be sure")]
32 | public void MaskValueIsCorrect63()
33 | {
34 | var mask = BitMask.Mask(63);
35 | Assert.That(mask, Is.EqualTo(0x7FFF_FFFF_FFFF_FFFF));
36 | }
37 |
38 | [Test]
39 | [Description("manually checking edge cases to be sure")]
40 | public void MaskValueIsCorrect64()
41 | {
42 | var mask = BitMask.Mask(64);
43 | Assert.That(mask, Is.EqualTo(0xFFFF_FFFF_FFFF_FFFF));
44 | }
45 |
46 | [Test]
47 | public void MaskValueIsCorrect([Range(0, 64)] int bits)
48 | {
49 | var mask = BitMask.Mask(bits);
50 | var expected = slowMask(bits);
51 | Assert.That(mask, Is.EqualTo(expected), $" mask:{mask:X}\nexpected:{expected:X}");
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/Packers/BitHelperTest.cs:
--------------------------------------------------------------------------------
1 | using Mirage.Serialization;
2 | using NUnit.Framework;
3 |
4 | namespace Mirage.Tests.Runtime.Serialization.Packers
5 | {
6 | public class BitHelperTest
7 | {
8 | [Test]
9 | [TestCase(2, 0.05f, ExpectedResult = 7)]
10 | [TestCase(100, 0.1f, ExpectedResult = 11)]
11 | [TestCase(.707f, 0.002f, ExpectedResult = 10)]
12 | [TestCase(1000, 0.1f, ExpectedResult = 15)]
13 | [TestCase(2000, 0.01f, ExpectedResult = 19)]
14 | // 1023 is 10 bits, but max is -+ so 11 bits
15 | [TestCase(1023, 1, ExpectedResult = 11)]
16 | [TestCase(16, 0.5f, ExpectedResult = 7)]
17 | public int ReturnCorrectBitCountForMaxPrecision(float max, float precision)
18 | {
19 | return BitHelper.BitCount(max, precision);
20 | }
21 |
22 | [Test]
23 | [TestCase(0b1UL, ExpectedResult = 1)]
24 | [TestCase(0b10UL, ExpectedResult = 2)]
25 | [TestCase(0b11UL, ExpectedResult = 2)]
26 | [TestCase(0b100UL, ExpectedResult = 3)]
27 | [TestCase(0b101UL, ExpectedResult = 3)]
28 | [TestCase(0b110UL, ExpectedResult = 3)]
29 | [TestCase(0b111UL, ExpectedResult = 3)]
30 | [TestCase(8UL, ExpectedResult = 4)]
31 | [TestCase(15UL, ExpectedResult = 4)]
32 | [TestCase(16UL, ExpectedResult = 5)]
33 | [TestCase(31UL, ExpectedResult = 5)]
34 | [TestCase(32UL, ExpectedResult = 6)]
35 | [TestCase(63UL, ExpectedResult = 6)]
36 | [TestCase(255UL, ExpectedResult = 8)]
37 | [TestCase(256UL, ExpectedResult = 9)]
38 | public int ReturnCorrectBitCountForRange(ulong range)
39 | {
40 | return BitHelper.BitCount(range);
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/Packers/VarFloatPacker.cs:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License
3 |
4 | Copyright (c) 2021 James Frowen
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | */
24 |
25 | using UnityEngine;
26 |
27 | namespace Mirage.Serialization
28 | {
29 | ///
30 | /// Packs a float using and
31 | ///
32 | public sealed class VarFloatPacker
33 | {
34 | private readonly int _blockSize;
35 | private readonly float _precision;
36 | private readonly float _inversePrecision;
37 |
38 | public VarFloatPacker(float precision, int blockSize)
39 | {
40 | _precision = precision;
41 | _blockSize = blockSize;
42 | _inversePrecision = 1 / precision;
43 | }
44 |
45 | public void Pack(NetworkWriter writer, float value)
46 | {
47 | var scaled = Mathf.RoundToInt(value * _inversePrecision);
48 | var zig = ZigZag.Encode(scaled);
49 | VarIntBlocksPacker.Pack(writer, zig, _blockSize);
50 | }
51 |
52 | public float Unpack(NetworkReader reader)
53 | {
54 | var zig = (uint)VarIntBlocksPacker.Unpack(reader, _blockSize);
55 | var scaled = ZigZag.Decode(zig);
56 | return scaled * _precision;
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/Packers/VarVector3Packer.cs:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License
3 |
4 | Copyright (c) 2021 James Frowen
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | */
24 |
25 | using UnityEngine;
26 |
27 | namespace Mirage.Serialization
28 | {
29 | ///
30 | /// Packs a vector3 using and
31 | ///
32 | public sealed class VarVector3Packer
33 | {
34 | private readonly VarFloatPacker _xPacker;
35 | private readonly VarFloatPacker _yPacker;
36 | private readonly VarFloatPacker _zPacker;
37 |
38 | public VarVector3Packer(Vector3 precision, int blocksize)
39 | {
40 | _xPacker = new VarFloatPacker(precision.x, blocksize);
41 | _yPacker = new VarFloatPacker(precision.y, blocksize);
42 | _zPacker = new VarFloatPacker(precision.z, blocksize);
43 | }
44 |
45 | public void Pack(NetworkWriter writer, Vector3 position)
46 | {
47 | _xPacker.Pack(writer, position.x);
48 | _yPacker.Pack(writer, position.y);
49 | _zPacker.Pack(writer, position.z);
50 | }
51 |
52 | public Vector3 Unpack(NetworkReader reader)
53 | {
54 | Vector3 value = default;
55 | value.x = _xPacker.Unpack(reader);
56 | value.y = _yPacker.Unpack(reader);
57 | value.z = _zPacker.Unpack(reader);
58 | return value;
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/Packers/Vector2Packer.cs:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License
3 |
4 | Copyright (c) 2021 James Frowen
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | */
24 |
25 | using UnityEngine;
26 |
27 | namespace Mirage.Serialization
28 | {
29 | public sealed class Vector2Packer
30 | {
31 | private readonly FloatPacker _xPacker;
32 | private readonly FloatPacker _yPacker;
33 |
34 | public Vector2Packer(float xMax, float yMax, int xBitCount, int yBitCount)
35 | {
36 | _xPacker = new FloatPacker(xMax, xBitCount);
37 | _yPacker = new FloatPacker(yMax, yBitCount);
38 | }
39 | public Vector2Packer(float xMax, float yMax, float xPrecision, float yPrecision)
40 | {
41 | _xPacker = new FloatPacker(xMax, xPrecision);
42 | _yPacker = new FloatPacker(yMax, yPrecision);
43 | }
44 | public Vector2Packer(Vector2 max, Vector2 precision)
45 | {
46 | _xPacker = new FloatPacker(max.x, precision.x);
47 | _yPacker = new FloatPacker(max.y, precision.y);
48 | }
49 |
50 | public void Pack(NetworkWriter writer, Vector2 value)
51 | {
52 | _xPacker.Pack(writer, value.x);
53 | _yPacker.Pack(writer, value.y);
54 | }
55 |
56 | public Vector2 Unpack(NetworkReader reader)
57 | {
58 | Vector2 value = default;
59 | value.x = _xPacker.Unpack(reader);
60 | value.y = _yPacker.Unpack(reader);
61 | return value;
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/BitMask.cs:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License
3 |
4 | Copyright (c) 2021 James Frowen
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | */
24 |
25 | using System.Runtime.CompilerServices;
26 |
27 | namespace Mirage.Serialization
28 | {
29 | public static class BitMask
30 | {
31 | ///
32 | /// Creates mask for
33 | ///
34 | /// (showing 32 bits for simplify, result is 64 bit)
35 | ///
36 | /// Example bits = 4 => mask = 00000000_00000000_00000000_00001111
37 | ///
38 | /// Example bits = 10 => mask = 00000000_00000000_00000011_11111111
39 | ///
40 | ///
41 | ///
42 | ///
43 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
44 | public static ulong Mask(int bits)
45 | {
46 | return bits == 0 ? 0 : ulong.MaxValue >> (64 - bits);
47 | }
48 |
49 | ///
50 | /// Creates Mask either side of start and end
51 | /// Note this mask is only valid for start [0..63] and end [0..64]
52 | ///
53 | ///
54 | ///
55 | ///
56 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
57 | public static ulong OuterMask(int start, int end)
58 | {
59 | return (ulong.MaxValue << start) ^ (ulong.MaxValue >> (64 - end));
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/Packers/VarIntBlocksPacker.cs:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License
3 |
4 | Copyright (c) 2021 James Frowen
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | */
24 |
25 | using System;
26 | using System.Runtime.CompilerServices;
27 |
28 | namespace Mirage.Serialization
29 | {
30 | public static class VarIntBlocksPacker
31 | {
32 | // todo needs doc comments
33 | // todo neeeds tests
34 |
35 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
36 | public static void Pack(NetworkWriter writer, ulong value, int blockSize)
37 | {
38 | // always writes atleast 1 block
39 | var count = 1;
40 | var checkValue = value >> blockSize;
41 | while (checkValue != 0)
42 | {
43 | count++;
44 | checkValue >>= blockSize;
45 | }
46 | // count = 1, write = b0, (1<<(1-1) -1 => 1<<0 -1) => 1 -1 => 0)
47 | // count = 2, write = b01
48 | // count = 3, write = b011, (1<<(3-1) -1 => 1<<2 -1) => 100 - 1 => 011)
49 | writer.Write((1ul << (count - 1)) - 1, count);
50 | writer.Write(value, Math.Min(64, blockSize * count));
51 | }
52 |
53 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
54 | public static ulong Unpack(NetworkReader reader, int blockSize)
55 | {
56 | var blocks = 1;
57 | // read bits till we see a zero
58 | while (reader.ReadBoolean())
59 | {
60 | blocks++;
61 | }
62 |
63 | return reader.Read(Math.Min(64, blocks * blockSize));
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/Packers/UintPackerTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Mirage.Serialization;
3 | using NUnit.Framework;
4 | using Random = System.Random;
5 |
6 | namespace Mirage.Tests.Runtime.Serialization.Packers
7 | {
8 | [TestFixture(50ul, 1_000ul, null)]
9 | [TestFixture(250ul, 10_000ul, null)]
10 | [TestFixture(500ul, 100_000ul, null)]
11 | [TestFixture(50ul, 1_000ul, 10_000_000ul)]
12 | [TestFixture(250ul, 10_000ul, 10_000_000ul)]
13 | [TestFixture(500ul, 100_000ul, 10_000_000ul)]
14 | public class UintPackerTests : PackerTestBase
15 | {
16 | private readonly Random random = new Random();
17 | private readonly VarIntPacker packer;
18 | private readonly ulong max;
19 |
20 | public UintPackerTests(ulong smallValue, ulong mediumValue, ulong? largeValue)
21 | {
22 | if (largeValue.HasValue)
23 | {
24 | packer = new VarIntPacker(smallValue, mediumValue, largeValue.Value, false);
25 | max = largeValue.Value;
26 | }
27 | else
28 | {
29 | packer = new VarIntPacker(smallValue, mediumValue);
30 | max = ulong.MaxValue;
31 | }
32 | }
33 |
34 | private ulong GetRandonUlongBias()
35 | {
36 | return (ulong)(Math.Abs(random.NextDouble() - random.NextDouble()) * max);
37 | }
38 |
39 | private uint GetRandonUintBias()
40 | {
41 | return (uint)(Math.Abs(random.NextDouble() - random.NextDouble()) * Math.Min(max, uint.MaxValue));
42 | }
43 |
44 | private ushort GetRandonUshortBias()
45 | {
46 | return (ushort)(Math.Abs(random.NextDouble() - random.NextDouble()) * Math.Min(max, ushort.MaxValue));
47 | }
48 |
49 | [Test]
50 | [Repeat(1000)]
51 | public void UnpacksCorrectUlongValue()
52 | {
53 | var start = GetRandonUlongBias();
54 | packer.PackUlong(writer, start);
55 | var unpacked = packer.UnpackUlong(GetReader());
56 |
57 | Assert.That(unpacked, Is.EqualTo(start));
58 | }
59 |
60 | [Test]
61 | [Repeat(1000)]
62 | public void UnpacksCorrectUintValue()
63 | {
64 | var start = GetRandonUintBias();
65 | packer.PackUint(writer, start);
66 | var unpacked = packer.UnpackUint(GetReader());
67 |
68 | Assert.That(unpacked, Is.EqualTo(start));
69 | }
70 |
71 | [Test]
72 | [Repeat(1000)]
73 | public void UnpacksCorrectUshortValue()
74 | {
75 | var start = GetRandonUshortBias();
76 | packer.PackUshort(writer, start);
77 | var unpacked = packer.UnpackUshort(GetReader());
78 |
79 | Assert.That(unpacked, Is.EqualTo(start));
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/Packers/Vector3Packer.cs:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License
3 |
4 | Copyright (c) 2021 James Frowen
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | */
24 |
25 | using UnityEngine;
26 |
27 | namespace Mirage.Serialization
28 | {
29 | public sealed class Vector3Packer
30 | {
31 | private readonly FloatPacker _xPacker;
32 | private readonly FloatPacker _yPacker;
33 | private readonly FloatPacker _zPacker;
34 |
35 | public Vector3Packer(float xMax, float yMax, float zMax, int xBitCount, int yBitCount, int zBitCount)
36 | {
37 | _xPacker = new FloatPacker(xMax, xBitCount);
38 | _yPacker = new FloatPacker(yMax, yBitCount);
39 | _zPacker = new FloatPacker(zMax, zBitCount);
40 | }
41 | public Vector3Packer(float xMax, float yMax, float zMax, float xPrecision, float yPrecision, float zPrecision)
42 | {
43 | _xPacker = new FloatPacker(xMax, xPrecision);
44 | _yPacker = new FloatPacker(yMax, yPrecision);
45 | _zPacker = new FloatPacker(zMax, zPrecision);
46 | }
47 | public Vector3Packer(Vector3 max, Vector3 precision)
48 | {
49 | _xPacker = new FloatPacker(max.x, precision.x);
50 | _yPacker = new FloatPacker(max.y, precision.y);
51 | _zPacker = new FloatPacker(max.z, precision.z);
52 | }
53 |
54 | public void Pack(NetworkWriter writer, Vector3 value)
55 | {
56 | _xPacker.Pack(writer, value.x);
57 | _yPacker.Pack(writer, value.y);
58 | _zPacker.Pack(writer, value.z);
59 | }
60 |
61 | public Vector3 Unpack(NetworkReader reader)
62 | {
63 | Vector3 value = default;
64 | value.x = _xPacker.Unpack(reader);
65 | value.y = _yPacker.Unpack(reader);
66 | value.z = _zPacker.Unpack(reader);
67 | return value;
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/Packers/FloatPackerCreateTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Mirage.Serialization;
3 | using NUnit.Framework;
4 | using UnityEngine;
5 | using Range = NUnit.Framework.RangeAttribute;
6 |
7 | namespace Mirage.Tests.Runtime.Serialization.Packers
8 | {
9 | public class FloatPackerCreateTest : PackerTestBase
10 | {
11 | [Test]
12 | [TestCase(1, ExpectedResult = 8)]
13 | [TestCase(0.1f, ExpectedResult = 11)]
14 | [TestCase(0.01f, ExpectedResult = 15)]
15 | public int CreateUsingPrecsion(float precision)
16 | {
17 | var packer = new FloatPacker(100, precision);
18 |
19 | packer.Pack(writer, 1f);
20 | return writer.BitPosition;
21 | }
22 |
23 | [Test]
24 | [TestCase(1, ExpectedResult = 7)]
25 | [TestCase(0.1f, ExpectedResult = 10)]
26 | [TestCase(0.01f, ExpectedResult = 14)]
27 | public int BitCountIsLessForUnSigned(float precision)
28 | {
29 | var packer = new FloatPacker(100, precision, false);
30 |
31 | packer.Pack(writer, 1f);
32 | return writer.BitPosition;
33 | }
34 |
35 | [Test]
36 | public void PackFromBitCountPacksToCorrectCount([Range(1, 30)] int bitCount, [Values(true, false)] bool signed)
37 | {
38 | var packer = new FloatPacker(100, bitCount, signed);
39 |
40 | packer.Pack(writer, 1f);
41 |
42 | Assert.That(writer.BitPosition, Is.EqualTo(bitCount));
43 | }
44 |
45 | [Test]
46 | public void ThrowsIfBitCountIsLessThan1([Range(-10, 0)] int bitCount, [Values(true, false)] bool signed)
47 | {
48 | var exception = Assert.Throws(() =>
49 | {
50 | _ = new FloatPacker(10, bitCount, signed);
51 | });
52 |
53 | var expected = new ArgumentException("Bit count is too low, bit count should be between 1 and 30", "bitCount");
54 | Assert.That(exception, Has.Message.EqualTo(expected.Message));
55 | }
56 |
57 | [Test]
58 | public void ThrowsIfBitCountIsGreaterThan30([Range(31, 40)] int bitCount, [Values(true, false)] bool signed)
59 | {
60 | var exception = Assert.Throws(() =>
61 | {
62 | _ = new FloatPacker(10, bitCount, signed);
63 | });
64 |
65 | var expected = new ArgumentException("Bit count is too high, bit count should be between 1 and 30", "bitCount");
66 | Assert.That(exception, Has.Message.EqualTo(expected.Message));
67 | }
68 |
69 | [Test]
70 | public void ThrowsIfMaxIsZero([Values(true, false)] bool signed)
71 | {
72 | var exception = Assert.Throws(() =>
73 | {
74 | _ = new FloatPacker(0, 1, signed);
75 | });
76 |
77 | var expected = new ArgumentException("Max can not be 0", "max");
78 | Assert.That(exception, Has.Message.EqualTo(expected.Message));
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/Packers/UintBlockPackerTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Mirage.Serialization;
3 | using NUnit.Framework;
4 | using Random = System.Random;
5 |
6 | namespace Mirage.Tests.Runtime.Serialization.Packers
7 | {
8 | [TestFixture(4)]
9 | [TestFixture(7)]
10 | [TestFixture(8)]
11 | [TestFixture(12)]
12 | [TestFixture(16)]
13 | public class UintBlockPackerTests : PackerTestBase
14 | {
15 | private readonly Random random = new Random();
16 | private readonly int blockSize;
17 |
18 | public UintBlockPackerTests(int blockSize)
19 | {
20 | this.blockSize = blockSize;
21 | }
22 |
23 | private ulong GetRandonUlongBias()
24 | {
25 | return (ulong)(Math.Abs(random.NextDouble() - random.NextDouble()) * ulong.MaxValue);
26 | }
27 |
28 | private uint GetRandonUintBias()
29 | {
30 | return (uint)(Math.Abs(random.NextDouble() - random.NextDouble()) * uint.MaxValue);
31 | }
32 |
33 | private ushort GetRandonUshortBias()
34 | {
35 | return (ushort)(Math.Abs(random.NextDouble() - random.NextDouble()) * ushort.MaxValue);
36 | }
37 |
38 | [Test]
39 | [Repeat(1000)]
40 | public void UnpacksCorrectUlongValue()
41 | {
42 | var start = GetRandonUlongBias();
43 | VarIntBlocksPacker.Pack(writer, start, blockSize);
44 | var unpacked = VarIntBlocksPacker.Unpack(GetReader(), blockSize);
45 |
46 | Assert.That(unpacked, Is.EqualTo(start));
47 | }
48 |
49 | [Test]
50 | [Repeat(1000)]
51 | public void UnpacksCorrectUintValue()
52 | {
53 | var start = GetRandonUintBias();
54 | VarIntBlocksPacker.Pack(writer, start, blockSize);
55 | var unpacked = VarIntBlocksPacker.Unpack(GetReader(), blockSize);
56 |
57 | Assert.That(unpacked, Is.EqualTo(start));
58 | }
59 |
60 | [Test]
61 | [Repeat(1000)]
62 | public void UnpacksCorrectUshortValue()
63 | {
64 | var start = GetRandonUshortBias();
65 | VarIntBlocksPacker.Pack(writer, start, blockSize);
66 | var unpacked = VarIntBlocksPacker.Unpack(GetReader(), blockSize);
67 |
68 | Assert.That(unpacked, Is.EqualTo(start));
69 | }
70 |
71 | [Test]
72 | public void WritesNplus1BitsPerBlock()
73 | {
74 | var zero = 0u;
75 | VarIntBlocksPacker.Pack(writer, zero, blockSize);
76 | Assert.That(writer.BitPosition, Is.EqualTo(blockSize + 1));
77 |
78 | var unpacked = VarIntBlocksPacker.Unpack(GetReader(), blockSize);
79 | Assert.That(unpacked, Is.EqualTo(zero));
80 | }
81 |
82 | [Test]
83 | public void WritesNplus1BitsPerBlock_bigger()
84 | {
85 | var aboveBlockSize = (1u << blockSize) + 1u;
86 | VarIntBlocksPacker.Pack(writer, aboveBlockSize, blockSize);
87 | Assert.That(writer.BitPosition, Is.EqualTo(2 * (blockSize + 1)));
88 |
89 | var unpacked = VarIntBlocksPacker.Unpack(GetReader(), blockSize);
90 | Assert.That(unpacked, Is.EqualTo(aboveBlockSize));
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/BitPacking.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30717.126
5 | MinimumVisualStudioVersion = 15.0.26124.0
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JamesFrowen.BitPacking", "JamesFrowen.BitPacking\JamesFrowen.BitPacking.csproj", "{82F3D519-6EB4-4B68-9D97-DC0F944C5136}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JamesFrowen.BitPacking.Tests", "JamesFrowen.BitPacking.Tests\JamesFrowen.BitPacking.Tests.csproj", "{A5308AB2-1557-4AEE-A9B9-F4C5CD23AEAB}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A38990DC-0C3C-45DA-9B73-6F3BE86D4361}"
11 | ProjectSection(SolutionItems) = preProject
12 | .editorconfig = .editorconfig
13 | EndProjectSection
14 | EndProject
15 | Global
16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
17 | Debug|Any CPU = Debug|Any CPU
18 | Debug|x64 = Debug|x64
19 | Debug|x86 = Debug|x86
20 | Release|Any CPU = Release|Any CPU
21 | Release|x64 = Release|x64
22 | Release|x86 = Release|x86
23 | EndGlobalSection
24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
25 | {82F3D519-6EB4-4B68-9D97-DC0F944C5136}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26 | {82F3D519-6EB4-4B68-9D97-DC0F944C5136}.Debug|Any CPU.Build.0 = Debug|Any CPU
27 | {82F3D519-6EB4-4B68-9D97-DC0F944C5136}.Debug|x64.ActiveCfg = Debug|Any CPU
28 | {82F3D519-6EB4-4B68-9D97-DC0F944C5136}.Debug|x64.Build.0 = Debug|Any CPU
29 | {82F3D519-6EB4-4B68-9D97-DC0F944C5136}.Debug|x86.ActiveCfg = Debug|Any CPU
30 | {82F3D519-6EB4-4B68-9D97-DC0F944C5136}.Debug|x86.Build.0 = Debug|Any CPU
31 | {82F3D519-6EB4-4B68-9D97-DC0F944C5136}.Release|Any CPU.ActiveCfg = Release|Any CPU
32 | {82F3D519-6EB4-4B68-9D97-DC0F944C5136}.Release|Any CPU.Build.0 = Release|Any CPU
33 | {82F3D519-6EB4-4B68-9D97-DC0F944C5136}.Release|x64.ActiveCfg = Release|Any CPU
34 | {82F3D519-6EB4-4B68-9D97-DC0F944C5136}.Release|x64.Build.0 = Release|Any CPU
35 | {82F3D519-6EB4-4B68-9D97-DC0F944C5136}.Release|x86.ActiveCfg = Release|Any CPU
36 | {82F3D519-6EB4-4B68-9D97-DC0F944C5136}.Release|x86.Build.0 = Release|Any CPU
37 | {A5308AB2-1557-4AEE-A9B9-F4C5CD23AEAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
38 | {A5308AB2-1557-4AEE-A9B9-F4C5CD23AEAB}.Debug|Any CPU.Build.0 = Debug|Any CPU
39 | {A5308AB2-1557-4AEE-A9B9-F4C5CD23AEAB}.Debug|x64.ActiveCfg = Debug|Any CPU
40 | {A5308AB2-1557-4AEE-A9B9-F4C5CD23AEAB}.Debug|x64.Build.0 = Debug|Any CPU
41 | {A5308AB2-1557-4AEE-A9B9-F4C5CD23AEAB}.Debug|x86.ActiveCfg = Debug|Any CPU
42 | {A5308AB2-1557-4AEE-A9B9-F4C5CD23AEAB}.Debug|x86.Build.0 = Debug|Any CPU
43 | {A5308AB2-1557-4AEE-A9B9-F4C5CD23AEAB}.Release|Any CPU.ActiveCfg = Release|Any CPU
44 | {A5308AB2-1557-4AEE-A9B9-F4C5CD23AEAB}.Release|Any CPU.Build.0 = Release|Any CPU
45 | {A5308AB2-1557-4AEE-A9B9-F4C5CD23AEAB}.Release|x64.ActiveCfg = Release|Any CPU
46 | {A5308AB2-1557-4AEE-A9B9-F4C5CD23AEAB}.Release|x64.Build.0 = Release|Any CPU
47 | {A5308AB2-1557-4AEE-A9B9-F4C5CD23AEAB}.Release|x86.ActiveCfg = Release|Any CPU
48 | {A5308AB2-1557-4AEE-A9B9-F4C5CD23AEAB}.Release|x86.Build.0 = Release|Any CPU
49 | EndGlobalSection
50 | GlobalSection(SolutionProperties) = preSolution
51 | HideSolutionNode = FALSE
52 | EndGlobalSection
53 | GlobalSection(ExtensibilityGlobals) = postSolution
54 | SolutionGuid = {DACFC036-4A8D-4097-9B77-57C70FB3ECEC}
55 | EndGlobalSection
56 | EndGlobal
57 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/Packers/BitHelper.cs:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License
3 |
4 | Copyright (c) 2021 James Frowen
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | */
24 |
25 | using System;
26 | using System.Runtime.CompilerServices;
27 | using UnityEngine;
28 |
29 | namespace Mirage.Serialization
30 | {
31 | public static class BitHelper
32 | {
33 | ///
34 | /// Gets the number of bits need for in range negative to positive
35 | ///
36 | /// WARNING: these methods are not fast, dont use in hotpath
37 | ///
38 | ///
39 | ///
40 | /// lowest precision required, bit count will round up so real precision might be higher
41 | ///
42 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
43 | public static int BitCount(float max, float precision)
44 | {
45 | return BitCount(max, precision, true);
46 | }
47 |
48 | ///
49 | /// Gets the number of bits need for in range
50 | /// If signed then range is negative max to positive max, If unsigned then 0 to max
51 | ///
52 | /// WARNING: these methods are not fast, dont use in hotpath
53 | ///
54 | ///
55 | ///
56 | /// lowest precision required, bit count will round up so real precision might be higher
57 | ///
58 | public static int BitCount(float max, float precision, bool signed)
59 | {
60 | float multiplier = signed ? 2 : 1;
61 | return Mathf.FloorToInt(Mathf.Log(multiplier * max / precision, 2)) + 1;
62 | }
63 |
64 | ///
65 | /// Gets the number of bits need for
66 | ///
67 | /// WARNING: these methods are not fast, dont use in hotpath
68 | ///
69 | ///
70 | ///
71 | /// lowest precision required, bit count will round up so real precision might be higher
72 | ///
73 | public static int BitCount(ulong max)
74 | {
75 | return (int)Math.Floor(Math.Log(max, 2)) + 1;
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/BitPackingProperties.cs:
--------------------------------------------------------------------------------
1 | using Mirage.Serialization;
2 | using NUnit.Framework;
3 |
4 | namespace Mirage.Tests.Runtime.Serialization
5 | {
6 | public class BitPackingProperties
7 | {
8 | private readonly NetworkWriter writer = new NetworkWriter(1300);
9 | private readonly NetworkReader reader = new NetworkReader();
10 | private readonly byte[] sampleData = new byte[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
11 | private const int BITS_PER_BYTE = 8;
12 |
13 | [TearDown]
14 | public void TearDown()
15 | {
16 | writer.Reset();
17 | reader.Dispose();
18 | }
19 |
20 |
21 | [Test]
22 | public void WriterBitPositionStartsAtZero()
23 | {
24 | Assert.That(writer.BitPosition, Is.EqualTo(0));
25 | }
26 |
27 | [Test]
28 | public void WriterByteLengthStartsAtZero()
29 | {
30 | Assert.That(writer.ByteLength, Is.EqualTo(0));
31 | }
32 |
33 | [Test]
34 | public void ReaderBitPositionStartsStartsAtZero()
35 | {
36 | reader.Reset(sampleData);
37 | Assert.That(reader.BitPosition, Is.EqualTo(0));
38 | }
39 |
40 | [Test]
41 | public void ReaderBytePositionStartsStartsAtZero()
42 | {
43 | reader.Reset(sampleData);
44 | Assert.That(reader.BytePosition, Is.EqualTo(0));
45 | }
46 |
47 | [Test]
48 | public void ReaderBitLengthStartsStartsAtArrayLength()
49 | {
50 | reader.Reset(sampleData);
51 | Assert.That(reader.BitLength, Is.EqualTo(sampleData.Length * BITS_PER_BYTE));
52 | }
53 |
54 |
55 |
56 | [Test]
57 | public void WriterBitPositionIncreasesAfterWriting()
58 | {
59 | writer.Write(0, 15);
60 | Assert.That(writer.BitPosition, Is.EqualTo(15));
61 |
62 | writer.Write(0, 50);
63 | Assert.That(writer.BitPosition, Is.EqualTo(65));
64 | }
65 |
66 | [Test]
67 | public void WriterByteLengthIncreasesAfterWriting_ShouldRoundUp()
68 | {
69 | writer.Write(0, 15);
70 | Assert.That(writer.ByteLength, Is.EqualTo(2));
71 |
72 | writer.Write(0, 50);
73 | Assert.That(writer.ByteLength, Is.EqualTo(9));
74 | }
75 |
76 | [Test]
77 | public void ReaderBitPositionIncreasesAfterReading()
78 | {
79 | reader.Reset(sampleData);
80 | _ = reader.Read(15);
81 | Assert.That(reader.BitPosition, Is.EqualTo(15));
82 |
83 | _ = reader.Read(50);
84 | Assert.That(reader.BitPosition, Is.EqualTo(65));
85 | }
86 |
87 | [Test]
88 | public void ReaderBytePositionIncreasesAfterReading_ShouldRoundUp()
89 | {
90 | reader.Reset(sampleData);
91 | _ = reader.Read(15);
92 | Assert.That(reader.BytePosition, Is.EqualTo(2));
93 |
94 | _ = reader.Read(50);
95 | Assert.That(reader.BytePosition, Is.EqualTo(9));
96 | }
97 |
98 | [Test]
99 | public void ReaderBitLengthDoesnotIncreasesAfterReading()
100 | {
101 | reader.Reset(sampleData);
102 | _ = reader.Read(15);
103 | Assert.That(reader.BitLength, Is.EqualTo(sampleData.Length * BITS_PER_BYTE));
104 |
105 | _ = reader.Read(50);
106 | Assert.That(reader.BitLength, Is.EqualTo(sampleData.Length * BITS_PER_BYTE));
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/Packers/Vector2PackerTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using Mirage.Serialization;
5 | using NUnit.Framework;
6 | using UnityEngine;
7 | using Random = Mirage.Tests.BitPacking.TestRandom;
8 |
9 | namespace Mirage.Tests.Runtime.Serialization.Packers
10 | {
11 | public class Vector2PackerTests : PackerTestBase
12 | {
13 | private static IEnumerable WriteCorrectNumberOfBitsCases()
14 | {
15 | yield return new TestCaseData(Vector2.one * 100, Vector2.one * 0.1f).Returns(11 * 2);
16 | yield return new TestCaseData(Vector2.one * 200, Vector2.one * 0.1f).Returns(12 * 2);
17 | yield return new TestCaseData(Vector2.one * 200, Vector2.one * 0.05f).Returns(13 * 2);
18 | yield return new TestCaseData(new Vector2(100, 50), Vector2.one * 0.1f).Returns(11 + 10);
19 | yield return new TestCaseData(new Vector2(100, 50), new Vector2(0.1f, 0.2f)).Returns(11 + 9);
20 | }
21 |
22 | [Test]
23 | [TestCaseSource(nameof(WriteCorrectNumberOfBitsCases))]
24 | public int WriteCorrectNumberOfBits(Vector2 max, Vector2 precision)
25 | {
26 | var packer = new Vector2Packer(max, precision);
27 | packer.Pack(writer, Vector2.zero);
28 | return writer.BitPosition;
29 | }
30 |
31 | private static IEnumerable ThrowsIfAnyMaxIsZeroCases()
32 | {
33 | yield return new TestCaseData(new Vector2(100, 0), Vector2.one * 0.1f);
34 | yield return new TestCaseData(new Vector2(0, 100), Vector2.one * 0.1f);
35 | }
36 |
37 | [Test]
38 | [TestCaseSource(nameof(ThrowsIfAnyMaxIsZeroCases))]
39 | public void ThrowsIfAnyMaxIsZero(Vector2 max, Vector2 precision)
40 | {
41 | var exception = Assert.Throws(() =>
42 | {
43 | _ = new Vector2Packer(max, precision);
44 | });
45 |
46 | var expected = new ArgumentException("Max can not be 0", "max");
47 | Assert.That(exception, Has.Message.EqualTo(expected.Message));
48 | }
49 |
50 | private static IEnumerable UnpacksToSaveValueCases()
51 | {
52 | yield return new TestCaseData(Vector2.one * 100, Vector2.one * 0.1f);
53 | yield return new TestCaseData(Vector2.one * 200, Vector2.one * 0.1f);
54 | yield return new TestCaseData(Vector2.one * 200, Vector2.one * 0.05f);
55 | yield return new TestCaseData(new Vector2(100, 50), Vector2.one * 0.1f);
56 | yield return new TestCaseData(new Vector2(100, 50), new Vector2(0.1f, 0.2f));
57 | }
58 |
59 | [Test]
60 | [TestCaseSource(nameof(UnpacksToSaveValueCases))]
61 | [Repeat(100)]
62 | public void UnpacksToSaveValue(Vector2 max, Vector2 precision)
63 | {
64 | var packer = new Vector2Packer(max, precision);
65 | var expected = new Vector2(
66 | Random.Range(-max.x, -max.x),
67 | Random.Range(-max.y, -max.y)
68 | );
69 |
70 | packer.Pack(writer, expected);
71 | var unpacked = packer.Unpack(GetReader());
72 |
73 | Assert.That(unpacked.x, Is.EqualTo(expected.x).Within(precision.x));
74 | Assert.That(unpacked.y, Is.EqualTo(expected.y).Within(precision.y));
75 | }
76 |
77 | [Test]
78 | [TestCaseSource(nameof(UnpacksToSaveValueCases))]
79 | public void ZeroUnpacksAsZero(Vector2 max, Vector2 precision)
80 | {
81 | var packer = new Vector2Packer(max, precision);
82 | var zero = Vector2.zero;
83 |
84 | packer.Pack(writer, zero);
85 | var unpacked = packer.Unpack(GetReader());
86 |
87 | Assert.That(unpacked, Is.EqualTo(zero));
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/Packers/Vector3PackerTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using Mirage.Serialization;
5 | using NUnit.Framework;
6 | using UnityEngine;
7 | using Random = Mirage.Tests.BitPacking.TestRandom;
8 |
9 | namespace Mirage.Tests.Runtime.Serialization.Packers
10 | {
11 | public class Vector3PackerTests : PackerTestBase
12 | {
13 | private static IEnumerable WriteCorrectNumberOfBitsCases()
14 | {
15 | yield return new TestCaseData(Vector3.one * 100, Vector3.one * 0.1f).Returns(11 * 3);
16 | yield return new TestCaseData(Vector3.one * 200, Vector3.one * 0.1f).Returns(12 * 3);
17 | yield return new TestCaseData(Vector3.one * 200, Vector3.one * 0.05f).Returns(13 * 3);
18 | yield return new TestCaseData(new Vector3(100, 50, 100), Vector3.one * 0.1f).Returns(11 + 10 + 11);
19 | yield return new TestCaseData(new Vector3(100, 50, 100), new Vector3(0.1f, 0.2f, 0.1f)).Returns(11 + 9 + 11);
20 | yield return new TestCaseData(new Vector3(100, 50, 200), Vector3.one * 0.1f).Returns(11 + 10 + 12);
21 | yield return new TestCaseData(new Vector3(100, 50, 200), new Vector3(0.1f, 0.2f, 0.05f)).Returns(11 + 9 + 13);
22 | }
23 |
24 | [Test]
25 | [TestCaseSource(nameof(WriteCorrectNumberOfBitsCases))]
26 | public int WriteCorrectNumberOfBits(Vector3 max, Vector3 precision)
27 | {
28 | var packer = new Vector3Packer(max, precision);
29 | packer.Pack(writer, Vector3.zero);
30 | return writer.BitPosition;
31 | }
32 |
33 | private static IEnumerable ThrowsIfAnyMaxIsZeroCases()
34 | {
35 | yield return new TestCaseData(new Vector3(100, 0, 100), Vector3.one * 0.1f);
36 | yield return new TestCaseData(new Vector3(0, 100, 100), Vector3.one * 0.1f);
37 | yield return new TestCaseData(new Vector3(100, 100, 0), Vector3.one * 0.1f);
38 | }
39 |
40 | [Test]
41 | [TestCaseSource(nameof(ThrowsIfAnyMaxIsZeroCases))]
42 | public void ThrowsIfAnyMaxIsZero(Vector3 max, Vector3 precision)
43 | {
44 | var exception = Assert.Throws(() =>
45 | {
46 | _ = new Vector3Packer(max, precision);
47 | });
48 |
49 | var expected = new ArgumentException("Max can not be 0", "max");
50 | Assert.That(exception, Has.Message.EqualTo(expected.Message));
51 | }
52 |
53 | private static IEnumerable UnpacksToSaveValueCases()
54 | {
55 | yield return new TestCaseData(Vector3.one * 100, Vector3.one * 0.1f);
56 | yield return new TestCaseData(Vector3.one * 200, Vector3.one * 0.1f);
57 | yield return new TestCaseData(Vector3.one * 200, Vector3.one * 0.05f);
58 | yield return new TestCaseData(new Vector3(100, 50, 100), Vector3.one * 0.1f);
59 | yield return new TestCaseData(new Vector3(100, 50, 100), new Vector3(0.1f, 0.2f, 0.1f));
60 | }
61 |
62 | [Test]
63 | [TestCaseSource(nameof(UnpacksToSaveValueCases))]
64 | [Repeat(100)]
65 | public void UnpacksToSaveValue(Vector3 max, Vector3 precision)
66 | {
67 | var packer = new Vector3Packer(max, precision);
68 | var expected = new Vector3(
69 | Random.Range(-max.x, -max.x),
70 | Random.Range(-max.y, -max.y),
71 | Random.Range(-max.z, -max.z)
72 | );
73 |
74 | packer.Pack(writer, expected);
75 | var unpacked = packer.Unpack(GetReader());
76 |
77 | Assert.That(unpacked.x, Is.EqualTo(expected.x).Within(precision.x));
78 | Assert.That(unpacked.y, Is.EqualTo(expected.y).Within(precision.y));
79 | Assert.That(unpacked.z, Is.EqualTo(expected.z).Within(precision.z));
80 | }
81 |
82 | [Test]
83 | [TestCaseSource(nameof(UnpacksToSaveValueCases))]
84 | public void ZeroUnpacksAsZero(Vector3 max, Vector3 precision)
85 | {
86 | var packer = new Vector3Packer(max, precision);
87 | var zero = Vector3.zero;
88 |
89 | packer.Pack(writer, zero);
90 | var unpacked = packer.Unpack(GetReader());
91 |
92 | Assert.That(unpacked, Is.EqualTo(zero));
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/Packers/FloatPackerTests.cs:
--------------------------------------------------------------------------------
1 | using Mirage.Serialization;
2 | using NUnit.Framework;
3 | using Random = Mirage.Tests.BitPacking.TestRandom;
4 |
5 | namespace Mirage.Tests.Runtime.Serialization.Packers
6 | {
7 | [TestFixture(100, 0.1f, true)]
8 | [TestFixture(500, 0.02f, true)]
9 | [TestFixture(2000, 0.05f, true)]
10 | [TestFixture(1.5f, 0.01f, true)]
11 | [TestFixture(100_000, 30, true)]
12 |
13 | [TestFixture(100, 0.1f, false)]
14 | [TestFixture(500, 0.02f, false)]
15 | [TestFixture(2000, 0.05f, false)]
16 | [TestFixture(1.5f, 0.01f, false)]
17 | [TestFixture(100_000, 30, false)]
18 | public class FloatPackerTests : PackerTestBase
19 | {
20 | private readonly FloatPacker packer;
21 | private readonly float max;
22 | private readonly float min;
23 | private readonly float precsion;
24 | private readonly bool signed;
25 |
26 | public FloatPackerTests(float max, float precsion, bool signed)
27 | {
28 | this.max = max;
29 | min = signed ? -max : 0;
30 | this.precsion = precsion;
31 | this.signed = signed;
32 | packer = new FloatPacker(max, precsion, signed);
33 | }
34 |
35 | private float GetRandomFloat()
36 | {
37 | return Random.Range(min, max);
38 | }
39 |
40 |
41 | [Test]
42 | // takes about 1 seconds per 1000 values (including all fixtures)
43 | [Repeat(1000)]
44 | public void UnpackedValueIsWithinPrecision()
45 | {
46 | var start = GetRandomFloat();
47 | var packed = packer.Pack(start);
48 | var unpacked = packer.Unpack(packed);
49 |
50 | Assert.That(unpacked, Is.EqualTo(start).Within(precsion));
51 | }
52 |
53 | [Test]
54 | public void ValueOverMaxWillBeUnpackedAsMax()
55 | {
56 | var start = max * 1.2f;
57 | var packed = packer.Pack(start);
58 | var unpacked = packer.Unpack(packed);
59 |
60 | Assert.That(unpacked, Is.EqualTo(max).Within(precsion));
61 | }
62 |
63 | [Test]
64 | public void ValueUnderNegativeMaxWillBeUnpackedAsNegativeMax()
65 | {
66 | var start = max * -1.2f;
67 | var packed = packer.Pack(start);
68 | var unpacked = packer.Unpack(packed);
69 |
70 | Assert.That(unpacked, Is.EqualTo(min).Within(precsion));
71 | }
72 |
73 | [Test]
74 | public void ZeroUnpackToExactlyZero()
75 | {
76 | const float zero = 0;
77 | var packed = packer.Pack(zero);
78 | var unpacked = packer.Unpack(packed);
79 |
80 | Assert.That(unpacked, Is.EqualTo(zero));
81 | }
82 |
83 |
84 | [Test]
85 | [Repeat(100)]
86 | public void UnpackedValueIsWithinPrecisionUsingWriter()
87 | {
88 | var start = GetRandomFloat();
89 | packer.Pack(writer, start);
90 | var unpacked = packer.Unpack(GetReader());
91 |
92 | Assert.That(unpacked, Is.EqualTo(start).Within(precsion));
93 | }
94 |
95 | [Test]
96 | public void ValueOverMaxWillBeUnpackedUsingWriterAsMax()
97 | {
98 | var start = max * 1.2f;
99 | packer.Pack(writer, start);
100 | var unpacked = packer.Unpack(GetReader());
101 |
102 | Assert.That(unpacked, Is.EqualTo(max).Within(precsion));
103 | }
104 |
105 | [Test]
106 | public void ValueUnderNegativeMaxWillBeUnpackedUsingWriterAsNegativeMax()
107 | {
108 | var start = max * -1.2f;
109 | packer.Pack(writer, start);
110 | var unpacked = packer.Unpack(GetReader());
111 |
112 | Assert.That(unpacked, Is.EqualTo(min).Within(precsion));
113 | }
114 |
115 |
116 | [Test]
117 | [Repeat(100)]
118 | public void UnpackedValueIsWithinPrecisionNoClamp()
119 | {
120 | var start = GetRandomFloat();
121 | var packed = packer.PackNoClamp(start);
122 | var unpacked = packer.Unpack(packed);
123 |
124 | Assert.That(unpacked, Is.EqualTo(start).Within(precsion));
125 | }
126 |
127 | [Test]
128 | [Repeat(100)]
129 | public void UnpackedValueIsWithinPrecisionNoClampUsingWriter()
130 | {
131 | var start = GetRandomFloat();
132 | packer.PackNoClamp(writer, start);
133 | var unpacked = packer.Unpack(GetReader());
134 |
135 | Assert.That(unpacked, Is.EqualTo(start).Within(precsion));
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/BitPackingReusingWriterTests.cs:
--------------------------------------------------------------------------------
1 | using Mirage.Serialization;
2 | using NUnit.Framework;
3 |
4 | namespace Mirage.Tests.Runtime.Serialization
5 | {
6 | public class BitPackingResizeTest
7 | {
8 | private NetworkWriter writer;
9 | private NetworkReader reader;
10 |
11 | [SetUp]
12 | public void SetUp()
13 | {
14 | writer = new NetworkWriter(1300, true);
15 | reader = new NetworkReader();
16 | }
17 |
18 | [TearDown]
19 | public void TearDown()
20 | {
21 | // we have to clear these each time so that capactity doesn't effect other tests
22 | writer.Reset();
23 | writer = null;
24 | reader.Dispose();
25 | reader = null;
26 | }
27 |
28 | [Test]
29 | public void ResizesIfWritingOverCapacity()
30 | {
31 | var overCapacity = (1300 / 8) + 10;
32 | Assert.That(writer.ByteCapacity, Is.EqualTo(1304), "is first multiple of 8 over 1300");
33 | for (var i = 0; i < overCapacity; i++)
34 | {
35 | writer.WriteUInt64((ulong)i);
36 | }
37 |
38 | Assert.That(writer.ByteCapacity, Is.EqualTo(1304 * 2), "should double in size");
39 | }
40 |
41 |
42 | [Test]
43 | public void WillResizeMultipleTimes()
44 | {
45 | var overCapacity = ((1300 / 8) + 10) * 10; // 1720 * 8 = 13760 bytes
46 |
47 | Assert.That(writer.ByteCapacity, Is.EqualTo(1304), "is first multiple of 8 over 1300");
48 | for (var i = 0; i < overCapacity; i++)
49 | {
50 | writer.WriteUInt64((ulong)i);
51 | }
52 |
53 |
54 | Assert.That(writer.ByteCapacity, Is.EqualTo(20_864), "should double each time it goes over capacity");
55 | }
56 |
57 | [Test]
58 | public void ResizedArrayContainsAllData()
59 | {
60 | var overCapacity = (1300 / 8) + 10;
61 | for (var i = 0; i < overCapacity; i++)
62 | {
63 | writer.WriteUInt64((ulong)i);
64 | }
65 |
66 |
67 | var segment = writer.ToArraySegment();
68 | reader.Reset(segment);
69 | for (var i = 0; i < overCapacity; i++)
70 | {
71 | Assert.That(reader.ReadUInt64(), Is.EqualTo((ulong)i));
72 | }
73 | }
74 | }
75 | public class BitPackingReusingWriterTests
76 | {
77 | private NetworkWriter writer;
78 | private NetworkReader reader;
79 |
80 | [OneTimeSetUp]
81 | public void OneTimeSetUp()
82 | {
83 | writer = new NetworkWriter(1300);
84 | reader = new NetworkReader();
85 | }
86 |
87 | [TearDown]
88 | public void TearDown()
89 | {
90 | writer.Reset();
91 | reader.Dispose();
92 | }
93 |
94 |
95 | [Test]
96 | public void WriteUShortAfterReset()
97 | {
98 | ushort value1 = 0b0101;
99 | ushort value2 = 0x1000;
100 |
101 | // write first value
102 | writer.WriteUInt16(value1);
103 |
104 | reader.Reset(writer.ToArray());
105 | var out1 = reader.ReadUInt16();
106 | Assert.That(out1, Is.EqualTo(value1));
107 |
108 | // reset and write 2nd value
109 | writer.Reset();
110 |
111 | writer.WriteUInt16(value2);
112 |
113 | reader.Reset(writer.ToArray());
114 | var out2 = reader.ReadUInt16();
115 | Assert.That(out2, Is.EqualTo(value2), "Value 2 was incorrect");
116 | }
117 |
118 | [Test]
119 | [TestCase(0b0101ul, 0x1000ul)]
120 | [TestCase(0xffff_0000_ffff_fffful, 0x0000_ffff_1111_0000ul)]
121 | public void WriteULongAfterReset(ulong value1, ulong value2)
122 | {
123 | // write first value
124 | writer.WriteUInt64(value1);
125 |
126 | reader.Reset(writer.ToArray());
127 | var out1 = reader.ReadUInt64();
128 | Assert.That(out1, Is.EqualTo(value1));
129 |
130 | // reset and write 2nd value
131 | writer.Reset();
132 |
133 | writer.WriteUInt64(value2);
134 |
135 | reader.Reset(writer.ToArray());
136 | var out2 = reader.ReadUInt64();
137 | Assert.That(out2, Is.EqualTo(value2), "Value 2 was incorrect");
138 | }
139 |
140 | [Test]
141 | [TestCase(0b0101ul, 0x1000ul)]
142 | [TestCase(0xffff_0000_ffff_fffful, 0x0000_ffff_1111_0000ul)]
143 | public void WriteULongWriteBitsAfterReset(ulong value1, ulong value2)
144 | {
145 | // write first value
146 | writer.Write(value1, 64);
147 |
148 | reader.Reset(writer.ToArray());
149 | var out1 = reader.Read(64);
150 | Assert.That(out1, Is.EqualTo(value1));
151 |
152 | // reset and write 2nd value
153 | writer.Reset();
154 |
155 | writer.Write(value2, 64);
156 |
157 | reader.Reset(writer.ToArray());
158 | var out2 = reader.Read(64);
159 | Assert.That(out2, Is.EqualTo(value2), "Value 2 was incorrect");
160 | }
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/BitPackingCopyFromOtherTests.cs:
--------------------------------------------------------------------------------
1 | using Mirage.Serialization;
2 | using NUnit.Framework;
3 | using Random = Mirage.Tests.BitPacking.TestRandom;
4 |
5 | namespace Mirage.Tests.Runtime.Serialization
6 | {
7 | public class BitPackingCopyFromOtherTests
8 | {
9 | private NetworkWriter writer;
10 | private NetworkWriter otherWriter;
11 | private NetworkReader reader;
12 |
13 | [SetUp]
14 | public void Setup()
15 | {
16 | writer = new NetworkWriter(1300);
17 | otherWriter = new NetworkWriter(1300);
18 | reader = new NetworkReader();
19 | }
20 |
21 | [TearDown]
22 | public void TearDown()
23 | {
24 | writer.Reset();
25 | otherWriter.Reset();
26 | reader.Dispose();
27 | }
28 |
29 | [Test]
30 | public void CopyFromOtherWriterAligned()
31 | {
32 | otherWriter.Write(1, 8);
33 | otherWriter.Write(2, 8);
34 | otherWriter.Write(3, 8);
35 | otherWriter.Write(4, 8);
36 | otherWriter.Write(5, 8);
37 |
38 |
39 | writer.CopyFromWriter(otherWriter, 0, 5 * 8);
40 |
41 | var segment = writer.ToArraySegment();
42 | reader.Reset(segment);
43 |
44 | Assert.That(reader.Read(8), Is.EqualTo(1));
45 | Assert.That(reader.Read(8), Is.EqualTo(2));
46 | Assert.That(reader.Read(8), Is.EqualTo(3));
47 | Assert.That(reader.Read(8), Is.EqualTo(4));
48 | Assert.That(reader.Read(8), Is.EqualTo(5));
49 | }
50 |
51 | [Test]
52 | public void CopyFromOtherWriterUnAligned()
53 | {
54 | otherWriter.Write(1, 6);
55 | otherWriter.Write(2, 7);
56 | otherWriter.Write(3, 8);
57 | otherWriter.Write(4, 9);
58 | otherWriter.Write(5, 10);
59 |
60 | writer.Write(1, 3);
61 |
62 | writer.CopyFromWriter(otherWriter, 0, 40);
63 |
64 | var segment = writer.ToArraySegment();
65 | reader.Reset(segment);
66 |
67 | Assert.That(reader.Read(3), Is.EqualTo(1));
68 | Assert.That(reader.Read(6), Is.EqualTo(1));
69 | Assert.That(reader.Read(7), Is.EqualTo(2));
70 | Assert.That(reader.Read(8), Is.EqualTo(3));
71 | Assert.That(reader.Read(9), Is.EqualTo(4));
72 | Assert.That(reader.Read(10), Is.EqualTo(5));
73 | }
74 |
75 | [Test]
76 | [Repeat(100)]
77 | public void CopyFromOtherWriterUnAlignedBig()
78 | {
79 | var value1 = (ulong)Random.Range(0, 20000);
80 | var value2 = (ulong)Random.Range(0, 20000);
81 | var value3 = (ulong)Random.Range(0, 20000);
82 | var value4 = (ulong)Random.Range(0, 20000);
83 | var value5 = (ulong)Random.Range(0, 20000);
84 | otherWriter.Write(value1, 46);
85 | otherWriter.Write(value2, 47);
86 | otherWriter.Write(value3, 48);
87 | otherWriter.Write(value4, 49);
88 | otherWriter.Write(value5, 50);
89 |
90 | writer.WriteUInt64(5);
91 | writer.Write(1, 3);
92 | writer.WriteByte(171);
93 |
94 | writer.CopyFromWriter(otherWriter, 0, 240);
95 |
96 | var segment = writer.ToArraySegment();
97 | reader.Reset(segment);
98 |
99 | Assert.That(reader.ReadUInt64(), Is.EqualTo(5ul));
100 | Assert.That(reader.Read(3), Is.EqualTo(1));
101 | Assert.That(reader.ReadByte(), Is.EqualTo(171));
102 | Assert.That(reader.Read(46), Is.EqualTo(value1), "Random value 1 not correct");
103 | Assert.That(reader.Read(47), Is.EqualTo(value2), "Random value 2 not correct");
104 | Assert.That(reader.Read(48), Is.EqualTo(value3), "Random value 3 not correct");
105 | Assert.That(reader.Read(49), Is.EqualTo(value4), "Random value 4 not correct");
106 | Assert.That(reader.Read(50), Is.EqualTo(value5), "Random value 5 not correct");
107 | }
108 |
109 | [Test]
110 | [Repeat(100)]
111 | public void CopyFromOtherWriterUnAlignedBigOtherUnaligned()
112 | {
113 | for (var i = 0; i < 10; i++)
114 | {
115 | otherWriter.Write(12, 20);
116 | }
117 |
118 |
119 | var value1 = (ulong)Random.Range(0, 20000);
120 | var value2 = (ulong)Random.Range(0, 20000);
121 | var value3 = (ulong)Random.Range(0, 20000);
122 | var value4 = (ulong)Random.Range(0, 20000);
123 | var value5 = (ulong)Random.Range(0, 20000);
124 | otherWriter.Write(value1, 46);
125 | otherWriter.Write(value2, 47);
126 | otherWriter.Write(value3, 48);
127 | otherWriter.Write(value4, 49);
128 | otherWriter.Write(value5, 50);
129 |
130 | writer.WriteUInt64(5);
131 | writer.Write(1, 3);
132 | writer.WriteByte(171);
133 |
134 | writer.CopyFromWriter(otherWriter, 200, 240);
135 |
136 | var segment = writer.ToArraySegment();
137 | reader.Reset(segment);
138 |
139 | Assert.That(reader.ReadUInt64(), Is.EqualTo(5ul));
140 | Assert.That(reader.Read(3), Is.EqualTo(1));
141 | Assert.That(reader.ReadByte(), Is.EqualTo(171));
142 | Assert.That(reader.Read(46), Is.EqualTo(value1), "Random value 1 not correct");
143 | Assert.That(reader.Read(47), Is.EqualTo(value2), "Random value 2 not correct");
144 | Assert.That(reader.Read(48), Is.EqualTo(value3), "Random value 3 not correct");
145 | Assert.That(reader.Read(49), Is.EqualTo(value4), "Random value 4 not correct");
146 | Assert.That(reader.Read(50), Is.EqualTo(value5), "Random value 5 not correct");
147 | }
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/Packers/UintPackerCreateTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Mirage.Serialization;
3 | using NUnit.Framework;
4 |
5 | namespace Mirage.Tests.Runtime.Serialization.Packers
6 | {
7 | public class UintPackerCreateTest : PackerTestBase
8 | {
9 | [Test]
10 | [TestCase(0ul, 50ul, 1000ul, ExpectedResult = 6 + 1)]
11 | [TestCase(60ul, 50ul, 1000ul, ExpectedResult = 6 + 1, Description = "50 rounds up to 64, so v=60 is still small")]
12 | [TestCase(100ul, 50ul, 1000ul, ExpectedResult = 10 + 2)]
13 | [TestCase(100ul, 500ul, 100000ul, ExpectedResult = 9 + 1)]
14 | [TestCase(501ul, 500ul, 100000ul, ExpectedResult = 9 + 1)]
15 | [TestCase(5_000ul, 500ul, 100000ul, ExpectedResult = 17 + 2)]
16 | [TestCase(5_000_000ul, 500ul, 100000ul, ExpectedResult = 64 + 2)]
17 | public int CreatesUsing2Values(ulong inValue, ulong smallValue, ulong mediumValue)
18 | {
19 | var packer = new VarIntPacker(smallValue, mediumValue);
20 | packer.PackUlong(writer, inValue);
21 | return writer.BitPosition;
22 | }
23 |
24 |
25 | [Test]
26 | [TestCase(0ul, 50ul, 1000ul, 50_000ul, ExpectedResult = 6 + 1)]
27 | [TestCase(60ul, 50ul, 1000ul, 50_000ul, ExpectedResult = 6 + 1, Description = "50 rounds up to 64, so v=60 is still small")]
28 | [TestCase(100ul, 50ul, 1000ul, 50_000ul, ExpectedResult = 10 + 2)]
29 | [TestCase(5000ul, 50ul, 1000ul, 50_000ul, ExpectedResult = 16 + 2)]
30 | [TestCase(100ul, 500ul, 100_000ul, 50_000_000ul, ExpectedResult = 9 + 1)]
31 | [TestCase(501ul, 500ul, 100_000ul, 50_000_000ul, ExpectedResult = 9 + 1)]
32 | [TestCase(5_000ul, 500ul, 100_000ul, 50_000_000ul, ExpectedResult = 17 + 2)]
33 | [TestCase(5_000_000ul, 500ul, 100_000ul, 50_000_000ul, ExpectedResult = 26 + 2)]
34 | public int CreatesUsing3Values(ulong inValue, ulong smallValue, ulong mediumValue, ulong largeValue)
35 | {
36 | var packer = new VarIntPacker(smallValue, mediumValue, largeValue);
37 | packer.PackUlong(writer, inValue);
38 | return writer.BitPosition;
39 | }
40 |
41 |
42 | [Test]
43 | public void ThrowsIfSmallBitIsZero()
44 | {
45 | var exception = Assert.Throws(() =>
46 | {
47 | _ = VarIntPacker.FromBitCount(0, 10);
48 | });
49 | var expected = new ArgumentException("Small value can not be zero", "smallBits");
50 | Assert.That(exception, Has.Message.EqualTo(expected.Message));
51 | }
52 | [Test]
53 | public void ThrowsIfMediumLessThanSmall()
54 | {
55 | var exception = Assert.Throws(() =>
56 | {
57 | _ = VarIntPacker.FromBitCount(6, 5);
58 | });
59 | var expected = new ArgumentException("Medium value must be greater than small value", "mediumBits");
60 | Assert.That(exception, Has.Message.EqualTo(expected.Message));
61 | }
62 | [Test]
63 | public void ThrowsIfLargeLessThanMedium()
64 | {
65 | var exception = Assert.Throws(() =>
66 | {
67 | _ = VarIntPacker.FromBitCount(4, 10, 8);
68 | });
69 | var expected = new ArgumentException("Large value must be greater than medium value", "largeBits");
70 | Assert.That(exception, Has.Message.EqualTo(expected.Message));
71 | }
72 | [Test]
73 | public void ThrowsIfLargeIsOver64()
74 | {
75 | var exception = Assert.Throws(() =>
76 | {
77 | _ = VarIntPacker.FromBitCount(5, 10, 65);
78 | });
79 | var expected = new ArgumentException("Large bits must be 64 or less", "largeBits");
80 | Assert.That(exception, Has.Message.EqualTo(expected.Message));
81 | }
82 | [Test]
83 | public void ThrowsIfMediumIsOver62()
84 | {
85 | var exception = Assert.Throws(() =>
86 | {
87 | _ = VarIntPacker.FromBitCount(5, 63);
88 | });
89 | var expected = new ArgumentException("Medium bits must be 62 or less", "mediumBits");
90 | Assert.That(exception, Has.Message.EqualTo(expected.Message));
91 | }
92 |
93 | [Test]
94 | public void ThrowsWhenValueIsOverLargeValue()
95 | {
96 | var packer = VarIntPacker.FromBitCount(1, 2, 3, true);
97 | var exception1 = Assert.Throws(() =>
98 | {
99 | packer.PackUlong(writer, 20);
100 | });
101 | var exception2 = Assert.Throws(() =>
102 | {
103 | packer.PackUint(writer, 20);
104 | });
105 | var exception3 = Assert.Throws(() =>
106 | {
107 | packer.PackUlong(writer, 20);
108 | });
109 | var expected = new ArgumentOutOfRangeException("value", 20, $"Value is over max of {7}");
110 | Assert.That(exception1, Has.Message.EqualTo(expected.Message));
111 | Assert.That(exception2, Has.Message.EqualTo(expected.Message));
112 | Assert.That(exception3, Has.Message.EqualTo(expected.Message));
113 | }
114 |
115 | [Test]
116 | [TestCase(20ul, 3)]
117 | [TestCase(260ul, 8)]
118 | [TestCase(50_000ul, 10)]
119 | public void WritesMaxIfOverLargeValue(ulong inValue, int largeBits)
120 | {
121 | var max = BitMask.Mask(largeBits);
122 | var packer = VarIntPacker.FromBitCount(1, 2, largeBits, false);
123 | Assert.DoesNotThrow(() =>
124 | {
125 | packer.PackUlong(writer, inValue);
126 | packer.PackUint(writer, (uint)inValue);
127 | packer.PackUlong(writer, (ushort)inValue);
128 | });
129 | var reader = GetReader();
130 | var unpack1 = packer.UnpackUlong(reader);
131 | var unpack2 = packer.UnpackUint(reader);
132 | var unpack3 = packer.UnpackUshort(reader);
133 |
134 | Assert.That(unpack1, Is.EqualTo(max));
135 | Assert.That(unpack2, Is.EqualTo(max));
136 | Assert.That(unpack3, Is.EqualTo(max));
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/BitPackingTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 | using Mirage.Serialization;
4 | using NUnit.Framework;
5 |
6 | namespace Mirage.Tests.Runtime.Serialization
7 | {
8 | public class BitPackingTests
9 | {
10 | private NetworkWriter writer;
11 | private NetworkReader reader;
12 |
13 | [SetUp]
14 | public void Setup()
15 | {
16 | // dont allow resizing for this test, because we test throw
17 | writer = new NetworkWriter(1300, false);
18 | reader = new NetworkReader();
19 | }
20 |
21 | [TearDown]
22 | public void TearDown()
23 | {
24 | writer.Reset();
25 | reader.Dispose();
26 | }
27 |
28 | [Test]
29 | [TestCase(0ul)]
30 | [TestCase(1ul)]
31 | [TestCase(0x_FFFF_FFFF_12Ul)]
32 | public void WritesCorrectUlongValue(ulong value)
33 | {
34 | writer.Write(value, 64);
35 | reader.Reset(writer.ToArray());
36 |
37 | var result = reader.Read(64);
38 | Assert.That(result, Is.EqualTo(value));
39 | }
40 |
41 | [Test]
42 | [TestCase(0u)]
43 | [TestCase(1u)]
44 | [TestCase(0x_FFFF_FF12U)]
45 | public void WritesCorrectUIntValue(uint value)
46 | {
47 | writer.Write(value, 32);
48 | reader.Reset(writer.ToArray());
49 |
50 | var result = reader.Read(32);
51 | Assert.That(result, Is.EqualTo(value));
52 | }
53 |
54 | [Test]
55 | [TestCase(0u, 10, 2u, 5)]
56 | [TestCase(10u, 10, 36u, 15)]
57 | [TestCase(1u, 1, 250u, 8)]
58 | public void WritesCorrectValues(uint value1, int bits1, uint value2, int bits2)
59 | {
60 | writer.Write(value1, bits1);
61 | writer.Write(value2, bits2);
62 | reader.Reset(writer.ToArray());
63 |
64 | var result1 = reader.Read(bits1);
65 | var result2 = reader.Read(bits2);
66 | Assert.That(result1, Is.EqualTo(value1));
67 | Assert.That(result2, Is.EqualTo(value2));
68 | }
69 |
70 |
71 | [Test]
72 | public void CanWriteToBufferLimit()
73 | {
74 | for (var i = 0; i < 208; i++)
75 | {
76 | writer.Write((ulong)i, 50);
77 | }
78 |
79 | var result = writer.ToArray();
80 |
81 | // written bits/8
82 | var expectedLength = (208 * 50) / 8;
83 | Assert.That(result, Has.Length.EqualTo(expectedLength));
84 | }
85 |
86 | [Test, Description("Real buffer size is 1304 because 1300 rounds up")]
87 | public void WriterThrowIfWritesTooMuch()
88 | {
89 | // write 1296 up to last word
90 | for (var i = 0; i < 162; i++)
91 | {
92 | writer.Write((ulong)i, 64);
93 | }
94 |
95 | writer.Write(0, 63);
96 |
97 | Assert.DoesNotThrow(() =>
98 | {
99 | writer.Write(0, 1);
100 | });
101 |
102 | var exception = Assert.Throws(() =>
103 | {
104 | writer.Write(0, 1);
105 | });
106 | const int max = 162 * 64 + 64;
107 | Assert.That(exception, Has.Message.EqualTo($"Can not write over end of buffer, new length {max + 1}, capacity {max}"));
108 | }
109 |
110 | [Test]
111 | [Repeat(10)]
112 | public void WritesAllValueSizesCorrectly([Range(0, 63)] int startPosition, [Range(0, 64)] int valueBits)
113 | {
114 | var randomValue = ULongRandom.Next();
115 | writer.Write(0, startPosition);
116 |
117 | var maskedValue = randomValue & BitMask.Mask(valueBits);
118 |
119 | writer.Write(randomValue, valueBits);
120 | reader.Reset(writer.ToArray());
121 |
122 | _ = reader.Read(startPosition);
123 | var result = reader.Read(valueBits);
124 | Assert.That(result, Is.EqualTo(maskedValue));
125 | }
126 |
127 | [Test]
128 | public void WritesAllMasksCorrectly()
129 | {
130 | // we can't use [range] args because we have to skip cases where end is over 64
131 | var count = 0;
132 | for (var start = 0; start < 64; start++)
133 | {
134 | for (var bits = 0; bits < 64; bits++)
135 | {
136 | var end = start + bits;
137 | if (end > 64)
138 | {
139 | continue;
140 | }
141 |
142 | var expected = SlowMask(start, end);
143 | var actual = BitMask.OuterMask(start, end);
144 | count++;
145 | if (expected != actual)
146 | {
147 | Assert.Fail($"Failed, start:{start} bits:{bits}");
148 | }
149 | }
150 | }
151 |
152 | // define to allow debug.log to be skipped when running outside of unity
153 | #if !BIT_PACKING_NO_DEBUG
154 | UnityEngine.Debug.Log($"{count} masks tested");
155 | #endif
156 | }
157 |
158 | ///
159 | /// Slower but correct mask
160 | ///
161 | ///
162 | ///
163 | ///
164 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
165 | public static ulong SlowMask(int start, int end)
166 | {
167 | // old mask, doesn't work when bitposition before/after is multiple of 64
168 | // so we need to check if values == 0 before shifting masks
169 | var mask1 = start == 0 ? 0ul : (ulong.MaxValue >> (64 - start));
170 | // note: new position can not be 0, so no need to worry about
171 | var mask2 = (end & 0b11_1111) == 0 ? 0ul : (ulong.MaxValue << end /*we can use full position here as c# will mask it to just 6 bits*/);
172 | // mask either side of value, eg writing 4 bits at position 3: 111...111_1000_0111
173 | var mask = mask1 | mask2;
174 | return mask;
175 | }
176 | }
177 |
178 | public static class ULongRandom
179 | {
180 | private static Random rand;
181 | private static byte[] bytes;
182 |
183 | public static unsafe ulong Next()
184 | {
185 | if (rand == null)
186 | {
187 | rand = new System.Random();
188 | bytes = new byte[8];
189 | }
190 |
191 | rand.NextBytes(bytes);
192 | fixed (byte* ptr = &bytes[0])
193 | {
194 | return *(ulong*)ptr;
195 | }
196 | }
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/Packers/VariableIntPacker.cs:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License
3 |
4 | Copyright (c) 2021 James Frowen
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | */
24 |
25 | using System;
26 | using System.Runtime.CompilerServices;
27 |
28 | namespace Mirage.Serialization
29 | {
30 | public sealed class VarIntPacker
31 | {
32 | // todo needs doc comments
33 | // todo need attribute to validate large bits based on pack type (eg if packing ushort, make sure largebits is 16 or less)
34 |
35 | private readonly int _smallBitCount;
36 | private readonly int _mediumBitsCount;
37 | private readonly int _largeBitsCount;
38 | private readonly ulong _smallValue;
39 | private readonly ulong _mediumValue;
40 | private readonly ulong _largeValue;
41 | private readonly bool _throwIfOverLarge;
42 |
43 | public VarIntPacker(ulong smallValue, ulong mediumValue)
44 | : this(BitHelper.BitCount(smallValue), BitHelper.BitCount(mediumValue), 64, false) { }
45 | public VarIntPacker(ulong smallValue, ulong mediumValue, ulong largeValue, bool throwIfOverLarge = true)
46 | : this(BitHelper.BitCount(smallValue), BitHelper.BitCount(mediumValue), BitHelper.BitCount(largeValue), throwIfOverLarge) { }
47 |
48 | public static VarIntPacker FromBitCount(int smallBits, int mediumBits)
49 | => FromBitCount(smallBits, mediumBits, 64, false);
50 | public static VarIntPacker FromBitCount(int smallBits, int mediumBits, int largeBits, bool throwIfOverLarge = true)
51 | => new VarIntPacker(smallBits, mediumBits, largeBits, throwIfOverLarge);
52 |
53 | private VarIntPacker(int smallBits, int mediumBits, int largeBits, bool throwIfOverLarge)
54 | {
55 | _throwIfOverLarge = throwIfOverLarge;
56 | if (smallBits == 0) throw new ArgumentException("Small value can not be zero", nameof(smallBits));
57 | if (smallBits >= mediumBits) throw new ArgumentException("Medium value must be greater than small value", nameof(mediumBits));
58 | if (mediumBits >= largeBits) throw new ArgumentException("Large value must be greater than medium value", nameof(largeBits));
59 | if (largeBits > 64) throw new ArgumentException("Large bits must be 64 or less", nameof(largeBits));
60 | // force medium to also be 62 or less so we can use 1 write call (2 bits to say its medium + 62 value bits
61 | if (mediumBits > 62) throw new ArgumentException("Medium bits must be 62 or less", nameof(mediumBits));
62 |
63 | _smallBitCount = smallBits;
64 | _mediumBitsCount = mediumBits;
65 | _largeBitsCount = largeBits;
66 |
67 | // mask is also max value for n bits
68 | _smallValue = BitMask.Mask(smallBits);
69 | _mediumValue = BitMask.Mask(mediumBits);
70 | _largeValue = BitMask.Mask(largeBits);
71 | }
72 |
73 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
74 | public void PackUlong(NetworkWriter writer, ulong value)
75 | {
76 | pack(writer, value, 64);
77 | }
78 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
79 | public void PackUint(NetworkWriter writer, uint value)
80 | {
81 | pack(writer, value, 32);
82 | }
83 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
84 | public void PackUshort(NetworkWriter writer, ushort value)
85 | {
86 | pack(writer, value, 16);
87 | }
88 |
89 | private void pack(NetworkWriter writer, ulong value, int maxBits)
90 | {
91 | if (value <= _smallValue)
92 | {
93 | // start with b0 to say small, then value
94 | writer.Write(value << 1, _smallBitCount + 1);
95 | }
96 | else if (value <= _mediumValue)
97 | {
98 | // start with b01 to say medium, then value
99 | writer.Write((value << 2) | 0b01, _mediumBitsCount + 2);
100 | }
101 | else if (value <= _largeValue)
102 | {
103 | // start with b11 to say large, then value
104 | // use 2 write calls here because bitCount could be 64
105 | writer.Write(0b11, 2);
106 | writer.Write(value, Math.Min(maxBits, _largeBitsCount));
107 | }
108 | else
109 | {
110 | if (_throwIfOverLarge)
111 | {
112 | throw new ArgumentOutOfRangeException(nameof(value), value, $"Value is over max of {_largeValue}");
113 | }
114 | else
115 | {
116 | // if no throw write MaxValue
117 | // we dont want to write value here because it will be masked and lose some high bits
118 | // need 2 write calls here because max is 64+2 bits
119 | writer.Write(0b11, 2);
120 | writer.Write(ulong.MaxValue, Math.Min(maxBits, _largeBitsCount));
121 | }
122 | }
123 | }
124 |
125 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
126 | public ulong UnpackUlong(NetworkReader reader)
127 | {
128 | return unpack(reader, 64);
129 | }
130 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
131 | public uint UnpackUint(NetworkReader reader)
132 | {
133 | return (uint)unpack(reader, 32);
134 | }
135 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
136 | public ushort UnpackUshort(NetworkReader reader)
137 | {
138 | return (ushort)unpack(reader, 16);
139 | }
140 |
141 | private ulong unpack(NetworkReader reader, int maxBits)
142 | {
143 | if (!reader.ReadBoolean())
144 | {
145 | return reader.Read(_smallBitCount);
146 | }
147 | else
148 | {
149 | if (!reader.ReadBoolean())
150 | {
151 | return reader.Read(_mediumBitsCount);
152 | }
153 | else
154 | {
155 | return reader.Read(Math.Min(_largeBitsCount, maxBits));
156 | }
157 | }
158 | }
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.csproj.meta
2 | *.sln.meta
3 | JamesFrowen.BitPacking.meta
4 | JamesFrowen.BitPacking.Tests.meta
5 | JamesFrowen.BitPacking/Source.meta
6 | JamesFrowen.BitPacking.Tests/Tests.meta
7 | README.md.meta
8 | LICENSE.meta
9 |
10 | # Created by https://www.toptal.com/developers/gitignore/api/dotnetcore,vs
11 | # Edit at https://www.toptal.com/developers/gitignore?templates=dotnetcore,vs
12 |
13 | ### DotnetCore ###
14 | # .NET Core build folders
15 | bin/
16 | obj/
17 |
18 | # Common node modules locations
19 | /node_modules
20 | /wwwroot/node_modules
21 |
22 | ### vs ###
23 | ## Ignore Visual Studio temporary files, build results, and
24 | ## files generated by popular Visual Studio add-ons.
25 | ##
26 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
27 |
28 | # User-specific files
29 | *.rsuser
30 | *.suo
31 | *.user
32 | *.userosscache
33 | *.sln.docstates
34 |
35 | # User-specific files (MonoDevelop/Xamarin Studio)
36 | *.userprefs
37 |
38 | # Mono auto generated files
39 | mono_crash.*
40 |
41 | # Build results
42 | [Dd]ebug/
43 | [Dd]ebugPublic/
44 | [Rr]elease/
45 | [Rr]eleases/
46 | x64/
47 | x86/
48 | [Aa][Rr][Mm]/
49 | [Aa][Rr][Mm]64/
50 | bld/
51 | [Bb]in/
52 | [Oo]bj/
53 | [Ll]og/
54 | [Ll]ogs/
55 |
56 | # Visual Studio 2015/2017 cache/options directory
57 | .vs/
58 | # Uncomment if you have tasks that create the project's static files in wwwroot
59 | #wwwroot/
60 |
61 | # Visual Studio 2017 auto generated files
62 | Generated\ Files/
63 |
64 | # MSTest test Results
65 | [Tt]est[Rr]esult*/
66 | [Bb]uild[Ll]og.*
67 |
68 | # NUnit
69 | *.VisualState.xml
70 | TestResult.xml
71 | nunit-*.xml
72 |
73 | # Build Results of an ATL Project
74 | [Dd]ebugPS/
75 | [Rr]eleasePS/
76 | dlldata.c
77 |
78 | # Benchmark Results
79 | BenchmarkDotNet.Artifacts/
80 |
81 | # .NET Core
82 | project.lock.json
83 | project.fragment.lock.json
84 | artifacts/
85 |
86 | # StyleCop
87 | StyleCopReport.xml
88 |
89 | # Files built by Visual Studio
90 | *_i.c
91 | *_p.c
92 | *_h.h
93 | *.ilk
94 | *.obj
95 | *.iobj
96 | *.pch
97 | *.pdb
98 | *.ipdb
99 | *.pgc
100 | *.pgd
101 | *.rsp
102 | *.sbr
103 | *.tlb
104 | *.tli
105 | *.tlh
106 | *.tmp
107 | *.tmp_proj
108 | *_wpftmp.csproj
109 | *.log
110 | *.vspscc
111 | *.vssscc
112 | .builds
113 | *.pidb
114 | *.svclog
115 | *.scc
116 |
117 | # Chutzpah Test files
118 | _Chutzpah*
119 |
120 | # Visual C++ cache files
121 | ipch/
122 | *.aps
123 | *.ncb
124 | *.opendb
125 | *.opensdf
126 | *.sdf
127 | *.cachefile
128 | *.VC.db
129 | *.VC.VC.opendb
130 |
131 | # Visual Studio profiler
132 | *.psess
133 | *.vsp
134 | *.vspx
135 | *.sap
136 |
137 | # Visual Studio Trace Files
138 | *.e2e
139 |
140 | # TFS 2012 Local Workspace
141 | $tf/
142 |
143 | # Guidance Automation Toolkit
144 | *.gpState
145 |
146 | # ReSharper is a .NET coding add-in
147 | _ReSharper*/
148 | *.[Rr]e[Ss]harper
149 | *.DotSettings.user
150 |
151 | # TeamCity is a build add-in
152 | _TeamCity*
153 |
154 | # DotCover is a Code Coverage Tool
155 | *.dotCover
156 |
157 | # AxoCover is a Code Coverage Tool
158 | .axoCover/*
159 | !.axoCover/settings.json
160 |
161 | # Coverlet is a free, cross platform Code Coverage Tool
162 | coverage*[.json, .xml, .info]
163 |
164 | # Visual Studio code coverage results
165 | *.coverage
166 | *.coveragexml
167 |
168 | # NCrunch
169 | _NCrunch_*
170 | .*crunch*.local.xml
171 | nCrunchTemp_*
172 |
173 | # MightyMoose
174 | *.mm.*
175 | AutoTest.Net/
176 |
177 | # Web workbench (sass)
178 | .sass-cache/
179 |
180 | # Installshield output folder
181 | [Ee]xpress/
182 |
183 | # DocProject is a documentation generator add-in
184 | DocProject/buildhelp/
185 | DocProject/Help/*.HxT
186 | DocProject/Help/*.HxC
187 | DocProject/Help/*.hhc
188 | DocProject/Help/*.hhk
189 | DocProject/Help/*.hhp
190 | DocProject/Help/Html2
191 | DocProject/Help/html
192 |
193 | # Click-Once directory
194 | publish/
195 |
196 | # Publish Web Output
197 | *.[Pp]ublish.xml
198 | *.azurePubxml
199 | # Note: Comment the next line if you want to checkin your web deploy settings,
200 | # but database connection strings (with potential passwords) will be unencrypted
201 | *.pubxml
202 | *.publishproj
203 |
204 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
205 | # checkin your Azure Web App publish settings, but sensitive information contained
206 | # in these scripts will be unencrypted
207 | PublishScripts/
208 |
209 | # NuGet Packages
210 | *.nupkg
211 | # NuGet Symbol Packages
212 | *.snupkg
213 | # The packages folder can be ignored because of Package Restore
214 | **/[Pp]ackages/*
215 | # except build/, which is used as an MSBuild target.
216 | !**/[Pp]ackages/build/
217 | # Uncomment if necessary however generally it will be regenerated when needed
218 | #!**/[Pp]ackages/repositories.config
219 | # NuGet v3's project.json files produces more ignorable files
220 | *.nuget.props
221 | *.nuget.targets
222 |
223 | # Microsoft Azure Build Output
224 | csx/
225 | *.build.csdef
226 |
227 | # Microsoft Azure Emulator
228 | ecf/
229 | rcf/
230 |
231 | # Windows Store app package directories and files
232 | AppPackages/
233 | BundleArtifacts/
234 | Package.StoreAssociation.xml
235 | _pkginfo.txt
236 | *.appx
237 | *.appxbundle
238 | *.appxupload
239 |
240 | # Visual Studio cache files
241 | # files ending in .cache can be ignored
242 | *.[Cc]ache
243 | # but keep track of directories ending in .cache
244 | !?*.[Cc]ache/
245 |
246 | # Others
247 | ClientBin/
248 | ~$*
249 | *~
250 | *.dbmdl
251 | *.dbproj.schemaview
252 | *.jfm
253 | *.pfx
254 | *.publishsettings
255 | orleans.codegen.cs
256 |
257 | # Including strong name files can present a security risk
258 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
259 | #*.snk
260 |
261 | # Since there are multiple workflows, uncomment next line to ignore bower_components
262 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
263 | #bower_components/
264 |
265 | # RIA/Silverlight projects
266 | Generated_Code/
267 |
268 | # Backup & report files from converting an old project file
269 | # to a newer Visual Studio version. Backup files are not needed,
270 | # because we have git ;-)
271 | _UpgradeReport_Files/
272 | Backup*/
273 | UpgradeLog*.XML
274 | UpgradeLog*.htm
275 | ServiceFabricBackup/
276 | *.rptproj.bak
277 |
278 | # SQL Server files
279 | *.mdf
280 | *.ldf
281 | *.ndf
282 |
283 | # Business Intelligence projects
284 | *.rdl.data
285 | *.bim.layout
286 | *.bim_*.settings
287 | *.rptproj.rsuser
288 | *- [Bb]ackup.rdl
289 | *- [Bb]ackup ([0-9]).rdl
290 | *- [Bb]ackup ([0-9][0-9]).rdl
291 |
292 | # Microsoft Fakes
293 | FakesAssemblies/
294 |
295 | # GhostDoc plugin setting file
296 | *.GhostDoc.xml
297 |
298 | # Node.js Tools for Visual Studio
299 | .ntvs_analysis.dat
300 | node_modules/
301 |
302 | # Visual Studio 6 build log
303 | *.plg
304 |
305 | # Visual Studio 6 workspace options file
306 | *.opt
307 |
308 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
309 | *.vbw
310 |
311 | # Visual Studio LightSwitch build output
312 | **/*.HTMLClient/GeneratedArtifacts
313 | **/*.DesktopClient/GeneratedArtifacts
314 | **/*.DesktopClient/ModelManifest.xml
315 | **/*.Server/GeneratedArtifacts
316 | **/*.Server/ModelManifest.xml
317 | _Pvt_Extensions
318 |
319 | # Paket dependency manager
320 | .paket/paket.exe
321 | paket-files/
322 |
323 | # FAKE - F# Make
324 | .fake/
325 |
326 | # CodeRush personal settings
327 | .cr/personal
328 |
329 | # Python Tools for Visual Studio (PTVS)
330 | __pycache__/
331 | *.pyc
332 |
333 | # Cake - Uncomment if you are using it
334 | # tools/**
335 | # !tools/packages.config
336 |
337 | # Tabs Studio
338 | *.tss
339 |
340 | # Telerik's JustMock configuration file
341 | *.jmconfig
342 |
343 | # BizTalk build output
344 | *.btp.cs
345 | *.btm.cs
346 | *.odx.cs
347 | *.xsd.cs
348 |
349 | # OpenCover UI analysis results
350 | OpenCover/
351 |
352 | # Azure Stream Analytics local run output
353 | ASALocalRun/
354 |
355 | # MSBuild Binary and Structured Log
356 | *.binlog
357 |
358 | # NVidia Nsight GPU debugger configuration file
359 | *.nvuser
360 |
361 | # MFractors (Xamarin productivity tool) working folder
362 | .mfractor/
363 |
364 | # Local History for Visual Studio
365 | .localhistory/
366 |
367 | # BeatPulse healthcheck temp database
368 | healthchecksdb
369 |
370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
371 | MigrationBackup/
372 |
373 | # Ionide (cross platform F# VS Code tools) working folder
374 | .ionide/
375 |
376 | # End of https://www.toptal.com/developers/gitignore/api/dotnetcore,vs
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/Packers/FloatPacker.cs:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License
3 |
4 | Copyright (c) 2021 James Frowen
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | */
24 |
25 | using System;
26 | using System.Runtime.CompilerServices;
27 | using UnityEngine;
28 |
29 | namespace Mirage.Serialization
30 | {
31 | ///
32 | /// Helps compresses a float into a reduced number of bits
33 | ///
34 | public sealed class FloatPacker
35 | {
36 | private readonly int _bitCount;
37 | private readonly float _multiplier_pack;
38 | private readonly float _multiplier_unpack;
39 | private readonly uint _mask;
40 | private readonly int _toNegative;
41 |
42 | /// max positive value, any uint value over this will be negative
43 | private readonly uint _midPoint;
44 | private readonly float _positiveMax;
45 | private readonly float _negativeMax;
46 |
47 | /// lowest precision, actual precision will be caculated from number of bits used
48 | public FloatPacker(float max, float lowestPrecision) : this(max, lowestPrecision, true) { }
49 |
50 | public FloatPacker(float max, int bitCount) : this(max, bitCount, true) { }
51 |
52 | ///
53 | /// lowest precision, actual precision will be caculated from number of bits used
54 | /// if negative values will be allowed or not
55 | public FloatPacker(float max, float lowestPrecision, bool signed) : this(max, BitHelper.BitCount(max, lowestPrecision, signed), signed) { }
56 |
57 | /// if negative values will be allowed or not
58 | public FloatPacker(float max, int bitCount, bool signed)
59 | {
60 | _bitCount = bitCount;
61 | // not sure what max bit count should be,
62 | // but 30 seems reasonable since an unpacked float is already 32
63 | if (max == 0) throw new ArgumentException("Max can not be 0", nameof(max));
64 | if (bitCount < 1) throw new ArgumentException("Bit count is too low, bit count should be between 1 and 30", nameof(bitCount));
65 | if (bitCount > 30) throw new ArgumentException("Bit count is too high, bit count should be between 1 and 30", nameof(bitCount));
66 |
67 | _mask = (1u << bitCount) - 1u;
68 |
69 | if (signed)
70 | {
71 | _midPoint = (1u << (bitCount - 1)) - 1u;
72 | _toNegative = (int)(_mask + 1u);
73 |
74 | _positiveMax = max;
75 | _negativeMax = -max;
76 | }
77 | else // unsigned
78 | {
79 | _midPoint = (1u << (bitCount)) - 1u;
80 | _toNegative = 0;
81 |
82 | _positiveMax = max;
83 | _negativeMax = 0;
84 | }
85 |
86 | _multiplier_pack = _midPoint / max;
87 | _multiplier_unpack = 1 / _multiplier_pack;
88 | }
89 |
90 |
91 | ///
92 | /// Packs a float value into a uint
93 | /// Clamps the value within min/max range
94 | ///
95 | ///
96 | ///
97 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
98 | public uint Pack(float value)
99 | {
100 | if (value >= _positiveMax) value = _positiveMax;
101 | if (value <= _negativeMax) value = _negativeMax;
102 | return PackNoClamp(value);
103 | }
104 |
105 | ///
106 | /// Packs and Writes a float value
107 | /// Clamps the value within min/max range
108 | ///
109 | ///
110 | ///
111 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
112 | public void Pack(NetworkWriter writer, float value)
113 | {
114 | if (value >= _positiveMax) value = _positiveMax;
115 | if (value <= _negativeMax) value = _negativeMax;
116 | PackNoClamp(writer, value);
117 | }
118 |
119 | ///
120 | /// Packs a float value into a uint without clamping it in range
121 | ///
122 | /// WARNING: only use this method if value is always in range. Out of range values may not be unpacked correctly
123 | ///
124 | ///
125 | ///
126 | ///
127 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
128 | public uint PackNoClamp(float value)
129 | {
130 | return (uint)Mathf.RoundToInt(value * _multiplier_pack) & _mask;
131 | }
132 |
133 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
134 | public void PackNoClamp(NetworkWriter writer, float value)
135 | {
136 | // dont need to mask value here because the Write function will mask it
137 | writer.Write((uint)Mathf.RoundToInt(value * _multiplier_pack), _bitCount);
138 | }
139 |
140 |
141 | ///
142 | /// Unpacks uint value to float
143 | ///
144 | ///
145 | ///
146 | ///
147 | /// Positive and Negative values need to be unpacked differnely so that they both keep same precision
148 | ///
149 | /// Example 10 bits (max uint = 1023):
150 | ///
151 | /// p = precision
152 | ///
153 | /// Positive values have uint range 0 to 511
154 | ///
155 | /// Unpacked: 0 to max
156 | ///
157 | ///
158 | /// Negative values have uint range 512 to 1023
159 | ///
160 | /// Unpacked using same as positive: max+p to max*2+p
161 | ///
162 | /// but we want range -max to -p
163 | ///
164 | /// so we need to subtrack -1024 so range is -512 to -1
165 | ///
166 | /// then scale to unpack to -max to -p
167 | ///
168 | ///
169 | ///
170 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
171 | public float Unpack(uint value)
172 | {
173 | if (value <= _midPoint) // positive
174 | {
175 | // 0 -> 511
176 | // 0 -> (max)
177 | return value * _multiplier_unpack;
178 | }
179 | else // negative
180 | {
181 | // max = 1024
182 | // 512 -> 1023
183 | // -max -> 0
184 |
185 | // doing `value - max*2` cause:
186 | // -512 -> -1
187 | return ((int)value - _toNegative) * _multiplier_unpack;
188 | }
189 | }
190 |
191 | ///
192 | /// Reads and unpacks float value
193 | ///
194 | ///
195 | ///
196 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
197 | public float Unpack(NetworkReader reader)
198 | {
199 | return Unpack((uint)reader.Read(_bitCount));
200 | }
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/Packers/QuaternionPacker.cs:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License
3 |
4 | Copyright (c) 2021 James Frowen
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | */
24 |
25 | using System;
26 | using System.Runtime.CompilerServices;
27 | using UnityEngine;
28 |
29 | namespace Mirage.Serialization
30 | {
31 | public sealed class QuaternionPacker
32 | {
33 | /// Default packer using 9 bits per element, 29 bits total
34 | public static readonly QuaternionPacker Default9 = new QuaternionPacker(9);
35 | /// Default packer using 10 bits per element, 32 bits total
36 | public static readonly QuaternionPacker Default10 = new QuaternionPacker(10);
37 |
38 | public static uint PackAsInt(Quaternion value)
39 | {
40 | return (uint)Default10.Pack(value);
41 | }
42 | public static Quaternion UnpackFromInt(uint value)
43 | {
44 | return Default10.Unpack(value);
45 | }
46 |
47 | ///
48 | /// 1 / sqrt(2)
49 | ///
50 | private const float MAX_VALUE = 1f / 1.414214f;
51 |
52 | ///
53 | /// bit count per element writen
54 | ///
55 | private readonly int _bitCountPerElement;
56 |
57 | ///
58 | /// total bit count for Quaternion
59 | ///
60 | /// count = 3 * perElement + 2;
61 | ///
62 | ///
63 | private readonly int _totalBitCount;
64 | private readonly uint _readMask;
65 | private readonly FloatPacker _floatPacker;
66 |
67 | /// 10 per "smallest 3" is good enough for most people
68 | public QuaternionPacker(int quaternionBitLength = 10)
69 | {
70 | // (this.BitLength - 1) because pack sign by itself
71 | _bitCountPerElement = quaternionBitLength;
72 | _totalBitCount = 2 + (quaternionBitLength * 3);
73 | _floatPacker = new FloatPacker(MAX_VALUE, quaternionBitLength);
74 | _readMask = (uint)BitMask.Mask(_bitCountPerElement);
75 | }
76 |
77 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
78 | public void Pack(NetworkWriter writer, Quaternion value)
79 | {
80 | writer.Write(Pack(value), _totalBitCount);
81 | }
82 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
83 | public ulong Pack(Quaternion value)
84 | {
85 | QuickNormalize(ref value);
86 |
87 | FindLargestIndex(ref value, out var index);
88 |
89 | GetSmallerDimensions(index, ref value, out var a, out var b, out var c);
90 |
91 | // largest needs to be positive to be calculated by reader
92 | // if largest is negative flip sign of others because Q = -Q
93 | if (value[(int)index] < 0)
94 | {
95 | a = -a;
96 | b = -b;
97 | c = -c;
98 | }
99 |
100 | // todo, should we be rounding down for abc? because if they are rounded up their sum may be greater than largest
101 |
102 | ulong combine = 0;
103 | // write Index as (3-i) so that Quaternion.identity will be all zeros
104 | combine |= (ulong)(3 - index) << (_bitCountPerElement * 3);
105 | combine |= (ulong)_floatPacker.PackNoClamp(a) << (_bitCountPerElement * 2);
106 | combine |= (ulong)_floatPacker.PackNoClamp(b) << _bitCountPerElement;
107 | combine |= _floatPacker.PackNoClamp(c);
108 | return combine;
109 | }
110 |
111 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
112 | internal static void QuickNormalize(ref Quaternion quaternion)
113 | {
114 | var dot =
115 | (quaternion.x * quaternion.x) +
116 | (quaternion.y * quaternion.y) +
117 | (quaternion.z * quaternion.z) +
118 | (quaternion.w * quaternion.w);
119 |
120 | const float allowedEpsilon = 1E-5f;
121 | const float minAllowed = 1 - allowedEpsilon;
122 | const float maxAllowed = 1 + allowedEpsilon;
123 | // only normalize if dot product is outside allowed range
124 | if (minAllowed > dot || maxAllowed < dot)
125 | {
126 | var dotSqrt = (float)Math.Sqrt(dot);
127 | // rotation is 0
128 | if (dotSqrt < allowedEpsilon)
129 | {
130 | // identity
131 | quaternion.x = 0;
132 | quaternion.y = 0;
133 | quaternion.z = 0;
134 | quaternion.w = 1;
135 | }
136 | else
137 | {
138 | var iDotSqrt = 1 / dotSqrt;
139 | quaternion.x *= iDotSqrt;
140 | quaternion.y *= iDotSqrt;
141 | quaternion.z *= iDotSqrt;
142 | quaternion.w *= iDotSqrt;
143 | }
144 | }
145 | }
146 |
147 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
148 | internal static void FindLargestIndex(ref Quaternion quaternion, out uint index)
149 | {
150 | var x2 = quaternion.x * quaternion.x;
151 | var y2 = quaternion.y * quaternion.y;
152 | var z2 = quaternion.z * quaternion.z;
153 | var w2 = quaternion.w * quaternion.w;
154 |
155 | index = 0;
156 | var current = x2;
157 | // check vs sq to avoid doing mathf.abs
158 | if (y2 > current)
159 | {
160 | index = 1;
161 | current = y2;
162 | }
163 | if (z2 > current)
164 | {
165 | index = 2;
166 | current = z2;
167 | }
168 | if (w2 > current)
169 | {
170 | index = 3;
171 | }
172 | }
173 |
174 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
175 | private static void GetSmallerDimensions(uint largestIndex, ref Quaternion quaternion, out float a, out float b, out float c)
176 | {
177 | switch (largestIndex)
178 | {
179 | case 0:
180 | a = quaternion.y;
181 | b = quaternion.z;
182 | c = quaternion.w;
183 | return;
184 | case 1:
185 | a = quaternion.x;
186 | b = quaternion.z;
187 | c = quaternion.w;
188 | return;
189 | case 2:
190 | a = quaternion.x;
191 | b = quaternion.y;
192 | c = quaternion.w;
193 | return;
194 | case 3:
195 | a = quaternion.x;
196 | b = quaternion.y;
197 | c = quaternion.z;
198 | return;
199 | default:
200 | ThrowIfOutOfRange();
201 | a = b = c = default;
202 | return;
203 | }
204 | }
205 |
206 | private static void ThrowIfOutOfRange() => throw new IndexOutOfRangeException("Invalid Quaternion index!");
207 |
208 |
209 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
210 | public Quaternion Unpack(NetworkReader reader)
211 | {
212 | return Unpack(reader.Read(_totalBitCount));
213 | }
214 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
215 | public Quaternion Unpack(ulong combine)
216 | {
217 | // Index writen as (3-i)
218 | // so (3-c) to decode
219 | var index = 3 - (uint)(combine >> (_bitCountPerElement * 3));
220 |
221 | var a = _floatPacker.Unpack((uint)(combine >> (_bitCountPerElement * 2)) & _readMask);
222 | var b = _floatPacker.Unpack((uint)(combine >> (_bitCountPerElement * 1)) & _readMask);
223 | var c = _floatPacker.Unpack((uint)combine & _readMask);
224 |
225 | var l2 = 1 - ((a * a) + (b * b) + (c * c));
226 | var largest = (float)Math.Sqrt(l2);
227 | // this Quaternion should already be normallized because of the way that largest is calculated
228 | switch (index)
229 | {
230 | case 0:
231 | return new Quaternion(largest, a, b, c);
232 | case 1:
233 | return new Quaternion(a, largest, b, c);
234 | case 2:
235 | return new Quaternion(a, b, largest, c);
236 | case 3:
237 | return new Quaternion(a, b, c, largest);
238 | default:
239 | ThrowIfOutOfRange();
240 | return default;
241 | }
242 | }
243 | }
244 | }
245 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking.Tests/Tests/Packers/QuaternionPackerTests.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using Mirage.Serialization;
4 | using NUnit.Framework;
5 | using NUnit.Framework.Constraints;
6 | using UnityEngine;
7 | using Random = Mirage.Tests.BitPacking.TestRandom;
8 | using Range = NUnit.Framework.RangeAttribute;
9 |
10 | namespace Mirage.Tests.Runtime.Serialization.Packers
11 | {
12 | public class QuaternionPackerTests : PackerTestBase
13 | {
14 | private static Quaternion GetRandomQuaternion()
15 | {
16 | return GetRandomQuaternionNotNormalized().normalized;
17 | }
18 |
19 | private static Quaternion GetRandomQuaternionNotNormalized()
20 | {
21 | return new Quaternion(
22 | Random.Range(-1, 1),
23 | Random.Range(-1, 1),
24 | Random.Range(-1, 1),
25 | Random.Range(-1, 1)
26 | );
27 | }
28 |
29 |
30 | [Test]
31 | public void IdentityIsUnpackedAsIdentity()
32 | {
33 | var packer = new QuaternionPacker(10);
34 | packer.Pack(writer, Quaternion.identity);
35 | var unpack = packer.Unpack(GetReader());
36 |
37 | Assert.That(unpack, Is.EqualTo(Quaternion.identity));
38 | }
39 |
40 | private static IEnumerable ReturnsCorrectIndexCases()
41 | {
42 | var values = new List() { 0.1f, 0.2f, 0.3f, 0.4f };
43 | // abcd are index
44 | // testing all permutation, index can only be used once
45 | for (var a = 0; a < 4; a++)
46 | {
47 | for (var b = 0; b < 4; b++)
48 | {
49 | if (b == a) { continue; }
50 |
51 | for (var c = 0; c < 4; c++)
52 | {
53 | if (c == a || c == b) { continue; }
54 |
55 | for (var d = 0; d < 4; d++)
56 | {
57 | if (d == a || d == b || d == c) { continue; }
58 |
59 | uint largest = 0;
60 | // index 3 is the largest,
61 | if (a == 3) { largest = 0; }
62 | if (b == 3) { largest = 1; }
63 | if (c == 3) { largest = 2; }
64 | if (d == 3) { largest = 3; }
65 | yield return new TestCaseData(new Quaternion(values[a], values[b], values[c], values[d]).normalized)
66 | .Returns(largest);
67 | }
68 | }
69 | }
70 | }
71 | }
72 |
73 | [Test]
74 | [TestCaseSource(nameof(ReturnsCorrectIndexCases))]
75 | public uint ReturnsCorrectIndex(Quaternion q)
76 | {
77 | QuaternionPacker.FindLargestIndex(ref q, out var index);
78 | return index;
79 | }
80 |
81 | private static IEnumerable PackAndUnpackCases()
82 | {
83 | for (var i = 8; i < 12; i++)
84 | {
85 | yield return new TestCaseData(i, Quaternion.identity);
86 | yield return new TestCaseData(i, Quaternion.Euler(25, 30, 0));
87 | yield return new TestCaseData(i, Quaternion.Euler(-50, 30, 90));
88 | yield return new TestCaseData(i, Quaternion.Euler(90, 90, 180));
89 | yield return new TestCaseData(i, Quaternion.Euler(-20, 0, 45));
90 | yield return new TestCaseData(i, Quaternion.Euler(80, 30, -45));
91 | }
92 | }
93 |
94 | [Test]
95 | [TestCaseSource(nameof(PackAndUnpackCases))]
96 | #if !UNITY_EDITOR
97 | [Ignore("Quaternion.Euler Requires unity engine to run")]
98 | #endif
99 | public void PackAndUnpack(int bits, Quaternion inValue)
100 | {
101 | RunPackAndUnpack(bits, inValue);
102 | }
103 |
104 |
105 | [Test]
106 | [Repeat(1000)]
107 | #if !UNITY_EDITOR
108 | [Ignore("Quaternion.Euler Requires unity engine to run")]
109 | #endif
110 | public void PackAndUnpackRepeat([Range(8, 12)] int bits)
111 | {
112 | var inValue = GetRandomQuaternion();
113 |
114 | RunPackAndUnpack(bits, inValue);
115 | }
116 |
117 | [Test]
118 | [Repeat(1000)]
119 | #if !UNITY_EDITOR
120 | [Ignore("Quaternion.Euler Requires unity engine to run")]
121 | #endif
122 | public void PackAndUnpackRepeatNotNormalized([Range(8, 12)] int bits)
123 | {
124 | var inValueNotNormalized = GetRandomQuaternionNotNormalized();
125 |
126 | RunPackAndUnpack(bits, inValueNotNormalized);
127 | }
128 |
129 | private void RunPackAndUnpack(int bits, Quaternion inValueNotNormalized)
130 | {
131 | var inValue = inValueNotNormalized.normalized;
132 |
133 | // precision for 1 element
134 | var max = (1 / Mathf.Sqrt(2));
135 | var precision = 2 * max / ((1 << bits) - 1);
136 | // allow extra precision because largest is caculated using the other 3 values so may be out side of precision
137 | precision *= 2;
138 |
139 | var packer = new QuaternionPacker(bits);
140 |
141 | packer.Pack(writer, inValueNotNormalized);
142 |
143 | var outValue = packer.Unpack(GetReader());
144 | //Debug.Log($"Packed: ({inValue.x:0.000},{inValue.y:0.000},{inValue.z:0.000},{inValue.w:0.000}) " +
145 | // $"UnPacked: ({outValue.x:0.000},{outValue.y:0.000},{outValue.z:0.000},{outValue.w:0.000})");
146 |
147 | Assert.That(outValue.x, Is.Not.NaN, "x was NaN");
148 | Assert.That(outValue.y, Is.Not.NaN, "y was NaN");
149 | Assert.That(outValue.z, Is.Not.NaN, "z was NaN");
150 | Assert.That(outValue.w, Is.Not.NaN, "w was NaN");
151 |
152 | var assertSign = getAssertSign(inValue, outValue);
153 |
154 | Assert.That(outValue.x, IsUnSignedEqualWithIn(inValue.x), $"x off by {Mathf.Abs(assertSign * inValue.x - outValue.x)}");
155 | Assert.That(outValue.y, IsUnSignedEqualWithIn(inValue.y), $"y off by {Mathf.Abs(assertSign * inValue.y - outValue.y)}");
156 | Assert.That(outValue.z, IsUnSignedEqualWithIn(inValue.z), $"z off by {Mathf.Abs(assertSign * inValue.z - outValue.z)}");
157 | Assert.That(outValue.w, IsUnSignedEqualWithIn(inValue.w), $"w off by {Mathf.Abs(assertSign * inValue.w - outValue.w)}");
158 |
159 | var inVec = inValue * Vector3.forward;
160 | var outVec = outValue * Vector3.forward;
161 |
162 | // allow for extra precision when rotating vector
163 | Assert.AreEqual(inVec.x, outVec.x, precision * 2, $"vx off by {Mathf.Abs(inVec.x - outVec.x)}");
164 | Assert.AreEqual(inVec.y, outVec.y, precision * 2, $"vy off by {Mathf.Abs(inVec.y - outVec.y)}");
165 | Assert.AreEqual(inVec.z, outVec.z, precision * 2, $"vz off by {Mathf.Abs(inVec.z - outVec.z)}");
166 |
167 |
168 | EqualConstraint IsUnSignedEqualWithIn(float v)
169 | {
170 | return Is.EqualTo(v).Within(precision).Or.EqualTo(assertSign * v).Within(precision);
171 | }
172 | }
173 |
174 |
175 | [Test]
176 | [Repeat(1000)]
177 | public void FastNormalize()
178 | {
179 | var q1 = GetRandomQuaternionNotNormalized();
180 | // create copy here so it can be used in ref without chanigng q1
181 | var q2 = q1;
182 | QuaternionPacker.QuickNormalize(ref q2);
183 |
184 | Assert.That(q2, Is.EqualTo(q1.normalized));
185 | }
186 |
187 | ///
188 | /// sign used to validate values (in/out are different, then flip values
189 | ///
190 | ///
191 | ///
192 | ///
193 | private static float getAssertSign(Quaternion inValue, Quaternion outValue)
194 | {
195 | // keep same index for in/out because largest *might* have chagned if all elements are equal
196 | QuaternionPacker.FindLargestIndex(ref inValue, out var index);
197 |
198 | var inLargest = inValue[(int)index];
199 | var outLargest = outValue[(int)index];
200 | // flip sign of A if largest is is negative
201 | // Q == (-Q)
202 | var inSign = Mathf.Sign(inLargest);
203 | var outSign = Mathf.Sign(outLargest);
204 |
205 | float assertSign = inSign == outSign ? 1 : -1;
206 | return assertSign;
207 | }
208 |
209 |
210 | private static IEnumerable PackToIntCases()
211 | {
212 | yield return new TestCaseData(Quaternion.identity);
213 | yield return new TestCaseData(Quaternion.Euler(25, 30, 0));
214 | yield return new TestCaseData(Quaternion.Euler(-50, 30, 90));
215 | yield return new TestCaseData(Quaternion.Euler(90, 90, 180));
216 | yield return new TestCaseData(Quaternion.Euler(-20, 0, 45));
217 | yield return new TestCaseData(Quaternion.Euler(80, 30, -45));
218 | }
219 | [Test]
220 | [TestCaseSource(nameof(PackToIntCases))]
221 | #if !UNITY_EDITOR
222 | [Ignore("Quaternion.Euler Requires unity engine to run")]
223 | #endif
224 | public void PackAsInt(Quaternion inValue)
225 | {
226 | // precision for 1 element
227 | var max = (1 / Mathf.Sqrt(2));
228 | var precision = 2 * max / ((1 << 10) - 1);
229 | // allow extra precision because largest is caculated using the other 3 values so may be out side of precision
230 | precision *= 2;
231 |
232 | var encoded = QuaternionPacker.PackAsInt(inValue);
233 | var outValue = QuaternionPacker.UnpackFromInt(encoded);
234 |
235 | //Debug.Log($"Packed: ({inValue.x:0.000},{inValue.y:0.000},{inValue.z:0.000},{inValue.w:0.000}) " +
236 | // $"UnPacked: ({outValue.x:0.000},{outValue.y:0.000},{outValue.z:0.000},{outValue.w:0.000})");
237 |
238 | Assert.That(outValue.x, Is.Not.NaN, "x was NaN");
239 | Assert.That(outValue.y, Is.Not.NaN, "y was NaN");
240 | Assert.That(outValue.z, Is.Not.NaN, "z was NaN");
241 | Assert.That(outValue.w, Is.Not.NaN, "w was NaN");
242 |
243 | var assertSign = getAssertSign(inValue, outValue);
244 |
245 | Assert.That(outValue.x, IsUnSignedEqualWithIn(inValue.x), $"x off by {Mathf.Abs(assertSign * inValue.x - outValue.x)}");
246 | Assert.That(outValue.y, IsUnSignedEqualWithIn(inValue.y), $"y off by {Mathf.Abs(assertSign * inValue.y - outValue.y)}");
247 | Assert.That(outValue.z, IsUnSignedEqualWithIn(inValue.z), $"z off by {Mathf.Abs(assertSign * inValue.z - outValue.z)}");
248 | Assert.That(outValue.w, IsUnSignedEqualWithIn(inValue.w), $"w off by {Mathf.Abs(assertSign * inValue.w - outValue.w)}");
249 |
250 | var inVec = inValue * Vector3.forward;
251 | var outVec = outValue * Vector3.forward;
252 |
253 | // allow for extra precision when rotating vector
254 | Assert.AreEqual(inVec.x, outVec.x, precision * 2, $"vx off by {Mathf.Abs(inVec.x - outVec.x)}");
255 | Assert.AreEqual(inVec.y, outVec.y, precision * 2, $"vy off by {Mathf.Abs(inVec.y - outVec.y)}");
256 | Assert.AreEqual(inVec.z, outVec.z, precision * 2, $"vz off by {Mathf.Abs(inVec.z - outVec.z)}");
257 |
258 |
259 | EqualConstraint IsUnSignedEqualWithIn(float v)
260 | {
261 | return Is.EqualTo(v).Within(precision).Or.EqualTo(assertSign * v).Within(precision);
262 | }
263 | }
264 |
265 | [Test]
266 | [Description("Quaternion is a likely value, so packing to 0 will be useful compression")]
267 | public void PackIdentityShouldBeZero()
268 | {
269 | var encoded = QuaternionPacker.PackAsInt(Quaternion.identity);
270 | Assert.That(encoded, Is.Zero);
271 | }
272 | }
273 | }
274 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/NetworkReader.cs:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License
3 |
4 | Copyright (c) 2021 James Frowen
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | */
24 |
25 | using System;
26 | using System.IO;
27 | using System.Runtime.CompilerServices;
28 | using System.Runtime.InteropServices;
29 |
30 | namespace Mirage.Serialization
31 | {
32 | ///
33 | /// Bit writer, writes values to a buffer on a bit level
34 | /// Use to reduce memory allocation
35 | ///
36 | public unsafe class NetworkReader : IDisposable
37 | {
38 | private byte[] _managedBuffer;
39 | private GCHandle _handle;
40 | private ulong* _longPtr;
41 | private bool _needsDisposing;
42 |
43 | /// Current read position
44 | private int _bitPosition;
45 |
46 | /// Offset of given buffer
47 | private int _bitOffset;
48 |
49 | /// Length of given buffer
50 | private int _bitLength;
51 |
52 | ///
53 | /// Size of buffer that is being read from
54 | ///
55 | public int BitLength
56 | {
57 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
58 | get => _bitLength;
59 | }
60 |
61 | ///
62 | /// Current bit position for reading from buffer
63 | ///
64 | public int BitPosition
65 | {
66 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
67 | get => _bitPosition;
68 | }
69 | ///
70 | /// Current rounded up to nearest multiple of 8
71 | ///
72 | public int BytePosition
73 | {
74 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
75 | // rounds up to nearest 8
76 | // add to 3 last bits,
77 | // if any are 1 then it will roll over 4th bit.
78 | // if all are 0, then nothing happens
79 | get => (_bitPosition + 0b111) >> 3;
80 | }
81 |
82 | public NetworkReader() { }
83 |
84 | ~NetworkReader()
85 | {
86 | Dispose(false);
87 | }
88 | /// true if called from IDisposable
89 | protected virtual void Dispose(bool disposing)
90 | {
91 | if (!_needsDisposing) return;
92 |
93 | _handle.Free();
94 | _longPtr = null;
95 | _needsDisposing = false;
96 |
97 | if (disposing)
98 | {
99 | // clear manged stuff here because we no longer want reader to keep reference to buffer
100 | _bitLength = 0;
101 | _managedBuffer = null;
102 | }
103 | }
104 | public void Dispose()
105 | {
106 | Dispose(true);
107 | }
108 |
109 | public void Reset(ArraySegment segment)
110 | {
111 | Reset(segment.Array, segment.Offset, segment.Count);
112 | }
113 | public void Reset(byte[] array)
114 | {
115 | Reset(array, 0, array.Length);
116 | }
117 | public void Reset(byte[] array, int position, int length)
118 | {
119 | if (array == null)
120 | throw new ArgumentNullException(nameof(array), "Cant use null array in Reader");
121 |
122 | if (_needsDisposing)
123 | {
124 | // dispose old handler first
125 | // false here so we dont release reader back to pool
126 | Dispose(false);
127 | }
128 |
129 | // reset disposed bool, as it can be disposed again after reset
130 | _needsDisposing = true;
131 |
132 | _bitPosition = position * 8;
133 | _bitOffset = position * 8;
134 | _bitLength = _bitPosition + (length * 8);
135 | _managedBuffer = array;
136 | _handle = GCHandle.Alloc(_managedBuffer, GCHandleType.Pinned);
137 | _longPtr = (ulong*)_handle.AddrOfPinnedObject();
138 | }
139 |
140 | ///
141 | /// Can read atleast 1 bit
142 | ///
143 | ///
144 | public bool CanRead()
145 | {
146 | return _bitPosition < _bitLength;
147 | }
148 |
149 | ///
150 | /// Can atleast bits
151 | ///
152 | ///
153 | public bool CanReadBits(int readCount)
154 | {
155 | return (_bitPosition + readCount) <= _bitLength;
156 | }
157 |
158 | ///
159 | /// Can atleast bytes
160 | ///
161 | ///
162 | ///
163 | public bool CanReadBytes(int readCount)
164 | {
165 | return (_bitPosition + (readCount * 8)) <= _bitLength;
166 | }
167 |
168 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
169 | private void CheckNewLength(int newPosition)
170 | {
171 | if (newPosition > _bitLength)
172 | {
173 | ThrowPositionOverLength(newPosition);
174 | }
175 | }
176 |
177 | private void ThrowPositionOverLength(int newPosition)
178 | {
179 | throw new EndOfStreamException($"Can not read over end of buffer, new position {newPosition}, length {_bitLength} bits");
180 | }
181 |
182 | private void PadToByte()
183 | {
184 | _bitPosition = BytePosition << 3;
185 | }
186 |
187 |
188 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
189 | public bool ReadBoolean()
190 | {
191 | return ReadBooleanAsUlong() == 1UL;
192 | }
193 |
194 | ///
195 | /// Writes first bit of to buffer
196 | ///
197 | ///
198 | public ulong ReadBooleanAsUlong()
199 | {
200 | var newPosition = _bitPosition + 1;
201 | CheckNewLength(newPosition);
202 |
203 | var ptr = _longPtr + (_bitPosition >> 6);
204 | var result = ((*ptr) >> _bitPosition) & 0b1;
205 |
206 | _bitPosition = newPosition;
207 | return result;
208 | }
209 |
210 |
211 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
212 | public sbyte ReadSByte() => (sbyte)ReadByte();
213 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
214 | public byte ReadByte() => (byte)ReadUnmasked(8);
215 |
216 |
217 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
218 | public short ReadInt16() => (short)ReadUInt16();
219 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
220 | public ushort ReadUInt16() => (ushort)ReadUnmasked(16);
221 |
222 |
223 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
224 | public int ReadInt32() => (int)ReadUInt32();
225 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
226 | public uint ReadUInt32() => (uint)ReadUnmasked(32);
227 |
228 |
229 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
230 | public long ReadInt64() => (long)ReadUInt64();
231 | public ulong ReadUInt64()
232 | {
233 | var newPosition = _bitPosition + 64;
234 | CheckNewLength(newPosition);
235 |
236 | var bitsInLong = _bitPosition & 0b11_1111;
237 | ulong result;
238 | if (bitsInLong == 0)
239 | {
240 | var ptr1 = _longPtr + (_bitPosition >> 6);
241 | result = *ptr1;
242 | }
243 | else
244 | {
245 | var bitsLeft = 64 - bitsInLong;
246 |
247 | var ptr1 = _longPtr + (_bitPosition >> 6);
248 | var ptr2 = ptr1 + 1;
249 |
250 | // eg use byte, read 6 =>bitPosition=5, bitsLeft=3, newPos=1
251 | // r1 = aaab_bbbb => 0000_0aaa
252 | // r2 = cccc_caaa => ccaa_a000
253 | // r = r1|r2 => ccaa_aaaa
254 | // we mask this result later
255 |
256 | var r1 = (*ptr1) >> _bitPosition;
257 | var r2 = (*ptr2) << bitsLeft;
258 | result = r1 | r2;
259 | }
260 |
261 | _bitPosition = newPosition;
262 |
263 | // dont need to mask this result because should be reading all 64 bits
264 | return result;
265 | }
266 |
267 |
268 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
269 | public float ReadSingle()
270 | {
271 | var uValue = ReadUInt32();
272 | return *(float*)&uValue;
273 | }
274 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
275 | public double ReadDouble()
276 | {
277 | var uValue = ReadUInt64();
278 | return *(double*)&uValue;
279 | }
280 |
281 |
282 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
283 | public ulong Read(int bits)
284 | {
285 | if (bits == 0) return 0;
286 | // mask so we dont returns extra bits
287 | return ReadUnmasked(bits) & (ulong.MaxValue >> (64 - bits));
288 | }
289 |
290 | private ulong ReadUnmasked(int bits)
291 | {
292 | var newPosition = _bitPosition + bits;
293 | CheckNewLength(newPosition);
294 |
295 | var bitsInLong = _bitPosition & 0b11_1111;
296 | var bitsLeft = 64 - bitsInLong;
297 |
298 | ulong result;
299 | if (bitsLeft >= bits)
300 | {
301 | var ptr = _longPtr + (_bitPosition >> 6);
302 | result = (*ptr) >> bitsInLong;
303 | }
304 | else
305 | {
306 | var ptr1 = _longPtr + (_bitPosition >> 6);
307 | var ptr2 = ptr1 + 1;
308 |
309 | // eg use byte, read 6 =>bitPosition=5, bitsLeft=3, newPos=1
310 | // r1 = aaab_bbbb => 0000_0aaa
311 | // r2 = cccc_caaa => ccaa_a000
312 | // r = r1|r2 => ccaa_aaaa
313 | // we mask this result later
314 |
315 | var r1 = (*ptr1) >> bitsInLong;
316 | var r2 = (*ptr2) << bitsLeft;
317 | result = r1 | r2;
318 | }
319 | _bitPosition = newPosition;
320 |
321 | return result;
322 | }
323 |
324 | ///
325 | /// Reads n from buffer at
326 | ///
327 | /// number of bits in value to write
328 | /// where to write bits
329 | public ulong ReadAtPosition(int bits, int bitPosition)
330 | {
331 | // check length here so this methods throws instead of the read below
332 | CheckNewLength(bitPosition + bits);
333 |
334 | var currentPosition = _bitPosition;
335 | _bitPosition = bitPosition;
336 | var result = Read(bits);
337 | _bitPosition = currentPosition;
338 |
339 | return result;
340 | }
341 |
342 |
343 | ///
344 | /// Moves the internal bit position
345 | /// For most usecases it is safer to use
346 | /// WARNING: When reading from earlier position make sure to move position back to end of buffer after reading
347 | ///
348 | ///
349 | /// throws when is less than
350 | public void MoveBitPosition(int newPosition)
351 | {
352 | if (newPosition < _bitOffset)
353 | {
354 | throw new ArgumentOutOfRangeException(nameof(newPosition), newPosition, $"New position can not be less than buffer offset, Buffer offset: {_bitOffset}");
355 | }
356 | CheckNewLength(newPosition);
357 | _bitPosition = newPosition;
358 | }
359 |
360 |
361 | ///
362 | ///
363 | /// Moves position to nearest byte then copies struct from that position
364 | ///
365 | ///
366 | ///
367 | ///
368 | public void PadAndCopy(out T value) where T : unmanaged
369 | {
370 | PadToByte();
371 | var newPosition = _bitPosition + (8 * sizeof(T));
372 | CheckNewLength(newPosition);
373 |
374 | var startPtr = ((byte*)_longPtr) + (_bitPosition >> 3);
375 |
376 | value = *(T*)startPtr;
377 | _bitPosition = newPosition;
378 | }
379 |
380 | ///
381 | ///
382 | /// Moves position to nearest byte then copies bytes from that position
383 | ///
384 | ///
385 | ///
386 | ///
387 | ///
388 | public void ReadBytes(byte[] array, int offset, int length)
389 | {
390 | PadToByte();
391 | var newPosition = _bitPosition + (8 * length);
392 | CheckNewLength(newPosition);
393 |
394 | // todo benchmark this vs Marshal.Copy or for loop
395 | Buffer.BlockCopy(_managedBuffer, BytePosition, array, offset, length);
396 | _bitPosition = newPosition;
397 | }
398 |
399 | public ArraySegment ReadBytesSegment(int count)
400 | {
401 | PadToByte();
402 | var newPosition = _bitPosition + (8 * count);
403 | CheckNewLength(newPosition);
404 |
405 | var result = new ArraySegment(_managedBuffer, BytePosition, count);
406 | _bitPosition = newPosition;
407 | return result;
408 | }
409 | }
410 | }
411 |
--------------------------------------------------------------------------------
/JamesFrowen.BitPacking/Source/NetworkWriter.cs:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License
3 |
4 | Copyright (c) 2021 James Frowen
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | */
24 |
25 | using System;
26 | using System.Runtime.CompilerServices;
27 | using System.Runtime.InteropServices;
28 | using UnityEngine;
29 |
30 | namespace Mirage.Serialization
31 | {
32 | ///
33 | /// Bit writer, writes values to a buffer on a bit level
34 | /// Use to reduce memory allocation
35 | ///
36 | public unsafe class NetworkWriter
37 | {
38 | ///
39 | /// Max buffer size = 0.5MB
40 | ///
41 | private const int MAX_BUFFER_SIZE = 524_288;
42 | private byte[] _managedBuffer;
43 | private int _bitCapacity;
44 |
45 | /// Allow internal buffer to resize if capcity is reached
46 | private readonly bool _allowResize;
47 | private GCHandle _handle;
48 | private ulong* _longPtr;
49 | private bool _needsDisposing;
50 | private int _bitPosition;
51 |
52 | ///
53 | /// Size limit of buffer
54 | ///
55 | public int ByteCapacity
56 | {
57 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
58 | // see ByteLength for comment on math
59 | get => (_bitCapacity + 0b111) >> 3;
60 | }
61 |
62 | ///
63 | /// Current rounded up to nearest multiple of 8
64 | /// To set byte position use multiple by 8
65 | ///
66 | public int ByteLength
67 | {
68 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
69 | // rounds up to nearest 8
70 | // add to 3 last bits,
71 | // if any are 1 then it will roll over 4th bit.
72 | // if all are 0, then nothing happens
73 | get => (_bitPosition + 0b111) >> 3;
74 | }
75 |
76 | ///
77 | /// Current bit position for writing to buffer
78 | /// To set bit position use
79 | ///
80 | public int BitPosition
81 | {
82 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
83 | get => _bitPosition;
84 | }
85 |
86 | public NetworkWriter(int minByteCapacity) : this(minByteCapacity, true) { }
87 | public NetworkWriter(int minByteCapacity, bool allowResize)
88 | {
89 | _allowResize = allowResize;
90 |
91 | // ensure capacity is multiple of 8
92 | var ulongCapacity = Mathf.CeilToInt(minByteCapacity / (float)sizeof(ulong));
93 | var byteCapacity = ulongCapacity * sizeof(ulong);
94 |
95 | _bitCapacity = byteCapacity * 8;
96 | _managedBuffer = new byte[byteCapacity];
97 |
98 | CreateHandle();
99 | }
100 |
101 |
102 | ~NetworkWriter()
103 | {
104 | FreeHandle();
105 | }
106 |
107 | private void ResizeBuffer(int minBitCapacity)
108 | {
109 | // +7 to round up to next byte
110 | var minByteCapacity = (minBitCapacity + 7) / 8;
111 | var size = _managedBuffer.Length;
112 | while (size < minByteCapacity)
113 | {
114 | size *= 2;
115 | if (size > MAX_BUFFER_SIZE)
116 | {
117 | throw new InvalidOperationException($"Can not resize buffer to {size} bytes because it is above max value of {MAX_BUFFER_SIZE}");
118 | }
119 | }
120 |
121 | // define to allow debug.log to be skipped when running outside of unity
122 | #if !BIT_PACKING_NO_DEBUG
123 | Debug.LogWarning($"Resizing buffer, new size:{size} bytes");
124 | #endif
125 |
126 | FreeHandle();
127 |
128 | Array.Resize(ref _managedBuffer, size);
129 | _bitCapacity = size * 8;
130 |
131 | CreateHandle();
132 | }
133 |
134 | private void CreateHandle()
135 | {
136 | if (_needsDisposing) FreeHandle();
137 |
138 | _handle = GCHandle.Alloc(_managedBuffer, GCHandleType.Pinned);
139 | _longPtr = (ulong*)_handle.AddrOfPinnedObject();
140 | _needsDisposing = true;
141 | }
142 |
143 | ///
144 | /// Frees the handle for the buffer
145 | /// In order for to work This class can not have . Instead we call this method from finalize
146 | ///
147 | private void FreeHandle()
148 | {
149 | if (!_needsDisposing) return;
150 |
151 | _handle.Free();
152 | _longPtr = null;
153 | _needsDisposing = false;
154 | }
155 |
156 | public void Reset()
157 | {
158 | _bitPosition = 0;
159 | }
160 |
161 | ///
162 | /// Copies internal buffer to new Array
163 | /// To reduce Allocations use instead
164 | ///
165 | ///
166 | public byte[] ToArray()
167 | {
168 | var data = new byte[ByteLength];
169 | Buffer.BlockCopy(_managedBuffer, 0, data, 0, ByteLength);
170 | return data;
171 | }
172 | public ArraySegment ToArraySegment()
173 | {
174 | return new ArraySegment(_managedBuffer, 0, ByteLength);
175 | }
176 |
177 |
178 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
179 | private void CheckCapacity(int newLength)
180 | {
181 | if (newLength > _bitCapacity)
182 | {
183 | if (_allowResize)
184 | {
185 | ResizeBuffer(newLength);
186 | }
187 | else
188 | {
189 | ThrowLengthOverCapacity(newLength);
190 | }
191 | }
192 | }
193 |
194 | private void ThrowLengthOverCapacity(int newLength)
195 | {
196 | throw new InvalidOperationException($"Can not write over end of buffer, new length {newLength}, capacity {_bitCapacity}");
197 | }
198 |
199 | private void PadToByte()
200 | {
201 | _bitPosition = ByteLength << 3;
202 | }
203 |
204 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
205 | public void WriteBoolean(bool value)
206 | {
207 | WriteBoolean(value ? 1UL : 0UL);
208 | }
209 | ///
210 | /// Writes first bit of to buffer
211 | ///
212 | ///
213 | public void WriteBoolean(ulong value)
214 | {
215 | var newPosition = _bitPosition + 1;
216 | CheckCapacity(newPosition);
217 |
218 | var bitsInLong = _bitPosition & 0b11_1111;
219 |
220 | var ptr = _longPtr + (_bitPosition >> 6);
221 | *ptr = (
222 | *ptr & (
223 | // start with 0000_0001
224 | // shift by number in bit, eg 5 => 0010_0000
225 | // then not 1101_1111
226 | ~(1UL << bitsInLong)
227 | )
228 | ) | ((value & 0b1) << bitsInLong);
229 |
230 | _bitPosition = newPosition;
231 | }
232 |
233 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
234 | public void WriteSByte(sbyte value) => WriteByte((byte)value);
235 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
236 | public void WriteByte(byte value) => WriterUnmasked(value, 8);
237 |
238 |
239 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
240 | public void WriteInt16(short value) => WriteUInt16((ushort)value);
241 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
242 | public void WriteUInt16(ushort value) => WriterUnmasked(value, 16);
243 |
244 |
245 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
246 | public void WriteInt32(int value) => WriteUInt32((uint)value);
247 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
248 | public void WriteUInt32(uint value) => WriterUnmasked(value, 32);
249 |
250 |
251 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
252 | public void WriteInt64(long value) => WriteUInt64((ulong)value);
253 | public void WriteUInt64(ulong value)
254 | {
255 | var newPosition = _bitPosition + 64;
256 | CheckCapacity(newPosition);
257 |
258 | var bitsInLong = _bitPosition & 0b11_1111;
259 |
260 | if (bitsInLong == 0)
261 | {
262 | var ptr1 = _longPtr + (_bitPosition >> 6);
263 | *ptr1 = value;
264 | }
265 | else
266 | {
267 | var bitsLeft = 64 - bitsInLong;
268 |
269 | var ptr1 = _longPtr + (_bitPosition >> 6);
270 | var ptr2 = ptr1 + 1;
271 |
272 | *ptr1 = (*ptr1 & (ulong.MaxValue >> bitsLeft)) | (value << bitsInLong);
273 | *ptr2 = (*ptr2 & (ulong.MaxValue << newPosition)) | (value >> bitsLeft);
274 | }
275 | _bitPosition = newPosition;
276 | }
277 |
278 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
279 | public void WriteSingle(float value) => WriteUInt32(*(uint*)&value);
280 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
281 | public void WriteDouble(double value) => WriteUInt64(*(ulong*)&value);
282 |
283 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
284 | public void Write(ulong value, int bits)
285 | {
286 | if (bits == 0) return;
287 | // mask so we dont overwrite
288 | WriterUnmasked(value & BitMask.Mask(bits), bits);
289 | }
290 |
291 | private void WriterUnmasked(ulong value, int bits)
292 | {
293 | var newPosition = _bitPosition + bits;
294 | CheckCapacity(newPosition);
295 |
296 | var bitsInLong = _bitPosition & 0b11_1111;
297 | var bitsLeft = 64 - bitsInLong;
298 |
299 | if (bitsLeft >= bits)
300 | {
301 | var ptr = _longPtr + (_bitPosition >> 6);
302 |
303 | *ptr = (*ptr & BitMask.OuterMask(_bitPosition, newPosition)) | (value << bitsInLong);
304 | }
305 | else
306 | {
307 | var ptr1 = _longPtr + (_bitPosition >> 6);
308 | var ptr2 = ptr1 + 1;
309 |
310 | *ptr1 = (*ptr1 & (ulong.MaxValue >> bitsLeft)) | (value << bitsInLong);
311 | *ptr2 = (*ptr2 & (ulong.MaxValue << newPosition)) | (value >> bitsLeft);
312 | }
313 | _bitPosition = newPosition;
314 | }
315 |
316 | ///
317 | /// Same as expect position given is in bytes instead of bits
318 | /// WARNING: When writing to bytes instead of bits make sure you are able to read at the right position when deserializing as it might cause data to be misaligned
319 | ///
320 | ///
321 | ///
322 | ///
323 | public void WriteAtBytePosition(ulong value, int bits, int bytePosition)
324 | {
325 | WriteAtPosition(value, bits, bytePosition * 8);
326 | }
327 |
328 | ///
329 | /// Writes n from to
330 | /// This methods can be used to go back to a previous position to write length or other flags to the buffer after other data has been written
331 | /// WARNING: This method does not change the internal position so will not change the overall length if writing past internal position
332 | ///
333 | /// value to write
334 | /// number of bits in value to write
335 | /// where to write bits
336 | public void WriteAtPosition(ulong value, int bits, int bitPosition)
337 | {
338 | // check length here so this methods throws instead of the write below
339 | // this is so that it is more obvious that the position arg for this method is invalid
340 | CheckCapacity(bitPosition + bits);
341 |
342 | // moves position to arg, then write, then reset position
343 | var currentPosition = _bitPosition;
344 | _bitPosition = bitPosition;
345 | Write(value, bits);
346 | _bitPosition = currentPosition;
347 | }
348 |
349 |
350 | ///
351 | /// Moves the internal bit position
352 | /// For most usecases it is safer to use
353 | /// WARNING: When writing to earlier position make sure to move position back to end of buffer after writing because position is also used as length
354 | ///
355 | ///
356 | public void MoveBitPosition(int newPosition)
357 | {
358 | CheckCapacity(newPosition);
359 | _bitPosition = newPosition;
360 | }
361 |
362 | ///
363 | ///
364 | /// Moves position to nearest byte then copies struct to that position
365 | ///
366 | ///
367 | ///
368 | ///
369 | public void PadAndCopy(in T value) where T : unmanaged
370 | {
371 | PadToByte();
372 | var newPosition = _bitPosition + (8 * sizeof(T));
373 | CheckCapacity(newPosition);
374 |
375 | var startPtr = ((byte*)_longPtr) + (_bitPosition >> 3);
376 |
377 | var ptr = (T*)startPtr;
378 | *ptr = value;
379 | _bitPosition = newPosition;
380 | }
381 |
382 | ///
383 | ///
384 | /// Moves position to nearest byte then writes bytes to that position
385 | ///
386 | ///
387 | ///
388 | ///
389 | ///
390 | public void WriteBytes(byte[] array, int offset, int length)
391 | {
392 | PadToByte();
393 | var newPosition = _bitPosition + (8 * length);
394 | CheckCapacity(newPosition);
395 |
396 | // todo benchmark this vs Marshal.Copy or for loop
397 | Buffer.BlockCopy(array, offset, _managedBuffer, ByteLength, length);
398 | _bitPosition = newPosition;
399 | }
400 |
401 | ///
402 | /// Copies all data from
403 | ///
404 | ///
405 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
406 | public void CopyFromWriter(NetworkWriter other)
407 | {
408 | CopyFromWriter(other, 0, other.BitPosition);
409 | }
410 |
411 | ///
412 | /// Copies bits from starting at
413 | ///
414 | ///
415 | ///
416 | ///
417 | public void CopyFromWriter(NetworkWriter other, int otherBitPosition, int bitLength)
418 | {
419 | var newBit = _bitPosition + bitLength;
420 | CheckCapacity(newBit);
421 |
422 | var ulongPos = otherBitPosition >> 6;
423 | var otherPtr = other._longPtr + ulongPos;
424 |
425 |
426 | var firstBitOffset = otherBitPosition & 0b11_1111;
427 |
428 | // first align other
429 | if (firstBitOffset != 0)
430 | {
431 | var bitsToCopyFromFirst = Math.Min(64 - firstBitOffset, bitLength);
432 |
433 | // if offset is 10, then we want to shift value by 10 to remove un-needed bits
434 | var firstValue = *otherPtr >> firstBitOffset;
435 |
436 | Write(firstValue, bitsToCopyFromFirst);
437 |
438 | bitLength -= bitsToCopyFromFirst;
439 | otherPtr++;
440 | }
441 |
442 | // write aligned with other
443 | while (bitLength > 64)
444 | {
445 | WriteUInt64(*otherPtr);
446 |
447 | bitLength -= 64;
448 | otherPtr++;
449 | }
450 |
451 | // write left over others
452 | // if bitlength == 0 then write will return
453 | Write(*otherPtr, bitLength);
454 |
455 | // define to allow debug.log to be skipped when running outside of unity
456 | #if !BIT_PACKING_NO_DEBUG
457 | Debug.Assert(_bitPosition == newBit, "bitPosition should already be equal to newBit because it would have incremented each WriteUInt64");
458 | #endif
459 |
460 | _bitPosition = newBit;
461 | }
462 | }
463 | }
464 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Remove the line below if you want to inherit .editorconfig settings from higher directories
2 | ###############################
3 | # Core EditorConfig Options #
4 | ###############################
5 | root = true
6 |
7 | [*.{cs,csx,vb,vbx,md}]
8 | indent_style = space
9 | indent_size = 4
10 | tab_width = 4
11 | insert_final_newline = true
12 | charset = utf-8
13 |
14 | [*.yml]
15 | indent_style = space
16 | indent_size = 2
17 |
18 | [*.md]
19 | trim_trailing_whitespace = false
20 |
21 | # Code files
22 | [*.{cs,csx,vb,vbx}]
23 | trim_trailing_whitespace = true
24 |
25 | ###############################
26 | # .NET Coding Conventions #
27 | ###############################
28 | [*.{cs,vb}]
29 |
30 | # Organize usings
31 | dotnet_sort_system_directives_first = true
32 | dotnet_separate_import_directive_groups = false
33 | file_header_template = unset
34 |
35 | # this. preferences
36 | dotnet_style_qualification_for_event = false:suggestion
37 | dotnet_style_qualification_for_field = false:suggestion
38 | dotnet_style_qualification_for_method = false:suggestion
39 | dotnet_style_qualification_for_property = false:suggestion
40 |
41 | # Language keywords vs BCL types preferences
42 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
43 | dotnet_style_predefined_type_for_member_access = true:suggestion
44 |
45 | # Parentheses preferences
46 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion
47 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion
48 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion
49 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
50 |
51 | # Modifier preferences
52 | dotnet_style_require_accessibility_modifiers = for_non_interface_members
53 |
54 | # Expression-level preferences
55 | dotnet_style_coalesce_expression = true
56 | dotnet_style_collection_initializer = true
57 | dotnet_style_explicit_tuple_names = true
58 | dotnet_style_namespace_match_folder = true
59 | dotnet_style_null_propagation = true
60 | dotnet_style_object_initializer = true
61 | dotnet_style_operator_placement_when_wrapping = beginning_of_line
62 | dotnet_style_prefer_auto_properties = true:silent
63 | dotnet_style_prefer_compound_assignment = true
64 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent
65 | dotnet_style_prefer_conditional_expression_over_return = true:silent
66 | dotnet_style_prefer_inferred_anonymous_type_member_names = true
67 | dotnet_style_prefer_inferred_tuple_names = true
68 | dotnet_style_prefer_is_null_check_over_reference_equality_method = false
69 | dotnet_style_prefer_simplified_boolean_expressions = true
70 | dotnet_style_prefer_simplified_interpolation = true
71 |
72 | # Field preferences
73 | dotnet_style_readonly_field = true:suggestion
74 |
75 | # Parameter preferences
76 | dotnet_code_quality_unused_parameters = all:warning
77 |
78 | # Suppression preferences
79 | dotnet_remove_unnecessary_suppression_exclusions = none
80 |
81 | # New line preferences
82 | dotnet_style_allow_multiple_blank_lines_experimental = true
83 | dotnet_style_allow_statement_immediately_after_block_experimental = true
84 |
85 | #### C# Coding Conventions ####
86 |
87 | # var preferences
88 | csharp_style_var_elsewhere = true:suggestion
89 | csharp_style_var_for_built_in_types = true:suggestion
90 | csharp_style_var_when_type_is_apparent = true:suggestion
91 |
92 | # Expression-bodied members
93 | csharp_style_expression_bodied_accessors = when_on_single_line:suggestion
94 | csharp_style_expression_bodied_constructors = false:silent
95 | csharp_style_expression_bodied_indexers = when_on_single_line:suggestion
96 | csharp_style_expression_bodied_lambdas = true:suggestion
97 | csharp_style_expression_bodied_local_functions = false
98 | csharp_style_expression_bodied_methods = false
99 | csharp_style_expression_bodied_operators = when_on_single_line:suggestion
100 | csharp_style_expression_bodied_properties = when_on_single_line:suggestion
101 |
102 | # Pattern matching preferences
103 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
104 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
105 | csharp_style_prefer_not_pattern = true
106 | csharp_style_prefer_pattern_matching = true
107 | csharp_style_prefer_switch_expression = true:silent
108 |
109 | # Null-checking preferences
110 | csharp_style_conditional_delegate_call = true:suggestion
111 |
112 | # Modifier preferences
113 | csharp_prefer_static_local_function = true
114 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async
115 |
116 | # Code-block preferences
117 | csharp_prefer_braces = when_multiline:suggestion
118 | csharp_prefer_simple_using_statement = false:silent
119 |
120 | # Expression-level preferences
121 | csharp_prefer_simple_default_expression = true
122 | csharp_style_deconstructed_variable_declaration = true
123 | csharp_style_implicit_object_creation_when_type_is_apparent = false
124 | csharp_style_inlined_variable_declaration = true
125 | csharp_style_pattern_local_over_anonymous_function = true
126 | csharp_style_prefer_index_operator = true
127 | csharp_style_prefer_range_operator = true
128 | csharp_style_throw_expression = true:silent
129 | csharp_style_unused_value_assignment_preference = discard_variable:silent
130 | csharp_style_unused_value_expression_statement_preference = discard_variable
131 |
132 | # 'using' directive preferences
133 | csharp_using_directive_placement = outside_namespace
134 |
135 | # New line preferences
136 | csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false
137 | csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:suggestion
138 | csharp_style_allow_embedded_statements_on_same_line_experimental = true
139 |
140 | #### C# Formatting Rules ####
141 |
142 | # New line preferences
143 | csharp_new_line_before_catch = true
144 | csharp_new_line_before_else = true
145 | csharp_new_line_before_finally = true
146 | csharp_new_line_before_members_in_anonymous_types = true
147 | csharp_new_line_before_members_in_object_initializers = true
148 | csharp_new_line_before_open_brace = all
149 | csharp_new_line_between_query_expression_clauses = true
150 |
151 | # Indentation preferences
152 | csharp_indent_block_contents = true
153 | csharp_indent_braces = false
154 | csharp_indent_case_contents = true
155 | csharp_indent_case_contents_when_block = true
156 | csharp_indent_labels = one_less_than_current
157 | csharp_indent_switch_labels = true
158 |
159 | # Space preferences
160 | csharp_space_after_cast = false
161 | csharp_space_after_colon_in_inheritance_clause = true
162 | csharp_space_after_comma = true
163 | csharp_space_after_dot = false
164 | csharp_space_after_keywords_in_control_flow_statements = true
165 | csharp_space_after_semicolon_in_for_statement = true
166 | csharp_space_around_binary_operators = before_and_after
167 | csharp_space_around_declaration_statements = false
168 | csharp_space_before_colon_in_inheritance_clause = true
169 | csharp_space_before_comma = false
170 | csharp_space_before_dot = false
171 | csharp_space_before_open_square_brackets = false
172 | csharp_space_before_semicolon_in_for_statement = false
173 | csharp_space_between_empty_square_brackets = false
174 | csharp_space_between_method_call_empty_parameter_list_parentheses = false
175 | csharp_space_between_method_call_name_and_opening_parenthesis = false
176 | csharp_space_between_method_call_parameter_list_parentheses = false
177 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
178 | csharp_space_between_method_declaration_name_and_open_parenthesis = false
179 | csharp_space_between_method_declaration_parameter_list_parentheses = false
180 | csharp_space_between_parentheses = false
181 | csharp_space_between_square_brackets = false
182 |
183 | # Wrapping preferences
184 | csharp_preserve_single_line_blocks = true
185 | csharp_preserve_single_line_statements = true
186 |
187 | #### Naming styles ####
188 |
189 | # Naming rules
190 | # rules from https://github.com/jasontaylordev/CleanArchitecture/blob/main/.editorconfig
191 | # modifcations:
192 | # upper_snake_case for constants
193 | # private static shohuld be camelcase
194 | # removing _readonly from static field rules
195 | dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion
196 | dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces
197 | dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase
198 |
199 | dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = suggestion
200 | dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces
201 | dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase
202 |
203 | dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = suggestion
204 | dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters
205 | dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase
206 |
207 | dotnet_naming_rule.methods_should_be_pascalcase.severity = suggestion
208 | dotnet_naming_rule.methods_should_be_pascalcase.symbols = methods
209 | dotnet_naming_rule.methods_should_be_pascalcase.style = pascalcase
210 |
211 | dotnet_naming_rule.properties_should_be_pascalcase.severity = suggestion
212 | dotnet_naming_rule.properties_should_be_pascalcase.symbols = properties
213 | dotnet_naming_rule.properties_should_be_pascalcase.style = pascalcase
214 |
215 | dotnet_naming_rule.events_should_be_pascalcase.severity = suggestion
216 | dotnet_naming_rule.events_should_be_pascalcase.symbols = events
217 | dotnet_naming_rule.events_should_be_pascalcase.style = pascalcase
218 |
219 | dotnet_naming_rule.local_variables_should_be_camelcase.severity = suggestion
220 | dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables
221 | dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase
222 |
223 | dotnet_naming_rule.local_constants_should_be_camelcase.severity = suggestion
224 | dotnet_naming_rule.local_constants_should_be_camelcase.symbols = local_constants
225 | dotnet_naming_rule.local_constants_should_be_camelcase.style = camelcase
226 |
227 | dotnet_naming_rule.parameters_should_be_camelcase.severity = suggestion
228 | dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters
229 | dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase
230 |
231 | dotnet_naming_rule.public_fields_should_be_pascalcase.severity = suggestion
232 | dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields
233 | dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase
234 |
235 | dotnet_naming_rule.private_fields_should_be__camelcase.severity = suggestion
236 | dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields
237 | dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase
238 |
239 | dotnet_naming_rule.internal_fields_should_be__camelcase.severity = suggestion
240 | dotnet_naming_rule.internal_fields_should_be__camelcase.symbols = internal_fields
241 | dotnet_naming_rule.internal_fields_should_be__camelcase.style = _camelcase
242 |
243 | dotnet_naming_rule.private_static_fields_should_be__camelcase.severity = suggestion
244 | dotnet_naming_rule.private_static_fields_should_be__camelcase.symbols = private_static_fields
245 | dotnet_naming_rule.private_static_fields_should_be__camelcase.style = camelcase
246 |
247 | dotnet_naming_rule.public_constant_fields_should_be_pascalcase.severity = suggestion
248 | dotnet_naming_rule.public_constant_fields_should_be_pascalcase.symbols = public_constant_fields
249 | dotnet_naming_rule.public_constant_fields_should_be_pascalcase.style = upper_snake_case
250 |
251 | dotnet_naming_rule.private_constant_fields_should_be_pascalcase.severity = suggestion
252 | dotnet_naming_rule.private_constant_fields_should_be_pascalcase.symbols = private_constant_fields
253 | dotnet_naming_rule.private_constant_fields_should_be_pascalcase.style = upper_snake_case
254 |
255 | dotnet_naming_rule.internal_constant_fields_should_be_pascalcase.severity = suggestion
256 | dotnet_naming_rule.internal_constant_fields_should_be_pascalcase.symbols = internal_constant_fields
257 | dotnet_naming_rule.internal_constant_fields_should_be_pascalcase.style = upper_snake_case
258 |
259 | dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.severity = suggestion
260 | dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.symbols = public_static_readonly_fields
261 | dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.style = pascalcase
262 |
263 | dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.severity = suggestion
264 | dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.symbols = private_static_readonly_fields
265 | dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.style = camelcase
266 |
267 | dotnet_naming_rule.enums_should_be_pascalcase.severity = suggestion
268 | dotnet_naming_rule.enums_should_be_pascalcase.symbols = enums
269 | dotnet_naming_rule.enums_should_be_pascalcase.style = pascalcase
270 |
271 | dotnet_naming_rule.local_functions_should_be_pascalcase.severity = suggestion
272 | dotnet_naming_rule.local_functions_should_be_pascalcase.symbols = local_functions
273 | dotnet_naming_rule.local_functions_should_be_pascalcase.style = pascalcase
274 |
275 | dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = suggestion
276 | dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members
277 | dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase
278 |
279 | # Symbol specifications
280 |
281 | dotnet_naming_symbols.interfaces.applicable_kinds = interface
282 | dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
283 | dotnet_naming_symbols.interfaces.required_modifiers =
284 |
285 | dotnet_naming_symbols.enums.applicable_kinds = enum
286 | dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
287 | dotnet_naming_symbols.enums.required_modifiers =
288 |
289 | dotnet_naming_symbols.events.applicable_kinds = event
290 | dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
291 | dotnet_naming_symbols.events.required_modifiers =
292 |
293 | dotnet_naming_symbols.methods.applicable_kinds = method
294 | dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
295 | dotnet_naming_symbols.methods.required_modifiers =
296 |
297 | dotnet_naming_symbols.properties.applicable_kinds = property
298 | dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
299 | dotnet_naming_symbols.properties.required_modifiers =
300 |
301 | dotnet_naming_symbols.public_fields.applicable_kinds = field
302 | dotnet_naming_symbols.public_fields.applicable_accessibilities = public
303 | dotnet_naming_symbols.public_fields.required_modifiers =
304 |
305 | dotnet_naming_symbols.private_fields.applicable_kinds = field
306 | dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
307 | dotnet_naming_symbols.private_fields.required_modifiers =
308 |
309 | dotnet_naming_symbols.private_static_fields.applicable_kinds = field
310 | dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
311 | dotnet_naming_symbols.private_static_fields.required_modifiers = static
312 |
313 | dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum
314 | dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
315 | dotnet_naming_symbols.types_and_namespaces.required_modifiers =
316 |
317 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
318 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
319 | dotnet_naming_symbols.non_field_members.required_modifiers =
320 |
321 | dotnet_naming_symbols.type_parameters.applicable_kinds = namespace
322 | dotnet_naming_symbols.type_parameters.applicable_accessibilities = *
323 | dotnet_naming_symbols.type_parameters.required_modifiers =
324 |
325 | dotnet_naming_symbols.private_constant_fields.applicable_kinds = field
326 | dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
327 | dotnet_naming_symbols.private_constant_fields.required_modifiers = const
328 |
329 | dotnet_naming_symbols.local_variables.applicable_kinds = local
330 | dotnet_naming_symbols.local_variables.applicable_accessibilities = local
331 | dotnet_naming_symbols.local_variables.required_modifiers =
332 |
333 | dotnet_naming_symbols.local_constants.applicable_kinds = local
334 | dotnet_naming_symbols.local_constants.applicable_accessibilities = local
335 | dotnet_naming_symbols.local_constants.required_modifiers = const
336 |
337 | dotnet_naming_symbols.parameters.applicable_kinds = parameter
338 | dotnet_naming_symbols.parameters.applicable_accessibilities = *
339 | dotnet_naming_symbols.parameters.required_modifiers =
340 |
341 | dotnet_naming_symbols.public_constant_fields.applicable_kinds = field
342 | dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public
343 | dotnet_naming_symbols.public_constant_fields.required_modifiers = const
344 |
345 | dotnet_naming_symbols.public_static_readonly_fields.applicable_kinds = field
346 | dotnet_naming_symbols.public_static_readonly_fields.applicable_accessibilities = public
347 | dotnet_naming_symbols.public_static_readonly_fields.required_modifiers = readonly, static
348 |
349 | dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field
350 | dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
351 | dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readonly, static
352 |
353 | dotnet_naming_symbols.public_static_fields.applicable_kinds = field
354 | dotnet_naming_symbols.public_static_fields.applicable_accessibilities = public
355 | dotnet_naming_symbols.public_static_fields.required_modifiers = readonly, static
356 |
357 | dotnet_naming_symbols.internal_fields.applicable_kinds = field
358 | dotnet_naming_symbols.internal_fields.applicable_accessibilities = internal
359 | dotnet_naming_symbols.internal_fields.required_modifiers =
360 |
361 | dotnet_naming_symbols.internal_constant_fields.applicable_kinds = field
362 | dotnet_naming_symbols.internal_constant_fields.applicable_accessibilities = internal
363 | dotnet_naming_symbols.internal_constant_fields.required_modifiers = const
364 |
365 | dotnet_naming_symbols.internal_static_readonly_fields.applicable_kinds = field
366 | dotnet_naming_symbols.internal_static_readonly_fields.applicable_accessibilities = internal
367 | dotnet_naming_symbols.internal_static_readonly_fields.required_modifiers = readonly, static
368 |
369 | dotnet_naming_symbols.internal_static_fields.applicable_kinds = field
370 | dotnet_naming_symbols.internal_static_fields.applicable_accessibilities = internal
371 | dotnet_naming_symbols.internal_static_fields.required_modifiers = readonly, static
372 |
373 | dotnet_naming_symbols.local_functions.applicable_kinds = local_function
374 | dotnet_naming_symbols.local_functions.applicable_accessibilities = *
375 | dotnet_naming_symbols.local_functions.required_modifiers =
376 |
377 | # Naming styles
378 |
379 | dotnet_naming_style.pascalcase.required_prefix =
380 | dotnet_naming_style.pascalcase.required_suffix =
381 | dotnet_naming_style.pascalcase.word_separator =
382 | dotnet_naming_style.pascalcase.capitalization = pascal_case
383 |
384 | dotnet_naming_style.ipascalcase.required_prefix = I
385 | dotnet_naming_style.ipascalcase.required_suffix =
386 | dotnet_naming_style.ipascalcase.word_separator =
387 | dotnet_naming_style.ipascalcase.capitalization = pascal_case
388 |
389 | dotnet_naming_style.tpascalcase.required_prefix = T
390 | dotnet_naming_style.tpascalcase.required_suffix =
391 | dotnet_naming_style.tpascalcase.word_separator =
392 | dotnet_naming_style.tpascalcase.capitalization = pascal_case
393 |
394 | dotnet_naming_style._camelcase.required_prefix = _
395 | dotnet_naming_style._camelcase.required_suffix =
396 | dotnet_naming_style._camelcase.word_separator =
397 | dotnet_naming_style._camelcase.capitalization = camel_case
398 |
399 | dotnet_naming_style.camelcase.required_prefix =
400 | dotnet_naming_style.camelcase.required_suffix =
401 | dotnet_naming_style.camelcase.word_separator =
402 | dotnet_naming_style.camelcase.capitalization = camel_case
403 |
404 | dotnet_naming_style.s_camelcase.required_prefix = s_
405 | dotnet_naming_style.s_camelcase.required_suffix =
406 | dotnet_naming_style.s_camelcase.word_separator =
407 | dotnet_naming_style.s_camelcase.capitalization = camel_case
408 |
409 | dotnet_naming_style.upper_snake_case.required_prefix =
410 | dotnet_naming_style.upper_snake_case.required_suffix =
411 | dotnet_naming_style.upper_snake_case.word_separator = _
412 | dotnet_naming_style.upper_snake_case.capitalization = all_upper
413 |
--------------------------------------------------------------------------------