Skip to content

Commit

Permalink
feat: adding option for unsigned floats in floatpacker
Browse files Browse the repository at this point in the history
  • Loading branch information
James-Frowen committed Jun 21, 2022
1 parent 273c14e commit dda61e1
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 31 deletions.
20 changes: 19 additions & 1 deletion Assets/Mirage/Runtime/Serialization/Packers/BitHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ MIT License
*/

using System;
using System.Runtime.CompilerServices;
using UnityEngine;

namespace Mirage.Serialization
Expand All @@ -38,9 +39,26 @@ public static class BitHelper
/// <param name="max"></param>
/// <param name="precision">lowest precision required, bit count will round up so real precision might be higher</param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int BitCount(float max, float precision)
{
return Mathf.FloorToInt(Mathf.Log(2 * max / precision, 2)) + 1;
return BitCount(max, precision, true);
}

/// <summary>
/// Gets the number of bits need for <paramref name="precision"/> in range <paramref name="max"/>
/// <para>If signed then range is negative max to positive max, If unsigned then 0 to max</para>
/// <para>
/// WARNING: these methods are not fast, dont use in hotpath
/// </para>
/// </summary>
/// <param name="max"></param>
/// <param name="precision">lowest precision required, bit count will round up so real precision might be higher</param>
/// <returns></returns>
public static int BitCount(float max, float precision, bool signed)
{
float multiplier = signed ? 2 : 1;
return Mathf.FloorToInt(Mathf.Log(multiplier * max / precision, 2)) + 1;
}

/// <summary>
Expand Down
39 changes: 27 additions & 12 deletions Assets/Mirage/Runtime/Serialization/Packers/FloatPacker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,18 @@ public sealed class FloatPacker
readonly float positiveMax;
readonly float negativeMax;

/// <param name="max"></param>
/// <param name="lowestPrecision">lowest precision, actual precision will be caculated from number of bits used</param>
public FloatPacker(float max, float lowestPrecision) : this(max, BitHelper.BitCount(max, lowestPrecision)) { }
public FloatPacker(float max, float lowestPrecision) : this(max, lowestPrecision, true) { }

public FloatPacker(float max, int bitCount) : this(max, bitCount, true) { }

/// <summary>
///
/// </summary>
/// <param name="max"></param>
/// <param name="lowestPrecision">lowest precision, actual precision will be caculated from number of bits used</param>
public FloatPacker(float max, int bitCount)
/// <param name="signed">if negative values will be allowed or not</param>
public FloatPacker(float max, float lowestPrecision, bool signed) : this(max, BitHelper.BitCount(max, lowestPrecision, signed), signed) { }

/// <param name="signed">if negative values will be allowed or not</param>
public FloatPacker(float max, int bitCount, bool signed)
{
this.bitCount = bitCount;
// not sure what max bit count should be,
Expand All @@ -62,14 +64,27 @@ public FloatPacker(float max, int bitCount)
if (bitCount < 1) throw new ArgumentException("Bit count is too low, bit count should be between 1 and 30", nameof(bitCount));
if (bitCount > 30) throw new ArgumentException("Bit count is too high, bit count should be between 1 and 30", nameof(bitCount));

midPoint = (1u << (bitCount - 1)) - 1u;
multiplier_pack = midPoint / max;
multiplier_unpack = 1 / multiplier_pack;
mask = (1u << bitCount) - 1u;
toNegative = (int)(mask + 1u);

positiveMax = max;
negativeMax = -max;
if (signed)
{
midPoint = (1u << (bitCount - 1)) - 1u;
toNegative = (int)(mask + 1u);

positiveMax = max;
negativeMax = -max;
}
else // unsigned
{
midPoint = (1u << (bitCount)) - 1u;
toNegative = 0;

positiveMax = max;
negativeMax = 0;
}

multiplier_pack = midPoint / max;
multiplier_unpack = 1 / multiplier_pack;
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,45 +21,57 @@ public int CreateUsingPrecsion(float precision)
}

[Test]
public void PackFromBitCountPacksToCorrectCount([Range(1, 30)] int bitCount)
[TestCase(1, ExpectedResult = 7)]
[TestCase(0.1f, ExpectedResult = 10)]
[TestCase(0.01f, ExpectedResult = 14)]
public int BitCountIsLessForUnSigned(float precision)
{
var packer = new FloatPacker(100, bitCount);
var packer = new FloatPacker(100, precision, false);

packer.Pack(writer, 1f);
return writer.BitPosition;
}

[Test]
public void PackFromBitCountPacksToCorrectCount([Range(1, 30)] int bitCount, [Values(true, false)] bool signed)
{
var packer = new FloatPacker(100, bitCount, signed);

packer.Pack(writer, 1f);

Assert.That(writer.BitPosition, Is.EqualTo(bitCount));
}

