Skip to content

Commit bdbee18

Browse files
author
Eugene Levchenkov
committed
Add quantization draft version
1 parent 8df2a2e commit bdbee18

7 files changed

Lines changed: 378 additions & 1 deletion

File tree

NetCode.UnitTests/MathTests.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using FluentAssertions;
2+
using Xunit;
3+
4+
namespace NetCode.UnitTests;
5+
6+
public class MathTests
7+
{
8+
[Fact]
9+
public void BitsRequired()
10+
{
11+
Mathi.BitsRequired(0).Should().Be(1); // 1 bit: 0, 1
12+
Mathi.BitsRequired(1).Should().Be(1); // 1 bits: 0, 1
13+
Mathi.BitsRequired(2).Should().Be(2); // 2 bits: [0, 3]
14+
Mathi.BitsRequired(3).Should().Be(2); // 2 bits: [0, 3]
15+
Mathi.BitsRequired(4).Should().Be(3); // 3 bits: [0, 7]
16+
Mathi.BitsRequired(7).Should().Be(3); // 3 bits: [0, 7]
17+
Mathi.BitsRequired(8).Should().Be(4); // 3 bits: [0, 15]
18+
Mathi.BitsRequired(15).Should().Be(4); // 3 bits: [0, 15]
19+
Mathi.BitsRequired(16).Should().Be(5); // 3 bits: [0, 31]
20+
Mathi.BitsRequired(uint.MaxValue / 2).Should().Be(31);
21+
Mathi.BitsRequired(uint.MaxValue).Should().Be(32);
22+
}
23+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using Xunit;
2+
3+
namespace NetCode.UnitTests.Quantization;
4+
5+
public class QuantizationBitWriterTests
6+
{
7+
[Fact]
8+
public void T()
9+
{
10+
var bitWriter = new QuantizationBitWriter();
11+
12+
// positive
13+
bitWriter.Write(1, 0, 10);
14+
bitWriter.Write(-1, -10, 0);
15+
16+
bitWriter.Write(1, 1, 1);
17+
bitWriter.Write(1, 1, 2);
18+
bitWriter.Write(2, 1, 2);
19+
20+
bitWriter.Write(-1, -1, -1);
21+
bitWriter.Write(-2, -2, 1);
22+
bitWriter.Write(-1, -2, 1);
23+
24+
bitWriter.Write(0, 0, 0);
25+
bitWriter.Write(0, 0, 1);
26+
bitWriter.Write(0, -1, 0);
27+
28+
// negative case for value
29+
bitWriter.Write(1, 0, 0);
30+
bitWriter.Write(-1, 0, 0);
31+
32+
bitWriter.Write(2, 1, 1);
33+
bitWriter.Write(-2, -1, -1);
34+
35+
bitWriter.Write(2, 0, 1);
36+
bitWriter.Write(-1, 0, 1);
37+
38+
bitWriter.Write(-2, -1, 0);
39+
bitWriter.Write(1, -1, 0);
40+
41+
bitWriter.Write(10, 1, 5);
42+
bitWriter.Write(-10, -5, -1);
43+
44+
bitWriter.Write(10, -5, 5);
45+
bitWriter.Write(-10, -5, 5);
46+
47+
// negative case for range
48+
49+
bitWriter.Write(1, 1, 0);
50+
bitWriter.Write(0, 1, 0);
51+
52+
bitWriter.Write(-1, 0, -1);
53+
bitWriter.Write(0, 0, -1);
54+
}
55+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using FluentAssertions;
2+
using Xunit;
3+
4+
namespace NetCode.UnitTests.Quantization;
5+
6+
public class QuantizationReaderWriterTests
7+
{
8+
[Fact]
9+
public void WriteAndReadTheSameData()
10+
{
11+
var floatLimit = new FloatLimit(50f, 200f, 0.1f);
12+
var array = new byte[100];
13+
var writer = new QuantizationBitWriter(array);
14+
15+
writer.Write(100, 50, 200);
16+
writer.Write(100f, floatLimit);
17+
writer.Flush();
18+
19+
var data = writer.Array;
20+
21+
var reader = new QuantizationBitReader(data);
22+
reader.ReadInt(50, 200).Should().Be(100);
23+
reader.ReadFloat(floatLimit).Should().BeApproximately(100f, 0.1f);
24+
}
25+
}

NetCode/LimitConfiguration.cs

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
using System.Linq.Expressions;
2+
using System.Numerics;
3+
using System.Reflection;
4+
5+
namespace NetCode;
6+
7+
public abstract class MinMaxConfiguration<T>
8+
where T : unmanaged
9+
{
10+
internal T Min { get; set; }
11+
12+
internal T Max { get; set; }
13+
}
14+
15+
16+
public abstract class MinMaxPrecisionConfiguration<T> : MinMaxConfiguration<T>
17+
where T : unmanaged
18+
{
19+
internal T Precision { get; set; }
20+
}
21+
22+
public sealed class IntFieldConfiguration : MinMaxConfiguration<int>
23+
{
24+
public void Limit(int min, int max)
25+
{
26+
Min = min;
27+
Max = max;
28+
}
29+
}
30+
31+
public sealed class FloatFieldConfiguration : MinMaxPrecisionConfiguration<float>
32+
{
33+
public void Limit(float min, float max, float precision)
34+
{
35+
Min = min;
36+
Max = max;
37+
Precision = precision;
38+
}
39+
}
40+
41+
public class LimitProfile<T>
42+
{
43+
public LimitProfile<T> Configure(Expression<Func<T, int>> fieldAccessor)
44+
{
45+
return this;
46+
}
47+
48+
public LimitProfile<T> Configure(Expression<Func<T, int>> fieldAccessor, Action<IntFieldConfiguration> config)
49+
{
50+
return this;
51+
}
52+
53+
public LimitProfile<T> Configure(Expression<Func<T, float>> fieldAccessor, Action<FloatFieldConfiguration> config)
54+
{
55+
return this;
56+
}
57+
}
58+
59+
public class QuantizationBitReader : BitReader
60+
{
61+
public QuantizationBitReader()
62+
{
63+
}
64+
65+
public QuantizationBitReader(byte[] data) : base(data)
66+
{
67+
}
68+
69+
public QuantizationBitReader(ByteReader byteReader) : base(byteReader)
70+
{
71+
}
72+
73+
public int ReadInt(int min, int max)
74+
{
75+
if (min > max)
76+
{
77+
ThrowHelper.ThrowArgumentException();
78+
}
79+
80+
var range = max - min;
81+
var bitsRequired = Mathi.BitsRequired((uint)range);
82+
var value = (int)ReadBits(bitsRequired);
83+
return value + min;
84+
}
85+
86+
public float ReadFloat(float min, float max, float precision)
87+
{
88+
if (min > max)
89+
{
90+
ThrowHelper.ThrowArgumentException();
91+
}
92+
93+
var result = ReadFloat(new FloatLimit(min, max, precision));
94+
95+
return result;
96+
}
97+
98+
public float ReadFloat(FloatLimit limit)
99+
{
100+
uint integerValue = ReadBits(limit.NumberOfBits);
101+
float normalizedValue = integerValue / (float)limit.MaxIntegerValue;
102+
103+
return normalizedValue * limit.Delta + limit.Min;
104+
}
105+
}
106+
107+
108+
public class QuantizationBitWriter : BitWriter
109+
{
110+
public QuantizationBitWriter(int capacity = 1500) : base(capacity)
111+
{
112+
}
113+
114+
public QuantizationBitWriter(byte[] data) : base(data)
115+
{
116+
}
117+
118+
public QuantizationBitWriter(ByteWriter byteWriter) : base(byteWriter)
119+
{
120+
}
121+
122+
public void Write(int value, int min, int max)
123+
{
124+
if (min > max)
125+
{
126+
ThrowHelper.ThrowArgumentException();
127+
}
128+
129+
if (value < min || value > max)
130+
{
131+
ThrowHelper.ThrowArgumentOutOfRangeException();
132+
}
133+
134+
var range = max - min;
135+
var bitsRequired = Mathi.BitsRequired((uint)range);
136+
WriteBits(bitsRequired, (uint)(value - min));
137+
}
138+
139+
public void Write(uint value, uint min, uint max)
140+
{
141+
if (min > max)
142+
{
143+
ThrowHelper.ThrowArgumentException();
144+
}
145+
146+
if (value < min || value > max)
147+
{
148+
ThrowHelper.ThrowArgumentOutOfRangeException();
149+
}
150+
151+
var range = max - min;
152+
var bitsRequired = Mathi.BitsRequired(range);
153+
WriteBits(bitsRequired, value - min);
154+
}
155+
156+
public void Write(float value, float min, float max, float precision)
157+
{
158+
if (min > max)
159+
{
160+
ThrowHelper.ThrowArgumentException();
161+
}
162+
163+
if (value < min || value > max)
164+
{
165+
ThrowHelper.ThrowArgumentOutOfRangeException();
166+
}
167+
168+
Write(value, new FloatLimit(min, max, precision));
169+
}
170+
171+
public void Write(float value, FloatLimit limit)
172+
{
173+
float normalizedValue = Math.Clamp((value - limit.Min) / limit.Delta, 0, 1);
174+
uint integerValue = (uint)Math.Floor(normalizedValue * limit.MaxIntegerValue + 0.5f);
175+
176+
WriteBits(limit.NumberOfBits, integerValue);
177+
}
178+
}
179+
180+
public readonly struct FloatLimit
181+
{
182+
public readonly float Min;
183+
184+
public readonly float Delta;
185+
186+
public readonly uint MaxIntegerValue;
187+
188+
public readonly int NumberOfBits;
189+
190+
public FloatLimit(float min, float max, float precision)
191+
{
192+
Min = min;
193+
Delta = max - min;
194+
float values = Delta / precision;
195+
MaxIntegerValue = (uint)Math.Ceiling(values);
196+
NumberOfBits = Mathi.BitsRequired(MaxIntegerValue);
197+
}
198+
}
199+
200+
public static class C
201+
{
202+
public static Action<T, TProperty> GetSetter<T, TProperty>(Expression<Func<T, TProperty>> expression)
203+
{
204+
var memberExpression = (MemberExpression)expression.Body;
205+
var property = (PropertyInfo)memberExpression.Member;
206+
var setMethod = property.GetSetMethod();
207+
208+
var parameterT = Expression.Parameter(typeof(T), "x");
209+
var parameterTProperty = Expression.Parameter(typeof(TProperty), "y");
210+
211+
var newExpression =
212+
Expression.Lambda<Action<T, TProperty>>(
213+
Expression.Call(parameterT, setMethod, parameterTProperty),
214+
parameterT,
215+
parameterTProperty
216+
);
217+
218+
return newExpression.Compile();
219+
}
220+
221+
public static void M()
222+
{
223+
var randomProfile = new LimitProfile<RandomComponent>()
224+
.Configure(x => x.Seed);
225+
226+
var healthProfile = new LimitProfile<HealthComponent>()
227+
.Configure(x => x.Health, c => c.Limit(0, 100))
228+
.Configure(x => x.MaxHealth, c => c.Limit(0, 100));
229+
230+
var transformProfile = new LimitProfile<TransformComponent>()
231+
.Configure(x => x.Position.X, c => c.Limit(-100, 100, 0.01f))
232+
.Configure(x => x.Position.Y, c => c.Limit(-3, 13, 0.01f))
233+
.Configure(x => x.Position.Z, c => c.Limit(-100, 100, 0.01f))
234+
.Configure(x => x.Yaw, c => c.Limit(0, 360, 0.1f))
235+
.Configure(x => x.Pitch, c => c.Limit(0, 360, 0.1f));
236+
}
237+
}
238+
239+
public struct RandomComponent
240+
{
241+
public int Seed;
242+
}
243+
244+
public struct HealthComponent
245+
{
246+
public int Health;
247+
248+
public int MaxHealth;
249+
}
250+
251+
public struct TransformComponent
252+
{
253+
public Vector3 Position;
254+
255+
public float Yaw;
256+
257+
public float Pitch;
258+
}
259+

NetCode/Mathi.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Runtime.CompilerServices;
1+
using System.Numerics;
2+
using System.Runtime.CompilerServices;
23

34
namespace NetCode;
45

@@ -22,4 +23,10 @@ public static (int Quotient, int Remainder) DivRem(int left, int right)
2223
int quotient = left / right;
2324
return (quotient, left - (quotient * right));
2425
}
26+
27+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
28+
public static int BitsRequired(uint range)
29+
{
30+
return range == 0 ? 1 : BitOperations.Log2(range) + 1;
31+
}
2532
}

NetCode/NetCode.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,8 @@
2727
<PackageReference Include="System.Memory" Version="4.5.5" />
2828
</ItemGroup>
2929

30+
<ItemGroup>
31+
<Folder Include="Quantization" />
32+
</ItemGroup>
33+
3034
</Project>

0 commit comments

Comments
 (0)