[Test]
public void ThrowsIfBitCountIsLessThan1([Range(-10, 0)] int bitCount)
public void ThrowsIfBitCountIsLessThan1([Range(-10, 0)] int bitCount, [Values(true, false)] bool signed)
{
ArgumentException exception = Assert.Throws<ArgumentException>(() =>
{
_ = new FloatPacker(10, bitCount);
_ = new FloatPacker(10, bitCount, signed);
});

var expected = new ArgumentException("Bit count is too low, bit count should be between 1 and 30", "bitCount");
Assert.That(exception, Has.Message.EqualTo(expected.Message));
}

[Test]
public void ThrowsIfBitCountIsGreaterThan30([Range(31, 40)] int bitCount)
public void ThrowsIfBitCountIsGreaterThan30([Range(31, 40)] int bitCount, [Values(true, false)] bool signed)
{
ArgumentException exception = Assert.Throws<ArgumentException>(() =>
{
_ = new FloatPacker(10, bitCount);
_ = new FloatPacker(10, bitCount, signed);
});

var expected = new ArgumentException("Bit count is too high, bit count should be between 1 and 30", "bitCount");
Assert.That(exception, Has.Message.EqualTo(expected.Message));
}

[Test]
public void ThrowsIfMaxIsZero()
public void ThrowsIfMaxIsZero([Values(true, false)] bool signed)
{
ArgumentException exception = Assert.Throws<ArgumentException>(() =>
{
_ = new FloatPacker(0, 1);
_ = new FloatPacker(0, 1, signed);
});

var expected = new ArgumentException("Max can not be 0", "max");
Expand Down
30 changes: 20 additions & 10 deletions Assets/Tests/Runtime/Serialization/Packers/FloatPackerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,38 @@

namespace Mirage.Tests.Runtime.Serialization.Packers
{
[TestFixture(100, 0.1f)]
[TestFixture(500, 0.02f)]
[TestFixture(2000, 0.05f)]
[TestFixture(1.5f, 0.01f)]
[TestFixture(100_000, 30)]
[TestFixture(100, 0.1f, true)]
[TestFixture(500, 0.02f, true)]
[TestFixture(2000, 0.05f, true)]
[TestFixture(1.5f, 0.01f, true)]
[TestFixture(100_000, 30, true)]

[TestFixture(100, 0.1f, false)]
[TestFixture(500, 0.02f, false)]
[TestFixture(2000, 0.05f, false)]
[TestFixture(1.5f, 0.01f, false)]
[TestFixture(100_000, 30, false)]
public class FloatPackerTests : PackerTestBase
{
readonly FloatPacker packer;
readonly float max;
readonly float min;
readonly float precsion;
readonly bool signed;

public FloatPackerTests(float max, float precsion)
public FloatPackerTests(float max, float precsion, bool signed)
{
this.max = max;
min = signed ? -max : 0;
this.precsion = precsion;
packer = new FloatPacker(max, precsion);
this.signed = signed;
packer = new FloatPacker(max, precsion, signed);
}


float GetRandomFloat()
{
return Random.Range(-max, max);
return Random.Range(min, max);
}


Expand Down Expand Up @@ -58,7 +68,7 @@ public void ValueUnderNegativeMaxWillBeUnpackedAsNegativeMax()
uint packed = packer.Pack(start);
float unpacked = packer.Unpack(packed);

Assert.That(unpacked, Is.EqualTo(-max).Within(precsion));
Assert.That(unpacked, Is.EqualTo(min).Within(precsion));
}

[Test]
Expand Down Expand Up @@ -100,7 +110,7 @@ public void ValueUnderNegativeMaxWillBeUnpackedUsingWriterAsNegativeMax()
packer.Pack(writer, start);
float unpacked = packer.Unpack(GetReader());

Assert.That(unpacked, Is.EqualTo(-max).Within(precsion));
Assert.That(unpacked, Is.EqualTo(min).Within(precsion));
}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Mirage.Serialization;
using NUnit.Framework;

namespace Mirage.Tests.Runtime.Serialization.Packers
{
public class UnsignedFloatPackerTests : PackerTestBase
{
FloatPacker packer;
float max;
float precsion;

[SetUp]
public void Setup()
{
max = 100;
precsion = 1 / 1000f;
packer = new FloatPacker(max, precsion, false);
}

[Test]
public void ClampsToZero()
{
packer.Pack(writer, -4.5f);
float outValue = packer.Unpack(GetReader());

Assert.That(outValue, Is.Zero);
}

[Test]
public void CanWriteNearMax()
{
const float value = 99.5f;
packer.Pack(writer, value);
float outValue = packer.Unpack(GetReader());

Assert.That(outValue, Is.EqualTo(value).Within(precsion));
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit dda61e1

Please sign in to comment.