├── .gitignore
├── LICENSE
├── NetCode.Benchmarks
├── BitReader_ReadBits_Benchmark.cs
├── BitReader_ReadByte_Benchmark.cs
├── BitReader_ReadInt_Benchmark.cs
├── BitWriter_WriteBits_Benchmark.cs
├── BitWriter_WriteByte_Benchmark.cs
├── BitWriter_WriteInt_Benchmark.cs
├── ByteReaderBenchmark.cs
├── ByteWriterBenchmark.cs
├── NetCode.Benchmarks.csproj
├── NetStack
│ └── BitBuffer.cs
└── Program.cs
├── NetCode.Demo
├── NetCode.Demo.csproj
└── Program.cs
├── NetCode.UnitTests
├── BitReaderAndWriterTests.cs
├── BitReaderTests.cs
├── BitWriterTests.cs
├── ByteReaderAndWriterTests.cs
├── ByteReaderTests.cs
├── ByteWriterTests.cs
├── Diff
│ ├── BitReaderAndWriterTests.cs
│ ├── BitReaderTests.cs
│ └── BitWriterTests.cs
├── DiffAndQuantization
│ └── BitReaderAndWriterTests.cs
├── MathTests.cs
├── NetCode.UnitTests.csproj
├── Quantization
│ ├── BitReaderAndWriter.Float.Tests.cs
│ ├── BitReaderAndWriter.Int.Tests.cs
│ ├── BitReaderAndWriter.UInt.Tests.cs
│ └── BitWriter.Int.Tests.cs
└── SevenBitEncodingTests.cs
├── NetCode.sln
├── NetCode
├── BitConverterNetStandart20.cs
├── BitReader.cs
├── BitWriter.cs
├── ByteReader.cs
├── ByteWriter.cs
├── Extensions
│ ├── BitReader.Byte.Extensions.cs
│ ├── BitReader.Double.Extensions.cs
│ ├── BitReader.Float.Extensions.cs
│ ├── BitReader.Int.Extensions.cs
│ ├── BitReader.Long.Extensions.cs
│ ├── BitReader.SevenBitEncoding.Extensions.cs
│ ├── BitReader.Short.Extensions.cs
│ ├── BitReader.String.Extensions.cs
│ ├── BitReader.UInt.Extensions.cs
│ ├── BitReader.ULong.Extensions.cs
│ ├── BitReader.UShort.Extensions.cs
│ ├── BitReader.Vector3.Extensions.cs
│ ├── BitWriter.Byte.Extensions.cs
│ ├── BitWriter.Double.Extensions.cs
│ ├── BitWriter.Float.Extensions.cs
│ ├── BitWriter.Int.Extensions.cs
│ ├── BitWriter.Long.Extensions.cs
│ ├── BitWriter.SevenBitEncoding.Extensions.cs
│ ├── BitWriter.Short.Extensions.cs
│ ├── BitWriter.String.Extensions.cs
│ ├── BitWriter.UInt.Extensions.cs
│ ├── BitWriter.ULong.Extensions.cs
│ ├── BitWriter.UShort.Extensions.cs
│ ├── BitWriter.Vector3.Extensions.cs
│ ├── ByteReader.Double.Extensions.cs
│ ├── ByteReader.Float.Extensions.cs
│ ├── ByteReader.Vector3.Extensions.cs
│ ├── ByteWriter.Double.Extensions.cs
│ ├── ByteWriter.Float.Extensions.cs
│ └── ByteWriter.Vector3.Extensions.cs
├── IBitReader.cs
├── IBitWriter.cs
├── IByteReader.cs
├── IByteWriter.cs
├── Limits
│ ├── ByteLimit.cs
│ ├── FloatLimit.cs
│ ├── IntLimit.cs
│ ├── ShortLimit.cs
│ ├── UIntLimit.cs
│ ├── UShortLimit.cs
│ └── Vector3Limit.cs
├── Mathf.cs
├── Mathi.cs
├── NetCode.csproj
├── NetCode.csproj.DotSettings
└── ThrowHelper.cs
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # StyleCop
65 | StyleCopReport.xml
66 |
67 | # Files built by Visual Studio
68 | *_i.c
69 | *_p.c
70 | *_h.h
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.iobj
75 | *.pch
76 | *.pdb
77 | *.ipdb
78 | *.pgc
79 | *.pgd
80 | *.rsp
81 | *.sbr
82 | *.tlb
83 | *.tli
84 | *.tlh
85 | *.tmp
86 | *.tmp_proj
87 | *_wpftmp.csproj
88 | *.log
89 | *.vspscc
90 | *.vssscc
91 | .builds
92 | *.pidb
93 | *.svclog
94 | *.scc
95 |
96 | # Chutzpah Test files
97 | _Chutzpah*
98 |
99 | # Visual C++ cache files
100 | ipch/
101 | *.aps
102 | *.ncb
103 | *.opendb
104 | *.opensdf
105 | *.sdf
106 | *.cachefile
107 | *.VC.db
108 | *.VC.VC.opendb
109 |
110 | # Visual Studio profiler
111 | *.psess
112 | *.vsp
113 | *.vspx
114 | *.sap
115 |
116 | # Visual Studio Trace Files
117 | *.e2e
118 |
119 | # TFS 2012 Local Workspace
120 | $tf/
121 |
122 | # Guidance Automation Toolkit
123 | *.gpState
124 |
125 | # ReSharper is a .NET coding add-in
126 | _ReSharper*/
127 | *.[Rr]e[Ss]harper
128 | *.DotSettings.user
129 |
130 | # TeamCity is a build add-in
131 | _TeamCity*
132 |
133 | # DotCover is a Code Coverage Tool
134 | *.dotCover
135 |
136 | # Rider is a .NET IDE
137 | .idea
138 |
139 | # AxoCover is a Code Coverage Tool
140 | .axoCover/*
141 | !.axoCover/settings.json
142 |
143 | # Visual Studio code coverage results
144 | *.coverage
145 | *.coveragexml
146 |
147 | # NCrunch
148 | _NCrunch_*
149 | .*crunch*.local.xml
150 | nCrunchTemp_*
151 |
152 | # MightyMoose
153 | *.mm.*
154 | AutoTest.Net/
155 |
156 | # Web workbench (sass)
157 | .sass-cache/
158 |
159 | # Installshield output folder
160 | [Ee]xpress/
161 |
162 | # DocProject is a documentation generator add-in
163 | DocProject/buildhelp/
164 | DocProject/Help/*.HxT
165 | DocProject/Help/*.HxC
166 | DocProject/Help/*.hhc
167 | DocProject/Help/*.hhk
168 | DocProject/Help/*.hhp
169 | DocProject/Help/Html2
170 | DocProject/Help/html
171 |
172 | # Click-Once directory
173 | publish/
174 |
175 | # Publish Web Output
176 | *.[Pp]ublish.xml
177 | *.azurePubxml
178 | # Note: Comment the next line if you want to checkin your web deploy settings,
179 | # but database connection strings (with potential passwords) will be unencrypted
180 | *.pubxml
181 | *.publishproj
182 |
183 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
184 | # checkin your Azure Web App publish settings, but sensitive information contained
185 | # in these scripts will be unencrypted
186 | PublishScripts/
187 |
188 | # NuGet Packages
189 | *.nupkg
190 | # NuGet Symbol Packages
191 | *.snupkg
192 | # The packages folder can be ignored because of Package Restore
193 | **/[Pp]ackages/*
194 | # except build/, which is used as an MSBuild target.
195 | !**/[Pp]ackages/build/
196 | # Uncomment if necessary however generally it will be regenerated when needed
197 | #!**/[Pp]ackages/repositories.config
198 | # NuGet v3's project.json files produces more ignorable files
199 | *.nuget.props
200 | *.nuget.targets
201 |
202 | # Microsoft Azure Build Output
203 | csx/
204 | *.build.csdef
205 |
206 | # Microsoft Azure Emulator
207 | ecf/
208 | rcf/
209 |
210 | # Windows Store app package directories and files
211 | AppPackages/
212 | BundleArtifacts/
213 | Package.StoreAssociation.xml
214 | _pkginfo.txt
215 | *.appx
216 | *.appxbundle
217 | *.appxupload
218 |
219 | # Visual Studio cache files
220 | # files ending in .cache can be ignored
221 | *.[Cc]ache
222 | # but keep track of directories ending in .cache
223 | !?*.[Cc]ache/
224 |
225 | # Others
226 | ClientBin/
227 | ~$*
228 | *~
229 | *.dbmdl
230 | *.dbproj.schemaview
231 | *.jfm
232 | *.pfx
233 | *.publishsettings
234 | orleans.codegen.cs
235 |
236 | # Including strong name files can present a security risk
237 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
238 | #*.snk
239 |
240 | # Since there are multiple workflows, uncomment next line to ignore bower_components
241 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
242 | #bower_components/
243 |
244 | # RIA/Silverlight projects
245 | Generated_Code/
246 |
247 | # Backup & report files from converting an old project file
248 | # to a newer Visual Studio version. Backup files are not needed,
249 | # because we have git ;-)
250 | _UpgradeReport_Files/
251 | Backup*/
252 | UpgradeLog*.XML
253 | UpgradeLog*.htm
254 | ServiceFabricBackup/
255 | *.rptproj.bak
256 |
257 | # SQL Server files
258 | *.mdf
259 | *.ldf
260 | *.ndf
261 |
262 | # Business Intelligence projects
263 | *.rdl.data
264 | *.bim.layout
265 | *.bim_*.settings
266 | *.rptproj.rsuser
267 | *- [Bb]ackup.rdl
268 | *- [Bb]ackup ([0-9]).rdl
269 | *- [Bb]ackup ([0-9][0-9]).rdl
270 |
271 | # Microsoft Fakes
272 | FakesAssemblies/
273 |
274 | # GhostDoc plugin setting file
275 | *.GhostDoc.xml
276 |
277 | # Node.js Tools for Visual Studio
278 | .ntvs_analysis.dat
279 | node_modules/
280 |
281 | # Visual Studio 6 build log
282 | *.plg
283 |
284 | # Visual Studio 6 workspace options file
285 | *.opt
286 |
287 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
288 | *.vbw
289 |
290 | # Visual Studio LightSwitch build output
291 | **/*.HTMLClient/GeneratedArtifacts
292 | **/*.DesktopClient/GeneratedArtifacts
293 | **/*.DesktopClient/ModelManifest.xml
294 | **/*.Server/GeneratedArtifacts
295 | **/*.Server/ModelManifest.xml
296 | _Pvt_Extensions
297 |
298 | # Paket dependency manager
299 | .paket/paket.exe
300 | paket-files/
301 |
302 | # FAKE - F# Make
303 | .fake/
304 |
305 | # CodeRush personal settings
306 | .cr/personal
307 |
308 | # Python Tools for Visual Studio (PTVS)
309 | __pycache__/
310 | *.pyc
311 |
312 | # Cake - Uncomment if you are using it
313 | # tools/**
314 | # !tools/packages.config
315 |
316 | # Tabs Studio
317 | *.tss
318 |
319 | # Telerik's JustMock configuration file
320 | *.jmconfig
321 |
322 | # BizTalk build output
323 | *.btp.cs
324 | *.btm.cs
325 | *.odx.cs
326 | *.xsd.cs
327 |
328 | # OpenCover UI analysis results
329 | OpenCover/
330 |
331 | # Azure Stream Analytics local run output
332 | ASALocalRun/
333 |
334 | # MSBuild Binary and Structured Log
335 | *.binlog
336 |
337 | # NVidia Nsight GPU debugger configuration file
338 | *.nvuser
339 |
340 | # MFractors (Xamarin productivity tool) working folder
341 | .mfractor/
342 |
343 | # Local History for Visual Studio
344 | .localhistory/
345 |
346 | # BeatPulse healthcheck temp database
347 | healthchecksdb
348 |
349 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
350 | MigrationBackup/
351 |
352 | # Ionide (cross platform F# VS Code tools) working folder
353 | .ionide/
354 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Levchenkov
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 |
--------------------------------------------------------------------------------
/NetCode.Benchmarks/BitReader_ReadBits_Benchmark.cs:
--------------------------------------------------------------------------------
1 | using BenchmarkDotNet.Attributes;
2 | using NetStack.Serialization;
3 |
4 | namespace NetCode.Benchmarks;
5 |
6 | ///
7 | /// BenchmarkDotNet=v0.13.1, OS=macOS Big Sur 11.5.2 (20G95) [Darwin 20.6.0]
8 | /// Intel Core i7-9750H CPU 2.60GHz, 1 CPU, 12 logical and 6 physical cores
9 | /// .NET SDK=6.0.100
10 | /// [Host] : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
11 | /// DefaultJob : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
12 | ///
13 | /// | Method | Mean | Error | StdDev | Ratio |
14 | /// |--------------------------- |---------:|--------:|--------:|------:|
15 | /// | NetCode_BitReader_ReadBits | 560.8 ns | 3.34 ns | 3.12 ns | 1.00 |
16 | /// | NetStack_BitBuffer_Read | 939.4 ns | 1.76 ns | 1.47 ns | 1.68 |
17 | ///
18 | ///
19 | public class BitReader_ReadBits_Benchmark
20 | {
21 | private const int ReadCount = 255;
22 | private const int BitsPerRead = 11;
23 |
24 | private BitReader _bitReader;
25 | private byte[] _array;
26 |
27 | private BitBuffer _bitBuffer;
28 |
29 | [GlobalSetup]
30 | public void GlobalSetup()
31 | {
32 | var arrayLength = (int)Math.Ceiling((float) ReadCount * BitsPerRead / 8);
33 |
34 | _array = new byte[arrayLength]; // 351
35 | for (int i = 0; i < _array.Length; i++)
36 | {
37 | _array[i] = (byte)i;
38 | }
39 |
40 | _bitReader = new BitReader();
41 | _bitBuffer = new BitBuffer();
42 | }
43 |
44 | [Benchmark(Baseline = true)]
45 | public uint NetCode_BitReader_ReadBits()
46 | {
47 | uint s = 0;
48 |
49 | _bitReader.SetArray(_array);
50 |
51 | for (int i = 0; i < ReadCount; i++)
52 | {
53 | var value = _bitReader.ReadBits(BitsPerRead);
54 | s += value;
55 | }
56 |
57 | return s;
58 | }
59 |
60 | [Benchmark]
61 | public uint NetStack_BitBuffer_Read()
62 | {
63 | uint s = 0;
64 |
65 | _bitBuffer.FromArray(_array, _array.Length);
66 |
67 | for (int i = 0; i < ReadCount; i++)
68 | {
69 | var value = _bitBuffer.Read(BitsPerRead);
70 | s += value;
71 | }
72 |
73 | return s;
74 | }
75 | }
--------------------------------------------------------------------------------
/NetCode.Benchmarks/BitReader_ReadByte_Benchmark.cs:
--------------------------------------------------------------------------------
1 | using BenchmarkDotNet.Attributes;
2 | using NetStack.Serialization;
3 |
4 | namespace NetCode.Benchmarks;
5 |
6 | ///
7 | /// BenchmarkDotNet=v0.13.1, OS=macOS Big Sur 11.5.2 (20G95) [Darwin 20.6.0]
8 | /// Intel Core i7-9750H CPU 2.60GHz, 1 CPU, 12 logical and 6 physical cores
9 | /// .NET SDK=6.0.100
10 | /// [Host] : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
11 | /// DefaultJob : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
12 | ///
13 | /// | Method | Mean | Error | StdDev | Ratio | RatioSD |
14 | /// |----------------------------- |---------:|---------:|---------:|------:|--------:|
15 | /// | BitReader_Aligned_ReadByte | 455.3 ns | 1.14 ns | 1.01 ns | 1.00 | 0.00 |
16 | /// | BitReader_UnAligned_ReadByte | 518.5 ns | 3.13 ns | 2.61 ns | 1.14 | 0.01 |
17 | /// | BitBuffer_Aligned_ReadByte | 867.8 ns | 1.55 ns | 1.45 ns | 1.91 | 0.01 |
18 | /// | BitBuffer_UnAligned_ReadByte | 965.5 ns | 12.69 ns | 11.87 ns | 2.12 | 0.03 |
19 | ///
20 | ///
21 | public class BitReader_ReadByte_Benchmark
22 | {
23 | private const int ReadCount = 255;
24 | private const int BitsPerRead = 8;
25 |
26 | private BitReader _bitReader;
27 | private byte[] _array;
28 |
29 | private BitBuffer _bitBuffer;
30 |
31 | [GlobalSetup]
32 | public void GlobalSetup()
33 | {
34 | var arrayLength = (int)Math.Ceiling((float) ReadCount * BitsPerRead / 8) + 1;
35 |
36 | _array = new byte[arrayLength]; // 256
37 | for (int i = 0; i < _array.Length; i++)
38 | {
39 | _array[i] = (byte)i;
40 | }
41 |
42 | _bitReader = new BitReader();
43 | _bitBuffer = new BitBuffer();
44 | }
45 |
46 | [Benchmark(Baseline = true)]
47 | public int BitReader_Aligned_ReadByte()
48 | {
49 | var s = 0;
50 | _bitReader.SetArray(_array);
51 |
52 | for (int i = 0; i < ReadCount; i++)
53 | {
54 | var value = _bitReader.ReadByte();
55 | s += value;
56 | }
57 |
58 | return s;
59 | }
60 |
61 | [Benchmark]
62 | public int BitReader_UnAligned_ReadByte()
63 | {
64 | var s = 0;
65 | _bitReader.SetArray(_array);
66 |
67 | _bitReader.ReadBits(1);
68 |
69 | for (int i = 0; i < ReadCount; i++)
70 | {
71 | var value = _bitReader.ReadByte();
72 | s += value;
73 | }
74 |
75 | return s;
76 | }
77 |
78 | [Benchmark]
79 | public int BitBuffer_Aligned_ReadByte()
80 | {
81 | var s = 0;
82 | _bitBuffer.FromArray(_array, _array.Length);
83 |
84 | for (int i = 0; i < ReadCount; i++)
85 | {
86 | var value = _bitBuffer.ReadByte();
87 | s += value;
88 | }
89 |
90 | return s;
91 | }
92 |
93 | [Benchmark]
94 | public int BitBuffer_UnAligned_ReadByte()
95 | {
96 | var s = 0;
97 | _bitBuffer.FromArray(_array, _array.Length);
98 |
99 | _bitBuffer.Read(1);
100 |
101 | for (int i = 0; i < ReadCount; i++)
102 | {
103 | var value = _bitBuffer.ReadByte();
104 | s += value;
105 | }
106 |
107 | return s;
108 | }
109 | }
--------------------------------------------------------------------------------
/NetCode.Benchmarks/BitReader_ReadInt_Benchmark.cs:
--------------------------------------------------------------------------------
1 | using BenchmarkDotNet.Attributes;
2 | using NetStack.Serialization;
3 |
4 | namespace NetCode.Benchmarks;
5 |
6 | ///
7 | /// BenchmarkDotNet=v0.13.1, OS=macOS Big Sur 11.5.2 (20G95) [Darwin 20.6.0]
8 | /// Intel Core i7-9750H CPU 2.60GHz, 1 CPU, 12 logical and 6 physical cores
9 | /// .NET SDK=6.0.100
10 | /// [Host] : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
11 | /// DefaultJob : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
12 | ///
13 | /// | Method | Mean | Error | StdDev | Ratio | RatioSD |
14 | /// |---------------------------- |-----------:|---------:|---------:|------:|--------:|
15 | /// | BitReader_Aligned_ReadInt | 518.2 ns | 2.62 ns | 2.45 ns | 1.00 | 0.00 |
16 | /// | BitReader_UnAligned_ReadInt | 808.9 ns | 2.35 ns | 2.08 ns | 1.56 | 0.01 |
17 | /// | BitBuffer_Aligned_ReadInt | 2,403.0 ns | 14.81 ns | 13.85 ns | 4.64 | 0.04 |
18 | /// | BitBuffer_UnAligned_ReadInt | 2,899.6 ns | 22.16 ns | 19.64 ns | 5.60 | 0.04 |
19 | ///
20 | ///
21 | public class BitReader_ReadInt_Benchmark
22 | {
23 | private const int ReadCount = 255;
24 | private const int BitsPerRead = 32;
25 |
26 | private BitReader _bitReader;
27 | private byte[] _array;
28 |
29 | private BitBuffer _bitBuffer;
30 |
31 | [GlobalSetup]
32 | public void GlobalSetup()
33 | {
34 | var arrayLength = (int)Math.Ceiling((float) ReadCount * BitsPerRead / 8) + 1;
35 |
36 | _array = new byte[arrayLength]; // 1021
37 | for (int i = 0; i < _array.Length; i++)
38 | {
39 | _array[i] = (byte)i;
40 | }
41 |
42 | _bitReader = new BitReader();
43 | _bitBuffer = new BitBuffer();
44 |
45 | }
46 |
47 | [Benchmark(Baseline = true)]
48 | public int BitReader_Aligned_ReadInt()
49 | {
50 | var s = 0;
51 | _bitReader.SetArray(_array);
52 |
53 | for (int i = 0; i < ReadCount; i++)
54 | {
55 | var value = _bitReader.ReadInt();
56 | s += value;
57 | }
58 |
59 | return s;
60 | }
61 |
62 | [Benchmark]
63 | public int BitReader_UnAligned_ReadInt()
64 | {
65 | var s = 0;
66 | _bitReader.SetArray(_array);
67 |
68 | _bitReader.ReadBits(1);
69 |
70 | for (int i = 0; i < ReadCount; i++)
71 | {
72 | var value = _bitReader.ReadInt();
73 | s += value;
74 | }
75 |
76 | return s;
77 | }
78 |
79 | [Benchmark]
80 | public int BitBuffer_Aligned_ReadInt()
81 | {
82 | var s = 0;
83 | _bitBuffer.FromArray(_array, _array.Length);
84 |
85 | for (int i = 0; i < ReadCount; i++)
86 | {
87 | var value = _bitBuffer.ReadInt();
88 | s += value;
89 | }
90 |
91 | return s;
92 | }
93 |
94 | [Benchmark]
95 | public int BitBuffer_UnAligned_ReadInt()
96 | {
97 | var s = 0;
98 | _bitBuffer.FromArray(_array, _array.Length);
99 |
100 | _bitBuffer.Read(1);
101 |
102 | for (int i = 0; i < ReadCount; i++)
103 | {
104 | var value = _bitBuffer.ReadInt();
105 | s += value;
106 | }
107 |
108 | return s;
109 | }
110 | }
--------------------------------------------------------------------------------
/NetCode.Benchmarks/BitWriter_WriteBits_Benchmark.cs:
--------------------------------------------------------------------------------
1 | using BenchmarkDotNet.Attributes;
2 | using NetStack.Serialization;
3 |
4 | namespace NetCode.Benchmarks;
5 |
6 | ///
7 | /// BenchmarkDotNet=v0.13.1, OS=macOS Big Sur 11.5.2 (20G95) [Darwin 20.6.0]
8 | /// Intel Core i7-9750H CPU 2.60GHz, 1 CPU, 12 logical and 6 physical cores
9 | /// .NET SDK=6.0.100
10 | /// [Host] : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
11 | /// DefaultJob : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
12 | ///
13 | /// | Method | Mean | Error | StdDev | Ratio |
14 | /// |---------------------------- |---------:|--------:|--------:|------:|
15 | /// | NetCode_BitWriter_WriteBits | 572.1 ns | 2.80 ns | 2.48 ns | 1.00 |
16 | /// | NetStack_BitBuffer_Add | 972.0 ns | 7.91 ns | 7.40 ns | 1.70 |
17 | ///
18 | ///
19 | public class BitWriter_WriteBits_Benchmark
20 | {
21 | private const int WriteCount = 255;
22 | private const int BitsPerWrite = 7;
23 |
24 | private BitWriter _bitWriter;
25 | private BitBuffer _bitBuffer;
26 |
27 | private byte[] _resultArray;
28 |
29 | [GlobalSetup]
30 | public void GlobalSetup()
31 | {
32 | _bitWriter = new BitWriter();
33 | _bitBuffer = new BitBuffer();
34 |
35 | var arrayLength = (int)Math.Ceiling((float) WriteCount * BitsPerWrite / 8);
36 | _resultArray = new byte[arrayLength];
37 | }
38 |
39 | [Benchmark(Baseline = true)]
40 | public byte NetCode_BitWriter_WriteBits()
41 | {
42 | _bitWriter.Clear();
43 |
44 | for (byte i = 0; i < WriteCount; i++)
45 | {
46 | _bitWriter.WriteBits(BitsPerWrite, i);
47 | }
48 |
49 | _bitWriter.Flush();
50 |
51 | return _bitWriter.Array[0];
52 | }
53 |
54 | [Benchmark]
55 | public byte NetStack_BitBuffer_Add()
56 | {
57 | _bitBuffer.Clear();
58 |
59 | for (byte i = 0; i < WriteCount; i++)
60 | {
61 | _bitBuffer.Add(BitsPerWrite, i);
62 | }
63 |
64 | _bitBuffer.ToArray(_resultArray);
65 |
66 | return _resultArray[0];
67 | }
68 | }
--------------------------------------------------------------------------------
/NetCode.Benchmarks/BitWriter_WriteByte_Benchmark.cs:
--------------------------------------------------------------------------------
1 | using BenchmarkDotNet.Attributes;
2 | using NetStack.Serialization;
3 |
4 | namespace NetCode.Benchmarks;
5 |
6 | ///
7 | /// BenchmarkDotNet=v0.13.1, OS=macOS Big Sur 11.5.2 (20G95) [Darwin 20.6.0]
8 | /// Intel Core i7-9750H CPU 2.60GHz, 1 CPU, 12 logical and 6 physical cores
9 | /// .NET SDK=6.0.100
10 | /// [Host] : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
11 | /// DefaultJob : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
12 | ///
13 | /// | Method | Mean | Error | StdDev | Ratio | RatioSD |
14 | /// |---------------------------- |---------:|--------:|--------:|------:|--------:|
15 | /// | BitWriter_Align_WriteByte | 392.4 ns | 1.38 ns | 1.22 ns | 1.00 | 0.00 |
16 | /// | BitWriter_UnAlign_WriteByte | 651.4 ns | 3.38 ns | 3.16 ns | 1.66 | 0.01 |
17 | /// | BitBuffer_Align_AddByte | 995.3 ns | 5.96 ns | 5.28 ns | 2.54 | 0.01 |
18 | /// | BitBuffer_UnAlign_AddByte | 996.1 ns | 6.84 ns | 6.40 ns | 2.54 | 0.02 |
19 | ///
20 | ///
21 | public class BitWriter_WriteByte_Benchmark
22 | {
23 | private const int WriteCount = 255;
24 | private const int BitsPerWrite = 8;
25 |
26 | private BitWriter _bitWriter;
27 | private BitBuffer _bitBuffer;
28 |
29 | private byte[] _resultArray;
30 |
31 | [GlobalSetup]
32 | public void GlobalSetup()
33 | {
34 | _bitWriter = new BitWriter();
35 | _bitBuffer = new BitBuffer();
36 |
37 | var arrayLength = (int)Math.Ceiling((float) WriteCount * BitsPerWrite / 8);
38 | _resultArray = new byte[arrayLength];
39 | }
40 |
41 | [Benchmark(Baseline = true)]
42 | public byte BitWriter_Align_WriteByte()
43 | {
44 | _bitWriter.Clear();
45 |
46 | for (byte i = 0; i < WriteCount; i++)
47 | {
48 | _bitWriter.Write(i);
49 | }
50 |
51 | _bitWriter.Flush();
52 |
53 | return _bitWriter.Array[0];
54 | }
55 |
56 | [Benchmark]
57 | public byte BitWriter_UnAlign_WriteByte()
58 | {
59 | _bitWriter.Clear();
60 | _bitWriter.WriteBits(1, 1);
61 |
62 | for (byte i = 0; i < WriteCount; i++)
63 | {
64 | _bitWriter.Write(i);
65 | }
66 |
67 | _bitWriter.Flush();
68 |
69 | return _bitWriter.Array[0];
70 | }
71 |
72 | [Benchmark]
73 | public byte BitBuffer_Align_AddByte()
74 | {
75 | _bitBuffer.Clear();
76 |
77 | for (byte i = 0; i < WriteCount; i++)
78 | {
79 | _bitBuffer.AddByte(i);
80 | }
81 |
82 | _bitBuffer.ToArray(_resultArray);
83 |
84 | return _resultArray[0];
85 | }
86 |
87 | [Benchmark]
88 | public byte BitBuffer_UnAlign_AddByte()
89 | {
90 | _bitBuffer.Clear();
91 | _bitBuffer.Add(1, 1);
92 |
93 | for (byte i = 0; i < WriteCount; i++)
94 | {
95 | _bitBuffer.AddByte(i);
96 | }
97 |
98 | _bitBuffer.ToArray(_resultArray);
99 |
100 | return _resultArray[0];
101 | }
102 | }
--------------------------------------------------------------------------------
/NetCode.Benchmarks/BitWriter_WriteInt_Benchmark.cs:
--------------------------------------------------------------------------------
1 | using BenchmarkDotNet.Attributes;
2 | using NetStack.Serialization;
3 |
4 | namespace NetCode.Benchmarks;
5 |
6 | ///
7 | /// BenchmarkDotNet=v0.13.1, OS=macOS Big Sur 11.5.2 (20G95) [Darwin 20.6.0]
8 | /// Intel Core i7-9750H CPU 2.60GHz, 1 CPU, 12 logical and 6 physical cores
9 | /// .NET SDK=6.0.100
10 | /// [Host] : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
11 | /// DefaultJob : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
12 | ///
13 | /// | Method | Mean | Error | StdDev | Ratio | RatioSD |
14 | /// |--------------------------- |-----------:|---------:|---------:|------:|--------:|
15 | /// | BitWriter_Align_IntWrite | 520.6 ns | 3.64 ns | 3.40 ns | 1.00 | 0.00 |
16 | /// | BitWriter_UnAlign_IntWrite | 851.2 ns | 6.46 ns | 5.72 ns | 1.63 | 0.02 |
17 | /// | BitBuffer_Align_AddInt | 2,118.7 ns | 13.12 ns | 12.27 ns | 4.07 | 0.05 |
18 | /// | BitBuffer_UnAlign_AddInt | 1,945.2 ns | 11.14 ns | 9.30 ns | 3.74 | 0.03 |
19 | ///
20 | ///
21 | ///
22 | public class BitWriter_WriteInt_Benchmark
23 | {
24 | private const int WriteCount = 255;
25 | private const int BitsPerWrite = 32;
26 |
27 | private BitWriter _bitWriter;
28 | private BitBuffer _bitBuffer;
29 |
30 | private byte[] _resultArray;
31 |
32 | [GlobalSetup]
33 | public void GlobalSetup()
34 | {
35 | _bitWriter = new BitWriter();
36 | _bitBuffer = new BitBuffer();
37 |
38 | var arrayLength = (int)Math.Ceiling((float) WriteCount * BitsPerWrite / 8) + 1;
39 | _resultArray = new byte[arrayLength];
40 | }
41 |
42 | [Benchmark(Baseline = true)]
43 | public byte BitWriter_Align_IntWrite()
44 | {
45 | _bitWriter.Clear();
46 |
47 | for (int i = 0; i < WriteCount; i++)
48 | {
49 | _bitWriter.Write(i);
50 | }
51 |
52 | _bitWriter.Flush();
53 |
54 | return _bitWriter.Array[0];
55 | }
56 |
57 | [Benchmark]
58 | public byte BitWriter_UnAlign_IntWrite()
59 | {
60 | _bitWriter.Clear();
61 | _bitWriter.WriteBits(1, 1);
62 |
63 | for (int i = 0; i < WriteCount; i++)
64 | {
65 | _bitWriter.Write(i);
66 | }
67 |
68 | _bitWriter.Flush();
69 |
70 | return _bitWriter.Array[0];
71 | }
72 |
73 | [Benchmark]
74 | public byte BitBuffer_Align_AddInt()
75 | {
76 | _bitBuffer.Clear();
77 |
78 | for (int i = 0; i < WriteCount; i++)
79 | {
80 | _bitBuffer.AddInt(i);
81 | }
82 |
83 | _bitBuffer.ToArray(_resultArray);
84 |
85 | return _resultArray[0];
86 | }
87 |
88 | [Benchmark]
89 | public byte BitBuffer_UnAlign_AddInt()
90 | {
91 | _bitBuffer.Clear();
92 | _bitBuffer.Add(1, 1);
93 |
94 | for (int i = 0; i < WriteCount; i++)
95 | {
96 | _bitBuffer.AddInt(i);
97 | }
98 |
99 | _bitBuffer.ToArray(_resultArray);
100 |
101 | return _resultArray[0];
102 | }
103 | }
--------------------------------------------------------------------------------
/NetCode.Benchmarks/ByteReaderBenchmark.cs:
--------------------------------------------------------------------------------
1 | using BenchmarkDotNet.Attributes;
2 |
3 | namespace NetCode.Benchmarks;
4 |
5 | ///
6 | /// BenchmarkDotNet=v0.13.1, OS=macOS Big Sur 11.5.2 (20G95) [Darwin 20.6.0]
7 | /// Intel Core i7-9750H CPU 2.60GHz, 1 CPU, 12 logical and 6 physical cores
8 | /// .NET SDK=6.0.100
9 | /// [Host] : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
10 | /// DefaultJob : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
11 | ///
12 | /// | Method | Mean | Error | StdDev | Ratio | RatioSD | Allocated |
13 | /// |----------------- |-----------:|--------:|--------:|------:|--------:|----------:|
14 | /// | BinaryPrimitives | 328.4 ns | 1.24 ns | 1.10 ns | 1.00 | 0.00 | - |
15 | /// | ByteReader | 329.4 ns | 1.71 ns | 1.60 ns | 1.00 | 0.01 | - |
16 | /// | BitReader | 457.8 ns | 1.09 ns | 0.91 ns | 1.39 | 0.01 | - |
17 | /// | BinaryReader | 1,205.8 ns | 6.75 ns | 6.31 ns | 3.67 | 0.02 | - |
18 | ///
19 | ///
20 | [MemoryDiagnoser]
21 | public class ByteReaderBenchmark
22 | {
23 | private ByteReader _byteReader;
24 | private BitReader _bitReader;
25 | private byte[] _array;
26 |
27 | private BinaryReader _binaryReader;
28 |
29 | [GlobalSetup]
30 | public void GlobalSetup()
31 | {
32 | _array = new byte[2000];
33 | for (int i = 0; i < _array.Length; i++)
34 | {
35 | _array[i] = (byte)i;
36 | }
37 |
38 | _byteReader = new ByteReader(_array);
39 | _bitReader = new BitReader(_array);
40 |
41 | _binaryReader = new BinaryReader(new MemoryStream(_array));
42 | }
43 |
44 | [Benchmark(Baseline = true)]
45 | public int BinaryPrimitives()
46 | {
47 | var s = 0;
48 | var count = 0;
49 | Span span = _array.AsSpan();
50 |
51 | for (int i = 0; i < 255; i++)
52 | {
53 | var value = System.Buffers.Binary.BinaryPrimitives.ReadInt32LittleEndian(span);
54 | s += value;
55 |
56 | span = span.Slice(4);
57 | count += 4;
58 | }
59 |
60 | return s;
61 | }
62 |
63 | [Benchmark]
64 | public int Shift()
65 | {
66 | var s = 0;
67 | int index = 0;
68 |
69 | for (int i = 0; i < 255; i++)
70 | {
71 | int value = _array[index++];
72 | value |= _array[index++] << 8;
73 | value |= _array[index++] << 16;
74 | value |= _array[index++] << 24;
75 |
76 | s += value;
77 | }
78 |
79 | return s;
80 | }
81 |
82 |
83 | [Benchmark]
84 | public int ByteReader()
85 | {
86 | var s = 0;
87 | _byteReader.Reset();
88 |
89 | for (int i = 0; i < 255; i++)
90 | {
91 | var value = _byteReader.ReadInt();
92 | s += value;
93 | }
94 |
95 | return s;
96 | }
97 |
98 | [Benchmark]
99 | public int BitReader()
100 | {
101 | var s = 0;
102 | _bitReader.Reset();
103 |
104 | for (int i = 0; i < 255; i++)
105 | {
106 | var value = _bitReader.ReadInt();
107 | s += value;
108 | }
109 |
110 | return s;
111 | }
112 |
113 | [Benchmark]
114 | public int BinaryReader()
115 | {
116 | var s = 0;
117 | _binaryReader.BaseStream.Seek(0, SeekOrigin.Begin);
118 |
119 | for (int i = 0; i < 255; i++)
120 | {
121 | var value = _binaryReader.ReadInt32();
122 | s += value;
123 | }
124 |
125 | return s;
126 | }
127 | }
--------------------------------------------------------------------------------
/NetCode.Benchmarks/ByteWriterBenchmark.cs:
--------------------------------------------------------------------------------
1 | using BenchmarkDotNet.Attributes;
2 |
3 | namespace NetCode.Benchmarks;
4 |
5 | ///
6 | /// BenchmarkDotNet=v0.13.1, OS=macOS Big Sur 11.5.2 (20G95) [Darwin 20.6.0]
7 | /// Intel Core i7-9750H CPU 2.60GHz, 1 CPU, 12 logical and 6 physical cores
8 | /// .NET SDK=6.0.100
9 | /// [Host] : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
10 | /// DefaultJob : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
11 | ///
12 | /// | Method | Mean | Error | StdDev | Ratio | RatioSD | Allocated |
13 | /// |----------------- |-----------:|---------:|---------:|------:|--------:|----------:|
14 | /// | BinaryPrimitives | 325.7 ns | 1.39 ns | 1.30 ns | 1.00 | 0.00 | - |
15 | /// | ByteWriter | 330.3 ns | 1.67 ns | 1.48 ns | 1.01 | 0.01 | - |
16 | /// | BitWriter | 338.0 ns | 1.69 ns | 1.58 ns | 1.04 | 0.00 | - |
17 | /// | BinaryWriter | 2,344.7 ns | 12.08 ns | 10.71 ns | 7.20 | 0.03 | - |
18 | ///
19 | ///
20 | [MemoryDiagnoser]
21 | public class ByteWriterBenchmark
22 | {
23 | private ByteWriter _byteWriter;
24 | private BitWriter _bitWriter;
25 | private byte[] _array;
26 |
27 | private BinaryWriter _binaryWriter;
28 |
29 | [GlobalSetup]
30 | public void GlobalSetup()
31 | {
32 | _array = new byte[2000];
33 | _byteWriter = new ByteWriter(_array);
34 | _bitWriter = new BitWriter(_array);
35 |
36 | _binaryWriter = new BinaryWriter(new MemoryStream(_array));
37 | }
38 |
39 | [Benchmark(Baseline = true)]
40 | public int BinaryPrimitives()
41 | {
42 | var count = 0;
43 | Span span = _array.AsSpan();
44 |
45 | for (int i = 0; i < 255; i++)
46 | {
47 | System.Buffers.Binary.BinaryPrimitives.WriteInt32LittleEndian(span, i);
48 | span = span.Slice(4);
49 | count += 4;
50 | }
51 |
52 | return count;
53 | }
54 |
55 | [Benchmark]
56 | public byte[] Shift()
57 | {
58 | var index = 0;
59 |
60 | for (int i = 0; i < 255; i++)
61 | {
62 | _array[index++] = (byte)(i);
63 | _array[index++] = (byte)(i >> 8);
64 | _array[index++] = (byte)(i >> 16);
65 | _array[index++] = (byte)(i >> 24);
66 | }
67 |
68 | return _array;
69 | }
70 |
71 | [Benchmark]
72 | public int ByteWriter()
73 | {
74 | _byteWriter.Clear();
75 |
76 | for (int i = 0; i < 255; i++)
77 | {
78 | _byteWriter.Write(i);
79 | }
80 |
81 | return _byteWriter.Count;
82 | }
83 |
84 | [Benchmark]
85 | public int BitWriter()
86 | {
87 | _bitWriter.Clear();
88 |
89 | for (int i = 0; i < 255; i++)
90 | {
91 | _bitWriter.Write(i);
92 | }
93 |
94 | return _bitWriter.BytesCount;
95 | }
96 |
97 | [Benchmark]
98 | public long BinaryWriter()
99 | {
100 | _binaryWriter.Seek(0, SeekOrigin.Begin);
101 |
102 | for (int i = 0; i < 255; i++)
103 | {
104 | _binaryWriter.Write(i);
105 | }
106 |
107 | _binaryWriter.Flush();
108 |
109 | return _binaryWriter.BaseStream.Position;
110 | }
111 | }
--------------------------------------------------------------------------------
/NetCode.Benchmarks/NetCode.Benchmarks.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/NetCode.Benchmarks/NetStack/BitBuffer.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 Stanislav Denisov, Maxim Munnig
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | * SOFTWARE.
21 | */
22 |
23 | /*
24 | * Copyright (c) 2018 Alexander Shoulson
25 | *
26 | * This software is provided 'as-is', without any express or implied
27 | * warranty. In no event will the authors be held liable for any damages
28 | * arising from the use of this software.
29 | * Permission is granted to anyone to use this software for any purpose,
30 | * including commercial applications, and to alter it and redistribute it
31 | * freely, subject to the following restrictions:
32 | *
33 | * 1. The origin of this software must not be misrepresented; you must not
34 | * claim that you wrote the original software. If you use this software
35 | * in a product, an acknowledgment in the product documentation would be
36 | * appreciated but is not required.
37 | * 2. Altered source versions must be plainly marked as such, and must not be
38 | * misrepresented as being the original software.
39 | * 3. This notice may not be removed or altered from any source distribution.
40 | */
41 |
42 | using System;
43 | using System.Diagnostics;
44 | using System.Runtime.CompilerServices;
45 | using System.Text;
46 |
47 | #if ENABLE_MONO || ENABLE_IL2CPP
48 | using UnityEngine.Assertions;
49 | #endif
50 |
51 | namespace NetStack.Serialization {
52 | public class BitBuffer {
53 | private const int defaultCapacity = 375; // 375 * 4 = 1500 bytes
54 | private const int stringLengthBits = 8;
55 | private const int stringLengthMax = (1 << stringLengthBits) - 1; // 255
56 | private const int bitsASCII = 7;
57 | private const int growFactor = 2;
58 | private const int minGrow = 1;
59 | private int readPosition;
60 | private int nextPosition;
61 | private uint[] chunks;
62 |
63 | public BitBuffer(int capacity = defaultCapacity) {
64 | readPosition = 0;
65 | nextPosition = 0;
66 | chunks = new uint[capacity];
67 | }
68 |
69 | public int Length {
70 | get {
71 | return ((nextPosition - 1) >> 3) + 1;
72 | }
73 | }
74 |
75 | public bool IsFinished {
76 | get {
77 | return nextPosition == readPosition;
78 | }
79 | }
80 |
81 | [MethodImpl(256)]
82 | public void Clear() {
83 | readPosition = 0;
84 | nextPosition = 0;
85 | }
86 |
87 | [MethodImpl(256)]
88 | public BitBuffer Add(int numBits, uint value) {
89 | #if ENABLE_MONO || ENABLE_IL2CPP
90 | Assert.IsFalse(numBits < 0); // Pushing negative bits
91 | Assert.IsFalse(numBits > 32); // Pushing too many bits
92 | #else
93 | Debug.Assert(!(numBits < 0));
94 | Debug.Assert(!(numBits > 32));
95 | #endif
96 |
97 | int index = nextPosition >> 5;
98 | int used = nextPosition & 0x0000001F;
99 |
100 | if ((index + 1) >= chunks.Length)
101 | ExpandArray();
102 |
103 | ulong chunkMask = ((1UL << used) - 1);
104 | ulong scratch = chunks[index] & chunkMask;
105 | ulong result = scratch | ((ulong)value << used);
106 |
107 | chunks[index] = (uint)result;
108 | chunks[index + 1] = (uint)(result >> 32);
109 | nextPosition += numBits;
110 |
111 | return this;
112 | }
113 |
114 | [MethodImpl(256)]
115 | public uint Read(int numBits) {
116 | uint result = Peek(numBits);
117 |
118 | readPosition += numBits;
119 |
120 | return result;
121 | }
122 |
123 | [MethodImpl(256)]
124 | public uint Peek(int numBits) {
125 | #if ENABLE_MONO || ENABLE_IL2CPP
126 | Assert.IsFalse(numBits < 0); // Pushing negative bits
127 | Assert.IsFalse(numBits > 32); // Pushing too many bits
128 | #else
129 | Debug.Assert(!(numBits < 0));
130 | Debug.Assert(!(numBits > 32));
131 | #endif
132 |
133 | int index = readPosition >> 5;
134 | int used = readPosition & 0x0000001F;
135 |
136 | ulong chunkMask = ((1UL << numBits) - 1) << used;
137 | ulong scratch = (ulong)chunks[index];
138 |
139 | if ((index + 1) < chunks.Length)
140 | scratch |= (ulong)chunks[index + 1] << 32;
141 |
142 | ulong result = (scratch & chunkMask) >> used;
143 |
144 | return (uint)result;
145 | }
146 |
147 | public int ToArray(byte[] data) {
148 | Add(1, 1);
149 |
150 | int numChunks = (nextPosition >> 5) + 1;
151 | int length = data.Length;
152 |
153 | for (int i = 0; i < numChunks; i++) {
154 | int dataIdx = i * 4;
155 | uint chunk = chunks[i];
156 |
157 | if (dataIdx < length)
158 | data[dataIdx] = (byte)(chunk);
159 |
160 | if (dataIdx + 1 < length)
161 | data[dataIdx + 1] = (byte)(chunk >> 8);
162 |
163 | if (dataIdx + 2 < length)
164 | data[dataIdx + 2] = (byte)(chunk >> 16);
165 |
166 | if (dataIdx + 3 < length)
167 | data[dataIdx + 3] = (byte)(chunk >> 24);
168 | }
169 |
170 | return Length;
171 | }
172 |
173 | public void FromArray(byte[] data, int length) {
174 | int numChunks = (length / 4) + 1;
175 |
176 | if (chunks.Length < numChunks)
177 | chunks = new uint[numChunks];
178 |
179 | for (int i = 0; i < numChunks; i++) {
180 | int dataIdx = i * 4;
181 | uint chunk = 0;
182 |
183 | if (dataIdx < length)
184 | chunk = (uint)data[dataIdx];
185 |
186 | if (dataIdx + 1 < length)
187 | chunk = chunk | (uint)data[dataIdx + 1] << 8;
188 |
189 | if (dataIdx + 2 < length)
190 | chunk = chunk | (uint)data[dataIdx + 2] << 16;
191 |
192 | if (dataIdx + 3 < length)
193 | chunk = chunk | (uint)data[dataIdx + 3] << 24;
194 |
195 | chunks[i] = chunk;
196 | }
197 |
198 | int positionInByte = FindHighestBitPosition(data[length - 1]);
199 |
200 | nextPosition = ((length - 1) * 8) + (positionInByte - 1);
201 | readPosition = 0;
202 | }
203 |
204 | #if NETSTACK_SPAN
205 | public int ToSpan(ref Span data) {
206 | Add(1, 1);
207 |
208 | int numChunks = (nextPosition >> 5) + 1;
209 | int length = data.Length;
210 |
211 | for (int i = 0; i < numChunks; i++) {
212 | int dataIdx = i * 4;
213 | uint chunk = chunks[i];
214 |
215 | if (dataIdx < length)
216 | data[dataIdx] = (byte)(chunk);
217 |
218 | if (dataIdx + 1 < length)
219 | data[dataIdx + 1] = (byte)(chunk >> 8);
220 |
221 | if (dataIdx + 2 < length)
222 | data[dataIdx + 2] = (byte)(chunk >> 16);
223 |
224 | if (dataIdx + 3 < length)
225 | data[dataIdx + 3] = (byte)(chunk >> 24);
226 | }
227 |
228 | return Length;
229 | }
230 |
231 | public void FromSpan(ref ReadOnlySpan data, int length) {
232 | int numChunks = (length / 4) + 1;
233 |
234 | if (chunks.Length < numChunks)
235 | chunks = new uint[numChunks];
236 |
237 | for (int i = 0; i < numChunks; i++) {
238 | int dataIdx = i * 4;
239 | uint chunk = 0;
240 |
241 | if (dataIdx < length)
242 | chunk = (uint)data[dataIdx];
243 |
244 | if (dataIdx + 1 < length)
245 | chunk = chunk | (uint)data[dataIdx + 1] << 8;
246 |
247 | if (dataIdx + 2 < length)
248 | chunk = chunk | (uint)data[dataIdx + 2] << 16;
249 |
250 | if (dataIdx + 3 < length)
251 | chunk = chunk | (uint)data[dataIdx + 3] << 24;
252 |
253 | chunks[i] = chunk;
254 | }
255 |
256 | int positionInByte = FindHighestBitPosition(data[length - 1]);
257 |
258 | nextPosition = ((length - 1) * 8) + (positionInByte - 1);
259 | readPosition = 0;
260 | }
261 | #endif
262 |
263 | [MethodImpl(256)]
264 | public BitBuffer AddBool(bool value) {
265 | Add(1, value ? 1U : 0U);
266 |
267 | return this;
268 | }
269 |
270 | [MethodImpl(256)]
271 | public bool ReadBool() {
272 | return Read(1) > 0;
273 | }
274 |
275 | [MethodImpl(256)]
276 | public bool PeekBool() {
277 | return Peek(1) > 0;
278 | }
279 |
280 | [MethodImpl(256)]
281 | public BitBuffer AddByte(byte value) {
282 | Add(8, value);
283 |
284 | return this;
285 | }
286 |
287 | [MethodImpl(256)]
288 | public byte ReadByte() {
289 | return (byte)Read(8);
290 | }
291 |
292 | [MethodImpl(256)]
293 | public byte PeekByte() {
294 | return (byte)Peek(8);
295 | }
296 |
297 | [MethodImpl(256)]
298 | public BitBuffer AddShort(short value) {
299 | AddInt(value);
300 |
301 | return this;
302 | }
303 |
304 | [MethodImpl(256)]
305 | public short ReadShort() {
306 | return (short)ReadInt();
307 | }
308 |
309 | [MethodImpl(256)]
310 | public short PeekShort() {
311 | return (short)PeekInt();
312 | }
313 |
314 | [MethodImpl(256)]
315 | public BitBuffer AddUShort(ushort value) {
316 | AddUInt(value);
317 |
318 | return this;
319 | }
320 |
321 | [MethodImpl(256)]
322 | public ushort ReadUShort() {
323 | return (ushort)ReadUInt();
324 | }
325 |
326 | [MethodImpl(256)]
327 | public ushort PeekUShort() {
328 | return (ushort)PeekUInt();
329 | }
330 |
331 | [MethodImpl(256)]
332 | public BitBuffer AddInt(int value) {
333 | uint zigzag = (uint)((value << 1) ^ (value >> 31));
334 |
335 | AddUInt(zigzag);
336 |
337 | return this;
338 | }
339 |
340 | [MethodImpl(256)]
341 | public int ReadInt() {
342 | uint value = ReadUInt();
343 | int zagzig = (int)((value >> 1) ^ (-(int)(value & 1)));
344 |
345 | return zagzig;
346 | }
347 |
348 | [MethodImpl(256)]
349 | public int PeekInt() {
350 | uint value = PeekUInt();
351 | int zagzig = (int)((value >> 1) ^ (-(int)(value & 1)));
352 |
353 | return zagzig;
354 | }
355 |
356 | [MethodImpl(256)]
357 | public BitBuffer AddUInt(uint value) {
358 | uint buffer = 0x0u;
359 |
360 | do {
361 | buffer = value & 0x7Fu;
362 | value >>= 7;
363 |
364 | if (value > 0)
365 | buffer |= 0x80u;
366 |
367 | Add(8, buffer);
368 | }
369 |
370 | while (value > 0);
371 |
372 | return this;
373 | }
374 |
375 | [MethodImpl(256)]
376 | public uint ReadUInt() {
377 | uint buffer = 0x0u;
378 | uint value = 0x0u;
379 | int shift = 0;
380 |
381 | do {
382 | buffer = Read(8);
383 |
384 | value |= (buffer & 0x7Fu) << shift;
385 | shift += 7;
386 | }
387 |
388 | while ((buffer & 0x80u) > 0);
389 |
390 | return value;
391 | }
392 |
393 | [MethodImpl(256)]
394 | public uint PeekUInt() {
395 | int tempPosition = readPosition;
396 | uint value = ReadUInt();
397 |
398 | readPosition = tempPosition;
399 |
400 | return value;
401 | }
402 |
403 | [MethodImpl(256)]
404 | public BitBuffer AddLong(long value) {
405 | AddInt((int)(value & uint.MaxValue));
406 | AddInt((int)(value >> 32));
407 |
408 | return this;
409 | }
410 |
411 | [MethodImpl(256)]
412 | public long ReadLong() {
413 | int low = ReadInt();
414 | int high = ReadInt();
415 | long value = high;
416 |
417 | return value << 32 | (uint)low;
418 | }
419 |
420 | [MethodImpl(256)]
421 | public long PeekLong() {
422 | int tempPosition = readPosition;
423 | long value = ReadLong();
424 |
425 | readPosition = tempPosition;
426 |
427 | return value;
428 | }
429 |
430 | [MethodImpl(256)]
431 | public BitBuffer AddULong(ulong value) {
432 | AddUInt((uint)(value & uint.MaxValue));
433 | AddUInt((uint)(value >> 32));
434 |
435 | return this;
436 | }
437 |
438 | [MethodImpl(256)]
439 | public ulong ReadULong() {
440 | uint low = ReadUInt();
441 | uint high = ReadUInt();
442 |
443 | return (ulong)high << 32 | low;
444 | }
445 |
446 | [MethodImpl(256)]
447 | public ulong PeekULong() {
448 | int tempPosition = readPosition;
449 | ulong value = ReadULong();
450 |
451 | readPosition = tempPosition;
452 |
453 | return value;
454 | }
455 |
456 | [MethodImpl(256)]
457 | public BitBuffer AddString(string value) {
458 | if (value == null)
459 | throw new ArgumentNullException("value");
460 |
461 | uint length = (uint)value.Length;
462 |
463 | if (length > stringLengthMax) {
464 | length = (uint)stringLengthMax;
465 |
466 | throw new ArgumentOutOfRangeException("value length exceeded");
467 | }
468 |
469 | Add(stringLengthBits, length);
470 |
471 | for (int i = 0; i < length; i++) {
472 | Add(bitsASCII, ToASCII(value[i]));
473 | }
474 |
475 | return this;
476 | }
477 |
478 | [MethodImpl(256)]
479 | public string ReadString() {
480 | StringBuilder builder = new StringBuilder();
481 | uint length = Read(stringLengthBits);
482 |
483 | for (int i = 0; i < length; i++) {
484 | builder.Append((char)Read(bitsASCII));
485 | }
486 |
487 | return builder.ToString();
488 | }
489 |
490 | public override string ToString() {
491 | StringBuilder builder = new StringBuilder();
492 |
493 | for (int i = chunks.Length - 1; i >= 0; i--) {
494 | builder.Append(Convert.ToString(chunks[i], 2).PadLeft(32, '0'));
495 | }
496 |
497 | StringBuilder spaced = new StringBuilder();
498 |
499 | for (int i = 0; i < builder.Length; i++) {
500 | spaced.Append(builder[i]);
501 |
502 | if (((i + 1) % 8) == 0)
503 | spaced.Append(" ");
504 | }
505 |
506 | return spaced.ToString();
507 | }
508 |
509 | private void ExpandArray() {
510 | int newCapacity = (chunks.Length * growFactor) + minGrow;
511 | uint[] newChunks = new uint[newCapacity];
512 |
513 | Array.Copy(chunks, newChunks, chunks.Length);
514 | chunks = newChunks;
515 | }
516 |
517 | [MethodImpl(256)]
518 | private static int FindHighestBitPosition(byte data) {
519 | int shiftCount = 0;
520 |
521 | while (data > 0) {
522 | data >>= 1;
523 | shiftCount++;
524 | }
525 |
526 | return shiftCount;
527 | }
528 |
529 | private static byte ToASCII(char character) {
530 | byte value = 0;
531 |
532 | try {
533 | value = Convert.ToByte(character);
534 | }
535 |
536 | catch (OverflowException) {
537 | throw new Exception("Cannot convert to ASCII: " + character);
538 | }
539 |
540 | if (value > 127)
541 | throw new Exception("Cannot convert to ASCII: " + character);
542 |
543 | return value;
544 | }
545 | }
546 | }
--------------------------------------------------------------------------------
/NetCode.Benchmarks/Program.cs:
--------------------------------------------------------------------------------
1 | // See https://aka.ms/new-console-template for more information
2 |
3 | using BenchmarkDotNet.Running;
4 | using NetCode.Benchmarks;
5 |
6 | var switcher = new BenchmarkSwitcher(new[]
7 | {
8 | typeof(BitReader_ReadBits_Benchmark),
9 | typeof(BitReader_ReadByte_Benchmark),
10 | typeof(BitReader_ReadInt_Benchmark),
11 | typeof(BitWriter_WriteBits_Benchmark),
12 | typeof(BitWriter_WriteByte_Benchmark),
13 | typeof(BitWriter_WriteInt_Benchmark),
14 | typeof(ByteReaderBenchmark),
15 | typeof(ByteWriterBenchmark),
16 | });
17 |
18 | switcher.Run(args);
--------------------------------------------------------------------------------
/NetCode.Demo/NetCode.Demo.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/NetCode.Demo/Program.cs:
--------------------------------------------------------------------------------
1 | using System.Buffers;
2 | using System.Numerics;
3 | using NetCode;
4 | using NetCode.Limits;
5 |
6 | var serializer = new TransformComponentSerializer();
7 | var deserializer = new TransformComponentDeserializer();
8 |
9 | var before = new TransformComponent { Position = new Vector3(10f, 5f, 10f), Pitch = 30f, Yaw = 60f };
10 | var after = new TransformComponent { Position = new Vector3(10.5f, 5.5f, 10.5f), Pitch = 30f, Yaw = 60f };
11 |
12 | var serializedComponent = serializer.Serialize(before, after);
13 | Console.WriteLine(serializedComponent.Length); // 3
14 |
15 | var updated = deserializer.Deserialize(before, serializedComponent.Array);
16 |
17 | serializedComponent.Dispose();
18 |
19 | Console.WriteLine(updated); // Position: <10.5, 5.5, 10.5>, Yaw: 60, Pitch: 30
20 |
21 | public record struct TransformComponent (Vector3 Position, float Yaw, float Pitch );
22 |
23 | public struct SerializedComponent
24 | {
25 | private readonly ArrayPool _arrayPool;
26 |
27 | public byte[] Array { get; }
28 |
29 | public int Length { get; }
30 |
31 | public SerializedComponent(ArrayPool arrayPool, byte[] array, int length)
32 | {
33 | _arrayPool = arrayPool;
34 | Array = array;
35 | Length = length;
36 | }
37 |
38 | public void Dispose()
39 | {
40 | _arrayPool.Return(Array);
41 | }
42 | }
43 |
44 | public static class Limits
45 | {
46 | public static readonly FloatLimit Rotation = new FloatLimit(0, 360, 0.1f);
47 |
48 | public static readonly Vector3Limit AbsolutePosition = new Vector3Limit(new FloatLimit(-100f, 100f, 0.1f), new FloatLimit(-10f, 10f, 0.1f), new FloatLimit(-100f, 100f, 0.1f));
49 |
50 | public static readonly Vector3Limit DiffPosition = new Vector3Limit(new FloatLimit(-1f, 1f, 0.1f), new FloatLimit(-1f, 1f, 0.1f), new FloatLimit(-1f, 1f, 0.1f));
51 | }
52 |
53 | public class TransformComponentSerializer
54 | {
55 | private const int MTU = 1500;
56 |
57 | private readonly BitWriter _bitWriter = new BitWriter();
58 | private readonly ArrayPool _arrayPool = ArrayPool.Shared;
59 |
60 | public SerializedComponent Serialize(TransformComponent baseline, TransformComponent updated)
61 | {
62 | var array = _arrayPool.Rent(MTU);
63 | _bitWriter.SetArray(array);
64 |
65 | _bitWriter.WriteDiffIfChanged(baseline.Position.X, updated.Position.X, Limits.AbsolutePosition.X, Limits.DiffPosition.X);
66 | _bitWriter.WriteDiffIfChanged(baseline.Position.Y, updated.Position.Y, Limits.AbsolutePosition.Y, Limits.DiffPosition.Y);
67 | _bitWriter.WriteDiffIfChanged(baseline.Position.Z, updated.Position.Z, Limits.AbsolutePosition.Z, Limits.DiffPosition.Z);
68 |
69 | _bitWriter.WriteValueIfChanged(baseline.Yaw, updated.Yaw, Limits.Rotation);
70 | _bitWriter.WriteValueIfChanged(baseline.Pitch, updated.Pitch, Limits.Rotation);
71 |
72 | _bitWriter.Flush();
73 |
74 | return new SerializedComponent(_arrayPool, _bitWriter.Array, _bitWriter.BytesCount);
75 | }
76 | }
77 |
78 | public class TransformComponentDeserializer
79 | {
80 | private readonly BitReader _bitReader = new BitReader();
81 |
82 | public TransformComponent Deserialize(TransformComponent before, byte[] array)
83 | {
84 | _bitReader.SetArray(array);
85 |
86 | TransformComponent result = default;
87 |
88 | result.Position = new Vector3(
89 | _bitReader.ReadFloat(before.Position.X, Limits.AbsolutePosition.X, Limits.DiffPosition.X),
90 | _bitReader.ReadFloat(before.Position.Y, Limits.AbsolutePosition.Y, Limits.DiffPosition.Y),
91 | _bitReader.ReadFloat(before.Position.Z, Limits.AbsolutePosition.Z, Limits.DiffPosition.Z));
92 |
93 | result.Yaw = _bitReader.ReadFloat(before.Yaw, Limits.Rotation);
94 | result.Pitch = _bitReader.ReadFloat(before.Pitch, Limits.Rotation);
95 |
96 | return result;
97 | }
98 | }
--------------------------------------------------------------------------------
/NetCode.UnitTests/BitReaderAndWriterTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Numerics;
3 | using FluentAssertions;
4 | using NetCode.Limits;
5 | using Xunit;
6 |
7 | namespace NetCode.UnitTests;
8 |
9 | public class BitReaderAndWriterTests
10 | {
11 | private static byte @byte = 0b_11110000;
12 | private static ushort @short = 0b_11110000_00001111;
13 | private static uint @int = 0b_10101010_01010101_11110000_00001111;
14 | private static float @float = BitConverter.UInt32BitsToSingle(@int);
15 |
16 | private static ulong @long = 0b_11111111_00000000_11001100_00110011_10101010_01010101_11110000_00001111;
17 | private static double @double = BitConverter.UInt64BitsToDouble(@long);
18 |
19 | [Fact]
20 | public void WriteAndReadTheSameData()
21 | {
22 | var array = new byte[100];
23 | var bitWriter = new BitWriter(array);
24 |
25 | bitWriter.WriteBits(19, 0b_10101010_01010101_11110000_00001111);
26 | bitWriter.Write(@byte);
27 | bitWriter.Write(@short);
28 | bitWriter.Write(@int);
29 | bitWriter.Write(@float);
30 | bitWriter.Write(@long);
31 | bitWriter.Write(@double);
32 | bitWriter.Flush();
33 |
34 | var data = bitWriter.Array;
35 | data.Should().BeSameAs(array);
36 |
37 | var bitReader = new BitReader(data);
38 |
39 | bitReader.ReadBits(19).Should().Be(0b_101_11110000_00001111);
40 | bitReader.ReadByte().Should().Be(@byte);
41 | bitReader.ReadUShort().Should().Be(@short);
42 | bitReader.ReadUInt().Should().Be(@int);
43 | bitReader.ReadFloat().Should().Be(@float);
44 | bitReader.ReadULong().Should().Be(@long);
45 | bitReader.ReadDouble().Should().Be(@double);
46 | }
47 |
48 | [Fact]
49 | public void WriteReadString_Utf8_ShouldBeTheSame()
50 | {
51 | var array = new byte[100];
52 | var bitWriter = new BitWriter(array);
53 |
54 | var s = "qwertyuiopasdfghjklzxcvbnm";
55 | bitWriter.WriteUtf8String(s);
56 | bitWriter.Flush();
57 |
58 | var data = bitWriter.Array;
59 | var length = bitWriter.BytesCount;
60 | length.Should().Be(s.Length + 1);
61 |
62 | var bitReader = new BitReader(data);
63 | var result = bitReader.ReadUtf8String();
64 | result.Should().Be(s);
65 | string.IsInterned(result).Should().Be(result);
66 | }
67 |
68 | [Fact]
69 | public void WriteReadString_Unicode_ShouldBeTheSame()
70 | {
71 | var array = new byte[100];
72 | var bitWriter = new BitWriter(array);
73 |
74 | var s = "qwertyuiopasdfghjklzxcvbnm";
75 | bitWriter.WriteUnicodeString(s);
76 | bitWriter.Flush();
77 |
78 | var data = bitWriter.Array;
79 | var length = bitWriter.BytesCount;
80 | length.Should().Be(s.Length * 2 + 1);
81 |
82 | var bitReader = new BitReader(data);
83 | var result = bitReader.ReadUnicodeString();
84 | result.Should().Be(s);
85 | string.IsInterned(result).Should().Be(result);
86 | }
87 |
88 | [Fact]
89 | public void WriteReadVector3_WithLimit_ShouldBeTheSame()
90 | {
91 | var vector3Limit = new Vector3Limit(
92 | new FloatLimit(-10f, 10f, 0.5f),
93 | new FloatLimit(-10f, 10f, 0.5f),
94 | new FloatLimit(-70f, 10f, 0.5f));
95 |
96 | var bitWriter = new BitWriter();
97 | var vector3 = new Vector3(1,1,1);
98 | bitWriter.Write(vector3,vector3Limit);
99 | bitWriter.Flush();
100 | var bitReader = new BitReader(bitWriter.Array);
101 | var readVector3 = bitReader.ReadVector3(vector3Limit);
102 | readVector3.X.Should().Be(vector3.X);
103 | readVector3.Y.Should().Be(vector3.Y);
104 | readVector3.Z.Should().Be(vector3.Z);
105 | }
106 | }
--------------------------------------------------------------------------------
/NetCode.UnitTests/ByteReaderAndWriterTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using FluentAssertions;
3 | using Xunit;
4 |
5 | namespace NetCode.UnitTests;
6 |
7 | public class ByteReaderAndWriterTests
8 | {
9 | private static byte @byte = 0b_11110000;
10 | private static ushort @short = 0b_11110000_00001111;
11 | private static uint @int = 0b_10101010_01010101_11110000_00001111;
12 | private static ulong @long = 0b_11111111_00000000_11001100_00110011_10101010_01010101_11110000_00001111;
13 | private static float @float = BitConverter.UInt32BitsToSingle(@int);
14 | private static double @double = BitConverter.UInt64BitsToDouble(@long);
15 |
16 | [Fact]
17 | public void WriteAndReadTheSameData()
18 | {
19 | var array = new byte[27];
20 | var byteWriter = new ByteWriter(array);
21 |
22 | byteWriter.Write(@byte); // 1
23 | byteWriter.Write(@short); // 2
24 | byteWriter.Write(@int); // 4
25 | byteWriter.Write(@long); // 8
26 | byteWriter.Write(@float); // 4
27 | byteWriter.Write(@double); // 8
28 |
29 | byteWriter.Count.Should().Be(27);
30 |
31 | var data = byteWriter.Array;
32 | data.Should().BeSameAs(array);
33 |
34 | var byteReader = new ByteReader(data);
35 |
36 | byteReader.ReadByte().Should().Be(@byte);
37 | byteReader.ReadUShort().Should().Be(@short);
38 | byteReader.ReadUInt().Should().Be(@int);
39 | byteReader.ReadULong().Should().Be(@long);
40 | byteReader.ReadFloat().Should().Be(@float);
41 | byteReader.ReadDouble().Should().Be(@double);
42 | }
43 | }
--------------------------------------------------------------------------------
/NetCode.UnitTests/ByteReaderTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using FluentAssertions;
3 | using Xunit;
4 |
5 | namespace NetCode.UnitTests;
6 |
7 | public class ByteReaderTests
8 | {
9 | [Fact]
10 | public void ArrayContains8Bytes_ReadULong_ULongShouldBeRead()
11 | {
12 | var array = new byte[]
13 | {
14 | 0b_00001111,
15 | 0b_11110000,
16 | 0b_01010101,
17 | 0b_10101010,
18 |
19 | 0b_00110011,
20 | 0b_11001100,
21 | 0b_00000000,
22 | 0b_11111111
23 | };
24 |
25 | var byteReader = new ByteReader(array);
26 | var value = byteReader.ReadULong();
27 |
28 | value.Should().Be(0b_11111111_00000000_11001100_00110011_10101010_01010101_11110000_00001111);
29 | }
30 |
31 | [Fact]
32 | public void ArrayContains8Bytes_ReadLong_LongShouldBeRead()
33 | {
34 | var array = new byte[]
35 | {
36 | 0b_00001111,
37 | 0b_11110000,
38 | 0b_01010101,
39 | 0b_10101010,
40 |
41 | 0b_00110011,
42 | 0b_11001100,
43 | 0b_00000000,
44 | 0b_11111111
45 | };
46 |
47 | var byteReader = new ByteReader(array);
48 | var value = byteReader.ReadLong();
49 |
50 | value.Should().Be(unchecked((long)0b_11111111_00000000_11001100_00110011_10101010_01010101_11110000_00001111));
51 | }
52 |
53 | [Fact]
54 | public void ArrayContains4Bytes_ReadUInt_UIntShouldBeRead()
55 | {
56 | var array = new byte[]
57 | {
58 | 0b_00001111,
59 | 0b_11110000,
60 | 0b_01010101,
61 | 0b_10101010
62 | };
63 |
64 | var byteReader = new ByteReader(array);
65 | var value = byteReader.ReadUInt();
66 |
67 | value.Should().Be(0b_10101010_01010101_11110000_00001111);
68 | }
69 |
70 | [Fact]
71 | public void ArrayContains4Bytes_ReadInt_IntShouldBeRead()
72 | {
73 | var array = new byte[]
74 | {
75 | 0b_00001111,
76 | 0b_11110000,
77 | 0b_01010101,
78 | 0b_10101010
79 | };
80 |
81 | var byteReader = new ByteReader(array);
82 | var value = byteReader.ReadInt();
83 |
84 | value.Should().Be(unchecked((int)0b_10101010_01010101_11110000_00001111));
85 | }
86 |
87 | [Fact]
88 | public void ArrayContains2Bytes_ReadUShort_UShortShouldBeRead()
89 | {
90 | var array = new byte[]
91 | {
92 | 0b_00001111,
93 | 0b_11110000
94 | };
95 |
96 | var byteReader = new ByteReader(array);
97 | var value = byteReader.ReadUShort();
98 |
99 | value.Should().Be(0b_11110000_00001111);
100 | }
101 |
102 | [Fact]
103 | public void ArrayContains2Bytes_ReadShort_ShortShouldBeRead()
104 | {
105 | var array = new byte[]
106 | {
107 | 0b_00001111,
108 | 0b_11110000
109 | };
110 |
111 | var byteReader = new ByteReader(array);
112 | var value = byteReader.ReadShort();
113 |
114 | value.Should().Be(unchecked((short)0b_11110000_00001111));
115 | }
116 |
117 | [Fact]
118 | public void ArrayContains1Byte_ReadByte_ByteShouldBeRead()
119 | {
120 | var array = new byte[]
121 | {
122 | 0b_11110000
123 | };
124 |
125 | var byteReader = new ByteReader(array);
126 | var value = byteReader.ReadByte();
127 |
128 | value.Should().Be(0b_11110000);
129 | }
130 |
131 | [Fact]
132 | public void EmptyArray_ReadByte_ExceptionExpected()
133 | {
134 | var byteReader = new ByteReader(Array.Empty());
135 |
136 | byteReader.End.Should().Be(0);
137 |
138 | Action action = () => byteReader.ReadByte();
139 |
140 | action.Should().Throw();
141 | }
142 |
143 | [Fact]
144 | public void ArrayContains1Byte_ReadUInt_ExceptionExpected()
145 | {
146 | var array = new byte[]
147 | {
148 | 0b_11110000
149 | };
150 |
151 | var byteReader = new ByteReader(array);
152 | Action action = () => byteReader.ReadUInt();
153 |
154 | action.Should().Throw();
155 | }
156 |
157 | [Fact]
158 | public void ArrayContains1Byte_TryReadUInt_ShouldBeOk()
159 | {
160 | var array = new byte[]
161 | {
162 | 0b_11110000
163 | };
164 |
165 | var byteReader = new ByteReader(array);
166 | byteReader.RemainingToRead.Should().Be(1);
167 |
168 | var (value, readBytes) = byteReader.TryReadUInt();
169 |
170 | value.Should().Be(0b_11110000);
171 | readBytes.Should().Be(1);
172 | }
173 |
174 | [Fact]
175 | public void ArrayContains4Bytes_TryReadUInt_ShouldBeOk()
176 | {
177 | var array = new byte[]
178 | {
179 | 0b_11110000,
180 | 0b_11110000,
181 | 0b_11110000,
182 | 0b_11110000
183 | };
184 |
185 | var byteReader = new ByteReader(array);
186 | byteReader.RemainingToRead.Should().Be(4);
187 |
188 | var (value, readBytes) = byteReader.TryReadUInt();
189 |
190 | value.Should().Be(0b_11110000_11110000_11110000_11110000);
191 | readBytes.Should().Be(4);
192 | }
193 |
194 | [Fact]
195 | public void ArrayContains5Bytes_TryReadUInt_ShouldBeOk()
196 | {
197 | var array = new byte[]
198 | {
199 | 0b_11110000,
200 | 0b_11110000,
201 | 0b_11110000,
202 | 0b_11110000,
203 | 0b_11110000
204 | };
205 |
206 | var byteReader = new ByteReader(array);
207 | byteReader.RemainingToRead.Should().Be(5);
208 |
209 | var (value, readBytes) = byteReader.TryReadUInt();
210 |
211 | value.Should().Be(0b_11110000_11110000_11110000_11110000);
212 | readBytes.Should().Be(4);
213 | byteReader.RemainingToRead.Should().Be(1);
214 | }
215 |
216 | [Fact]
217 | public void ArrayContains1Byte_TryReadUIntTwice_FirstShouldHaveDataSecondShouldBeEmpty()
218 | {
219 | var array = new byte[]
220 | {
221 | 0b_11110000
222 | };
223 |
224 | var byteReader = new ByteReader(array);
225 | var (value, readBytes) = byteReader.TryReadUInt();
226 |
227 | value.Should().Be(0b_11110000);
228 | readBytes.Should().Be(1);
229 |
230 | (value, readBytes) = byteReader.TryReadUInt();
231 | value.Should().Be(default);
232 | readBytes.Should().Be(0);
233 | }
234 |
235 | [Fact]
236 | public void ArrayContains3Bytes_ReadShort_ShortShouldBeRead()
237 | {
238 | var array = new byte[]
239 | {
240 | 0b_00001111,
241 | 0b_11110000,
242 | 0b_10101010
243 | };
244 |
245 | var byteReader = new ByteReader(array);
246 | var value = byteReader.ReadShort();
247 |
248 | value.Should().Be(unchecked((short)0b_11110000_00001111));
249 | }
250 |
251 | [Fact]
252 | public void ArrayContains4Bytes_ReadDataSeveralTimes_RemainingToReadShouldBeValid()
253 | {
254 | var array = new byte[]
255 | {
256 | 0b_11001100,
257 | 0b_00001111,
258 | 0b_11110000,
259 | 0b_10101010
260 | };
261 |
262 | var byteReader = new ByteReader(array);
263 | byteReader.RemainingToRead.Should().Be(4);
264 |
265 | byteReader.ReadByte();
266 | byteReader.RemainingToRead.Should().Be(3);
267 |
268 | byteReader.ReadShort();
269 | byteReader.RemainingToRead.Should().Be(1);
270 |
271 | byteReader.ReadByte();
272 | byteReader.RemainingToRead.Should().Be(0);
273 | }
274 |
275 | [Fact]
276 | public void ArrayContains4Bytes_TryReadDataSeveralTimes_RemainingToReadShouldBeValid()
277 | {
278 | var array = new byte[]
279 | {
280 | 0b_11001100,
281 | 0b_00001111,
282 | 0b_11110000,
283 | 0b_10101010
284 | };
285 |
286 | var byteReader = new ByteReader(array);
287 | byteReader.RemainingToRead.Should().Be(4);
288 |
289 | byteReader.ReadByte();
290 | byteReader.RemainingToRead.Should().Be(3);
291 |
292 | byteReader.ReadShort();
293 | byteReader.RemainingToRead.Should().Be(1);
294 |
295 | byteReader.TryReadUInt();
296 | byteReader.RemainingToRead.Should().Be(0);
297 | }
298 |
299 | [Fact]
300 | public void ArrayContains2Bytes_ReadShortAndResetAndReadShort_ShouldBeRead()
301 | {
302 | var array = new byte[]
303 | {
304 | 0b_00001111,
305 | 0b_11110000
306 | };
307 |
308 | var byteReader = new ByteReader(array);
309 | var value = byteReader.ReadShort();
310 |
311 | value.Should().Be(unchecked((short)0b_11110000_00001111));
312 |
313 | byteReader.Reset();
314 |
315 | var value2 = byteReader.ReadShort();
316 |
317 | value2.Should().Be(unchecked((short)0b_11110000_00001111));
318 | }
319 |
320 | [Fact]
321 | public void EmptyReader_SetArrayAndReadShort_ShouldBeRead()
322 | {
323 | var byteReader = new ByteReader(Array.Empty());
324 |
325 | var array = new byte[]
326 | {
327 | 0b_00001111,
328 | 0b_11110000
329 | };
330 |
331 | byteReader.SetArray(array);
332 |
333 | byteReader.RemainingToRead.Should().Be(2);
334 | byteReader.ReadByte().Should().Be(0b_00001111);
335 | byteReader.ReadByte().Should().Be(0b_11110000);
336 | }
337 |
338 | [Fact]
339 | public void EmptyReader_SetArrayWithStartEq0AndReadShort_ShouldBeRead()
340 | {
341 | var byteReader = new ByteReader(Array.Empty());
342 |
343 | var array = new byte[]
344 | {
345 | 0b_00001111,
346 | 0b_11110000
347 | };
348 |
349 | byteReader.SetArray(array, 0, 2);
350 |
351 | byteReader.RemainingToRead.Should().Be(2);
352 | byteReader.ReadByte().Should().Be(0b_00001111);
353 | byteReader.ReadByte().Should().Be(0b_11110000);
354 | }
355 |
356 | [Fact]
357 | public void EmptyReader_SetArrayWithStartEq1AndReadByte_ShouldBeRead()
358 | {
359 | var byteReader = new ByteReader(Array.Empty());
360 |
361 | var array = new byte[]
362 | {
363 | 0b_00001111,
364 | 0b_11110000
365 | };
366 |
367 | byteReader.SetArray(array, 1, 1);
368 |
369 | byteReader.RemainingToRead.Should().Be(1);
370 | byteReader.ReadByte().Should().Be(0b_11110000);
371 | }
372 |
373 | [Fact]
374 | public void EmptyReader_SetArrayWithLengthMoreThanArrayLength_ExceptionExpected()
375 | {
376 | var byteReader = new ByteReader(Array.Empty());
377 |
378 | Action action = () => byteReader.SetArray(new byte[2], 0, 3);
379 |
380 | action.Should().Throw();
381 | }
382 |
383 | [Fact]
384 | public void EmptyReader_SetArrayWithStartMoreThanArrayLength_ExceptionExpected()
385 | {
386 | var byteReader = new ByteReader(Array.Empty());
387 |
388 | Action action = () => byteReader.SetArray(new byte[2], 3, 2);
389 |
390 | action.Should().Throw();
391 | }
392 |
393 | [Fact]
394 | public void SetArray_ReadDataSeveralTimes_RemainingToReadShouldBeValid()
395 | {
396 | var array = new byte[]
397 | {
398 | 0b_11001100,
399 | 0b_00001111,
400 | 0b_11110000,
401 | 0b_10101010
402 | };
403 |
404 | var byteReader = new ByteReader();
405 | byteReader.SetArray(array);
406 |
407 | byteReader.Start.Should().Be(0);
408 | byteReader.End.Should().Be(4);
409 | byteReader.Head.Should().Be(0);
410 | byteReader.RemainingToRead.Should().Be(4);
411 |
412 | byteReader.ReadByte();
413 |
414 | byteReader.Start.Should().Be(0);
415 | byteReader.End.Should().Be(4);
416 | byteReader.Head.Should().Be(1);
417 | byteReader.RemainingToRead.Should().Be(3);
418 |
419 | byteReader.ReadShort();
420 |
421 | byteReader.Start.Should().Be(0);
422 | byteReader.End.Should().Be(4);
423 | byteReader.Head.Should().Be(3);
424 | byteReader.RemainingToRead.Should().Be(1);
425 |
426 | byteReader.TryReadUInt();
427 |
428 | byteReader.Start.Should().Be(0);
429 | byteReader.End.Should().Be(4);
430 | byteReader.Head.Should().Be(4);
431 | byteReader.RemainingToRead.Should().Be(0);
432 | }
433 |
434 | [Fact]
435 | public void SetArrayWithStart_ReadDataSeveralTimes_RemainingToReadShouldBeValid()
436 | {
437 | var array = new byte[]
438 | {
439 | 0b_11001100,
440 | 0b_00001111,
441 | 0b_11110000,
442 | 0b_10101010
443 | };
444 |
445 | var byteReader = new ByteReader();
446 | byteReader.SetArray(array, 1, 3);
447 |
448 | byteReader.Start.Should().Be(1);
449 | byteReader.End.Should().Be(4);
450 | byteReader.Head.Should().Be(1);
451 | byteReader.RemainingToRead.Should().Be(3);
452 |
453 | byteReader.ReadByte();
454 |
455 | byteReader.Start.Should().Be(1);
456 | byteReader.End.Should().Be(4);
457 | byteReader.Head.Should().Be(2);
458 | byteReader.RemainingToRead.Should().Be(2);
459 |
460 | byteReader.ReadShort();
461 |
462 | byteReader.Start.Should().Be(1);
463 | byteReader.End.Should().Be(4);
464 | byteReader.Head.Should().Be(4);
465 | byteReader.RemainingToRead.Should().Be(0);
466 |
467 | byteReader.TryReadUInt();
468 |
469 | byteReader.Start.Should().Be(1);
470 | byteReader.End.Should().Be(4);
471 | byteReader.Head.Should().Be(4);
472 | byteReader.RemainingToRead.Should().Be(0);
473 |
474 | byteReader.Reset();
475 |
476 | byteReader.Start.Should().Be(1);
477 | byteReader.End.Should().Be(4);
478 | byteReader.Head.Should().Be(1);
479 | byteReader.RemainingToRead.Should().Be(3);
480 | }
481 | }
--------------------------------------------------------------------------------
/NetCode.UnitTests/ByteWriterTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using FluentAssertions;
3 | using Xunit;
4 |
5 | namespace NetCode.UnitTests;
6 |
7 | public class ByteWriterTests
8 | {
9 | [Fact]
10 | public void DefaultCtor_WriteULong_ArrayShouldContainBytes()
11 | {
12 | var byteWriter = new ByteWriter();
13 |
14 | ulong value = 0b_11111111_00000000_11001100_00110011_10101010_01010101_11110000_00001111;
15 | byteWriter.Write(value);
16 |
17 | byteWriter.Count.Should().Be(8);
18 | byteWriter.Array[0].Should().Be((byte) value);
19 | byteWriter.Array[1].Should().Be((byte) (value >> 8));
20 | byteWriter.Array[2].Should().Be((byte) (value >> 16));
21 | byteWriter.Array[3].Should().Be((byte) (value >> 24));
22 | byteWriter.Array[4].Should().Be((byte) (value >> 32));
23 | byteWriter.Array[5].Should().Be((byte) (value >> 40));
24 | byteWriter.Array[6].Should().Be((byte) (value >> 48));
25 | byteWriter.Array[7].Should().Be((byte) (value >> 56));
26 | }
27 |
28 | [Fact]
29 | public void DefaultCtor_WriteLong_ArrayShouldContainBytes()
30 | {
31 | var byteWriter = new ByteWriter();
32 |
33 | long value = unchecked((long)0b_11111111_00000000_11001100_00110011_10101010_01010101_11110000_00001111);
34 | byteWriter.Write(value);
35 |
36 | byteWriter.Count.Should().Be(8);
37 | byteWriter.Array[0].Should().Be((byte) value);
38 | byteWriter.Array[1].Should().Be((byte) (value >> 8));
39 | byteWriter.Array[2].Should().Be((byte) (value >> 16));
40 | byteWriter.Array[3].Should().Be((byte) (value >> 24));
41 | byteWriter.Array[4].Should().Be((byte) (value >> 32));
42 | byteWriter.Array[5].Should().Be((byte) (value >> 40));
43 | byteWriter.Array[6].Should().Be((byte) (value >> 48));
44 | byteWriter.Array[7].Should().Be((byte) (value >> 56));
45 | }
46 |
47 | [Fact]
48 | public void DefaultCtor_WriteUInt_ArrayShouldContainBytes()
49 | {
50 | var byteWriter = new ByteWriter();
51 |
52 | uint value = 0b_10101010_01010101_11001100_11110000;
53 | byteWriter.Write(value);
54 |
55 | byteWriter.Count.Should().Be(4);
56 | byteWriter.Array[0].Should().Be(0b11110000);
57 | byteWriter.Array[1].Should().Be(0b11001100);
58 | byteWriter.Array[2].Should().Be(0b01010101);
59 | byteWriter.Array[3].Should().Be(0b10101010);
60 | }
61 |
62 | [Fact]
63 | public void DefaultCtor_WriteInt_ArrayShouldContainBytes()
64 | {
65 | var byteWriter = new ByteWriter();
66 |
67 | int value = unchecked((int)0b_10101010_01010101_11001100_11110000);
68 | byteWriter.Write(value);
69 |
70 | byteWriter.Count.Should().Be(4);
71 | byteWriter.Array[0].Should().Be(0b11110000);
72 | byteWriter.Array[1].Should().Be(0b11001100);
73 | byteWriter.Array[2].Should().Be(0b01010101);
74 | byteWriter.Array[3].Should().Be(0b10101010);
75 | }
76 |
77 | [Fact]
78 | public void DefaultCtor_WriteUShort_ArrayShouldContainBytes()
79 | {
80 | var byteWriter = new ByteWriter();
81 |
82 | ushort value = 0b_10101010_01010101;
83 | byteWriter.Write(value);
84 |
85 | byteWriter.Count.Should().Be(2);
86 | byteWriter.Array[0].Should().Be(0b01010101);
87 | byteWriter.Array[1].Should().Be(0b10101010);
88 | }
89 |
90 | [Fact]
91 | public void DefaultCtor_WriteShort_ArrayShouldContainBytes()
92 | {
93 | var byteWriter = new ByteWriter();
94 |
95 | short value = unchecked((short)0b_10101010_01010101);
96 | byteWriter.Write(value);
97 |
98 | byteWriter.Count.Should().Be(2);
99 | byteWriter.Array[0].Should().Be(0b01010101);
100 | byteWriter.Array[1].Should().Be(0b10101010);
101 | }
102 |
103 | [Fact]
104 | public void DefaultCtor_WriteByte_ArrayShouldContainByte()
105 | {
106 | var byteWriter = new ByteWriter();
107 |
108 | byte value = 0b10101010;
109 | byteWriter.Write(value);
110 |
111 | byteWriter.Count.Should().Be(1);
112 | byteWriter.Array[0].Should().Be(0b10101010);
113 | }
114 |
115 | [Fact]
116 | public void EmptyByteWriter_WriteByte_ExceptionExpected()
117 | {
118 | var emptyByteWriter = new ByteWriter(Array.Empty());
119 | emptyByteWriter.Capacity.Should().Be(0);
120 |
121 | Action action = () => emptyByteWriter.Write(byte.MaxValue);
122 |
123 | action.Should().Throw();
124 | }
125 |
126 | [Fact]
127 | public void SizeOfArrayIs1Byte_WriteShort_ExceptionExpected()
128 | {
129 | var byteWriter = new ByteWriter(new byte[1]);
130 |
131 | Action action = () => byteWriter.Write(short.MaxValue);
132 |
133 | action.Should().Throw();
134 | }
135 |
136 | [Fact]
137 | public void EmptyByteWriter_SetNonEmptyArrayAndWriteShort_ArrayShouldContainBytes()
138 | {
139 | var byteWriter = new ByteWriter(Array.Empty());
140 | byteWriter.SetArray(new byte[2]);
141 |
142 | ushort value = 0b_10101010_01010101;
143 | byteWriter.Write(value);
144 |
145 | byteWriter.Count.Should().Be(2);
146 | byteWriter.Array[0].Should().Be(0b_01010101);
147 | byteWriter.Array[1].Should().Be(0b_10101010);
148 | }
149 |
150 | [Fact]
151 | public void EmptyByteWriter_SetArrayWithZeroOffsetAndWriteShort_ArrayShouldContainBytes()
152 | {
153 | var byteWriter = new ByteWriter(Array.Empty());
154 | byteWriter.SetArray(new byte[2], 0);
155 |
156 | ushort value = 0b_10101010_01010101;
157 | byteWriter.Write(value);
158 |
159 | byteWriter.Count.Should().Be(2);
160 | byteWriter.Array[0].Should().Be(0b_01010101);
161 | byteWriter.Array[1].Should().Be(0b_10101010);
162 | }
163 |
164 | [Fact]
165 | public void EmptyByteWriter_SetArrayWithNonZeroOffsetAndWriteShort_ArrayShouldContainBytes()
166 | {
167 | var byteWriter = new ByteWriter(Array.Empty());
168 | byteWriter.SetArray(new byte[3], 1);
169 |
170 | ushort value = 0b_10101010_01010101;
171 | byteWriter.Write(value);
172 |
173 | byteWriter.Count.Should().Be(3);
174 | byteWriter.Array[0].Should().Be(0b_00000000);
175 | byteWriter.Array[1].Should().Be(0b_01010101);
176 | byteWriter.Array[2].Should().Be(0b_10101010);
177 | }
178 |
179 | [Fact]
180 | public void EmptyByteWriter_SetArrayWithNonZeroOffsetAndWriteByte_ArrayShouldContainBytes()
181 | {
182 | var byteWriter = new ByteWriter(Array.Empty());
183 | byteWriter.SetArray(new byte[3], 2);
184 |
185 | byte value = 0b_10101010;
186 | byteWriter.Write(value);
187 |
188 | byteWriter.Count.Should().Be(3);
189 | byteWriter.Array[0].Should().Be(0b_00000000);
190 | byteWriter.Array[1].Should().Be(0b_00000000);
191 | byteWriter.Array[2].Should().Be(0b_10101010);
192 | }
193 |
194 | [Fact]
195 | public void EmptyByteWriter_SetArrayWithoutEmptySpaceAndWriteByte_ExceptionExpected()
196 | {
197 | var byteWriter = new ByteWriter(Array.Empty());
198 | byteWriter.SetArray(new byte[3], 3);
199 |
200 | Action action = () => byteWriter.Write(byte.MaxValue);
201 |
202 | action.Should().Throw();
203 | }
204 |
205 | [Fact]
206 | public void EmptyByteWriter_SetArrayWithNonValidOffset_ExceptionExpected()
207 | {
208 | var byteWriter = new ByteWriter(Array.Empty());
209 |
210 | Action action = () => byteWriter.SetArray(new byte[3], 4);
211 |
212 | action.Should().Throw();
213 | }
214 |
215 | [Fact]
216 | public void SizeOfArrayIs1Byte_WriteByteAndClearAndWriteByte_ArrayShouldContainTheLastWrittenByte()
217 | {
218 | var byteWriter = new ByteWriter(new byte[1]);
219 | byteWriter.Write(byte.MaxValue);
220 | byteWriter.Clear();
221 |
222 | byteWriter.Count.Should().Be(0);
223 | byteWriter.Write((byte)0b_10101010);
224 |
225 | byteWriter.Array[0].Should().Be(0b_10101010);
226 | }
227 | }
--------------------------------------------------------------------------------
/NetCode.UnitTests/Diff/BitReaderAndWriterTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using Xunit;
3 |
4 | namespace NetCode.UnitTests.Diff;
5 |
6 | public class BitReaderAndWriterTests
7 | {
8 | [Fact]
9 | public void WriteAndReadInt()
10 | {
11 | var writer = new BitWriter();
12 |
13 | writer.WriteValueIfChanged(41, 42);
14 | writer.Flush();
15 |
16 | var data = writer.Array;
17 |
18 | var reader = new BitReader(data);
19 | var updated = reader.ReadInt(41);
20 |
21 | updated.Should().Be(42);
22 | }
23 |
24 | [Fact]
25 | public void WriteAndReadUInt()
26 | {
27 | var writer = new BitWriter();
28 |
29 | writer.WriteValueIfChanged(41u, 42u);
30 | writer.Flush();
31 |
32 | var data = writer.Array;
33 |
34 | var reader = new BitReader(data);
35 | var updated = reader.ReadUInt(41u);
36 |
37 | updated.Should().Be(42u);
38 | }
39 |
40 | [Fact]
41 | public void WriteAndReadFloat()
42 | {
43 | var writer = new BitWriter();
44 |
45 | var baseline = 42f;
46 | var updated = 42.42f;
47 |
48 | writer.WriteValueIfChanged(baseline, updated);
49 | writer.Flush();
50 |
51 | var data = writer.Array;
52 |
53 | var reader = new BitReader(data);
54 |
55 | reader.ReadFloat(baseline).Should().Be(updated);
56 | }
57 | }
--------------------------------------------------------------------------------
/NetCode.UnitTests/Diff/BitReaderTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using FluentAssertions;
3 | using Xunit;
4 |
5 | namespace NetCode.UnitTests.Diff;
6 |
7 | public class BitReaderTests
8 | {
9 | [Fact]
10 | public void ReadInt_ReaderContainsSingle0Bit_UpdatedValueShouldBeTheSameAsBaseline()
11 | {
12 | var bitReader = new BitReader(new byte[] {0b_0});
13 |
14 | var updated = bitReader.ReadInt(10);
15 |
16 | updated.Should().Be(10);
17 | }
18 |
19 | [Fact]
20 | public void ReadInt_ReaderContains1BitAndUpdatedValue_UpdatedValueShouldBeRead()
21 | {
22 | var bitReader = new BitReader(new byte[] { 0b_00010101, 0b_00000000, 0b_00000000, 0b_00000000, 0b_00000000 });
23 |
24 | var updated = bitReader.ReadInt(0b_111);
25 |
26 | updated.Should().Be(0b_1010);
27 | }
28 |
29 | [Fact]
30 | public void ReadUInt_ReaderContainsSingle0Bit_UpdatedValueShouldBeTheSameAsBaseline()
31 | {
32 | var bitReader = new BitReader(new byte[] {0b_0});
33 |
34 | var updated = bitReader.ReadUInt(10);
35 |
36 | updated.Should().Be(10);
37 | }
38 |
39 | [Fact]
40 | public void ReadUInt_ReaderContains1BitAndUpdatedValue_UpdatedValueShouldBeRead()
41 | {
42 | var bitReader = new BitReader(new byte[] { 0b_00010101, 0b_00000000, 0b_00000000, 0b_00000000, 0b_00000000 });
43 |
44 | var updated = bitReader.ReadUInt(0b_111);
45 |
46 | updated.Should().Be(0b_1010);
47 | }
48 |
49 | [Fact]
50 | public void ReadFloat_ReaderContainsSingle0Bit_UpdatedValueShouldBeTheSameAsBaseline()
51 | {
52 | var bitReader = new BitReader(new byte[] {0b_0});
53 |
54 | var updated = bitReader.ReadFloat(10f);
55 |
56 | updated.Should().Be(10f);
57 | }
58 |
59 | [Fact]
60 | public void ReadFloat_ReaderContains1BitAndUpdatedValue_UpdatedValueShouldBeRead()
61 | {
62 | var bitReader = new BitReader(new byte[] { 0b_10101011, 0b_10011000, 0b_11100001, 0b_01010101, 0b_00000001});
63 |
64 | var updated = bitReader.ReadFloat(123f);
65 |
66 | updated.Should().Be(BitConverter.UInt32BitsToSingle(0b_10101010_11110000_11001100_01010101));
67 | }
68 |
69 | [Fact]
70 | public void ReadString_ReaderContainsSingle0Bit_UpdatedValueShouldBeTheSameAsBaseline()
71 | {
72 | var bitReader = new BitReader(new byte[] {0b_0});
73 |
74 | var updated = bitReader.ReadString("abc");
75 |
76 | updated.Should().Be("abc");
77 | }
78 |
79 | [Fact]
80 | public void ReadString_ReaderContains1BitAndUpdatedValue_UpdatedValueShouldBeRead()
81 | {
82 | var bitReader = new BitReader(new byte[] { 0b_00001101, 0b_11000010, 0b_11000100, 0b_11000110, 0b_0});
83 |
84 | var updated = bitReader.ReadString("abb");
85 |
86 | updated.Should().Be("abc");
87 | }
88 | }
--------------------------------------------------------------------------------
/NetCode.UnitTests/Diff/BitWriterTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Numerics;
3 | using FluentAssertions;
4 | using Xunit;
5 |
6 | namespace NetCode.UnitTests.Diff;
7 |
8 | public class BitWriterTests
9 | {
10 | [Fact]
11 | public void WriteValueIfChanged_IntBaseAndUpdatedAreTheSame_ArrayShouldContainSingle0Bit()
12 | {
13 | var writer = new BitWriter();
14 |
15 | writer.WriteValueIfChanged(0b_1010, 0b_1010);
16 | writer.BitsCount.Should().Be(1);
17 |
18 | writer.Flush();
19 |
20 | writer.Array[0].Should().Be(0b_0);
21 | }
22 |
23 | [Fact]
24 | public void WriteValueIfChanged_IntBaseAndUpdatedAreDifferent_ArrayShouldContainBit1AndUpdatedValue()
25 | {
26 | var writer = new BitWriter();
27 |
28 | writer.WriteValueIfChanged(0b_1010, 0b_1011);
29 | writer.BitsCount.Should().Be(33);
30 |
31 | writer.Flush();
32 |
33 | writer.Array[0].Should().Be(0b_1_0111);
34 | }
35 |
36 | [Fact]
37 | public void WriteValueIfChanged_UIntBaseAndUpdatedAreTheSame_ArrayShouldContainSingle0Bit()
38 | {
39 | var writer = new BitWriter();
40 |
41 | writer.WriteValueIfChanged(0b_1010u, 0b_1010u);
42 | writer.BitsCount.Should().Be(1);
43 |
44 | writer.Flush();
45 |
46 | writer.Array[0].Should().Be(0b_0);
47 | }
48 |
49 | [Fact]
50 | public void WriteValueIfChanged_UIntBaseAndUpdatedAreDifferent_ArrayShouldContainBit1AndUpdatedValue()
51 | {
52 | var writer = new BitWriter();
53 |
54 | writer.WriteValueIfChanged(0b_1010u, 0b_1011u);
55 | writer.BitsCount.Should().Be(33);
56 |
57 | writer.Flush();
58 |
59 | writer.Array[0].Should().Be(0b_1_0111);
60 | }
61 |
62 | [Fact]
63 | public void WriteValueIfChanged_FloatBaseAndUpdatedAreTheSame_ArrayShouldContainSingle0Bit()
64 | {
65 | var writer = new BitWriter();
66 |
67 | writer.WriteValueIfChanged((float)0b_1010, (float)0b_1010);
68 | writer.BitsCount.Should().Be(1);
69 |
70 | writer.Flush();
71 |
72 | writer.Array[0].Should().Be(0b_0);
73 | }
74 |
75 | [Fact]
76 | public void WriteValueIfChanged_FloatBaseAndUpdatedAreDifferent_ArrayShouldContainBit1AndUpdatedValue()
77 | {
78 | var writer = new BitWriter();
79 |
80 | var updated = BitConverter.UInt32BitsToSingle(0b_10101010_11110000_11001100_01010101);
81 | var baseline = updated - 1f;
82 | writer.WriteValueIfChanged(baseline, updated);
83 | writer.BitsCount.Should().Be(33);
84 |
85 | writer.Flush();
86 |
87 | writer.Array[0].Should().Be(0b_10101011);
88 | }
89 |
90 | [Fact]
91 | public void WriteValueIfChanged_StringBaseAndUpdatedAreTheSame_ArrayShouldContainSingle0Bit()
92 | {
93 | var writer = new BitWriter();
94 |
95 | writer.WriteValueIfChanged("abc", "abc");
96 | writer.BitsCount.Should().Be(1);
97 |
98 | writer.Flush();
99 |
100 | writer.Array[0].Should().Be(0b_0);
101 | }
102 |
103 | [Fact]
104 | public void WriteValueIfChanged_StringBaseAndUpdatedAreDifferent_ArrayShouldContainBit1AndUpdatedValue()
105 | {
106 | var writer = new BitWriter();
107 |
108 | var updated = "abc";
109 | var baseline = "abb";
110 | writer.WriteValueIfChanged(baseline, updated);
111 | writer.BitsCount.Should().Be(1 + 8 + 24); // 1 bit changed + 8 bits of length + 3 * 8 chars
112 |
113 | writer.Flush();
114 | }
115 | }
--------------------------------------------------------------------------------
/NetCode.UnitTests/DiffAndQuantization/BitReaderAndWriterTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using NetCode.Limits;
3 | using Xunit;
4 |
5 | namespace NetCode.UnitTests.DiffAndQuantization;
6 |
7 | public class BitReaderAndWriterTests
8 | {
9 | [Fact]
10 | public void WriteValueIfChangedAndReadInt_ValueChanged_ReadValueShouldBeEqualToUpdated()
11 | {
12 | var writer = new BitWriter();
13 |
14 | writer.WriteValueIfChanged(10, 50, Limits.IntLimit);
15 | writer.Flush();
16 |
17 | var data = writer.Array;
18 |
19 | var reader = new BitReader(data);
20 |
21 | reader.ReadInt(10, Limits.IntLimit).Should().Be(50);
22 | }
23 |
24 | [Fact]
25 | public void WriteValueIfChangedAndReadInt_ValueIsNotChanged_ReadValueShouldBeEqualToBaseline()
26 | {
27 | var writer = new BitWriter();
28 |
29 | writer.WriteValueIfChanged(10, 10, Limits.IntLimit);
30 | writer.Flush();
31 |
32 | var data = writer.Array;
33 |
34 | var reader = new BitReader(data);
35 |
36 | reader.ReadInt(10, Limits.IntLimit).Should().Be(10);
37 | }
38 |
39 | [Fact]
40 | public void WriteValueIfChangedAndReadUInt_ValueChanged_ReadValueShouldBeEqualToUpdated()
41 | {
42 | var writer = new BitWriter();
43 |
44 | writer.WriteValueIfChanged(10u, 50u, Limits.UIntLimit);
45 | writer.Flush();
46 |
47 | var data = writer.Array;
48 |
49 | var reader = new BitReader(data);
50 |
51 | reader.ReadUInt(10u, Limits.UIntLimit).Should().Be(50);
52 | }
53 |
54 | [Fact]
55 | public void WriteValueIfChangedAndReadUInt_ValueIsNotChanged_ReadValueShouldBeEqualToBaseline()
56 | {
57 | var writer = new BitWriter();
58 |
59 | writer.WriteValueIfChanged(10u, 10u, Limits.UIntLimit);
60 | writer.Flush();
61 |
62 | var data = writer.Array;
63 |
64 | var reader = new BitReader(data);
65 |
66 | reader.ReadUInt(10u, Limits.UIntLimit).Should().Be(10);
67 | }
68 |
69 | [Fact]
70 | public void WriteValueIfChangedAndReadFloat_ValueChanged_ReadValueShouldBeEqualToUpdated()
71 | {
72 | var writer = new BitWriter();
73 |
74 | writer.WriteValueIfChanged(1.0f, 5.0f, Limits.FloatLimit);
75 | writer.Flush();
76 |
77 | var data = writer.Array;
78 |
79 | var reader = new BitReader(data);
80 |
81 | reader.ReadFloat(1.0f, Limits.FloatLimit).Should().BeApproximately(5.0f, Limits.FloatLimit.Precision);
82 | }
83 |
84 | [Fact]
85 | public void WriteValueIfChangedAndReadFloat_ValueIsNotChanged_ReadValueShouldBeEqualToBaseline()
86 | {
87 | var writer = new BitWriter();
88 |
89 | writer.WriteValueIfChanged(1.0f, 1.0f, Limits.FloatLimit);
90 | writer.Flush();
91 |
92 | var data = writer.Array;
93 |
94 | var reader = new BitReader(data);
95 |
96 | reader.ReadFloat(1.0f, Limits.FloatLimit).Should().BeApproximately(1.0f, Limits.FloatLimit.Precision);
97 | }
98 |
99 | [Fact]
100 | public void WriteDiffIfChangedAndReadInt_DiffSuitsDiffLimits_ReadValueShouldBeEqualToWritten()
101 | {
102 | var writer = new BitWriter();
103 |
104 | writer.WriteDiffIfChanged(10, 11, Limits.IntLimit, DiffLimits.IntLimit);
105 |
106 | writer.BitsCount.Should().Be(2 + 3);
107 | writer.Flush();
108 |
109 | var data = writer.Array;
110 |
111 | var reader = new BitReader(data);
112 |
113 | reader.ReadInt(10, Limits.IntLimit, DiffLimits.IntLimit).Should().Be(11);
114 | }
115 |
116 | [Fact]
117 | public void WriteDiffIfChangedAndReadInt_DiffDoesNotSuitDiffLimits_ReadValueShouldBeEqualToWritten()
118 | {
119 | var writer = new BitWriter();
120 |
121 | writer.WriteDiffIfChanged(10, 50, Limits.IntLimit, DiffLimits.IntLimit);
122 |
123 | writer.BitsCount.Should().Be(2 + 7);
124 | writer.Flush();
125 |
126 | var data = writer.Array;
127 |
128 | var reader = new BitReader(data);
129 |
130 | reader.ReadInt(10, Limits.IntLimit, DiffLimits.IntLimit).Should().Be(50);
131 | }
132 |
133 | [Fact]
134 | public void WriteDiffIfChangedAndReadInt_ValueIsNotChanged_ReadValueShouldBeEqualToBaseline()
135 | {
136 | var writer = new BitWriter();
137 |
138 | writer.WriteDiffIfChanged(10, 10, Limits.IntLimit, DiffLimits.IntLimit);
139 |
140 | writer.BitsCount.Should().Be(1);
141 | writer.Flush();
142 |
143 | var data = writer.Array;
144 |
145 | var reader = new BitReader(data);
146 |
147 | reader.ReadInt(10, Limits.IntLimit, DiffLimits.IntLimit).Should().Be(10);
148 | }
149 |
150 | [Fact]
151 | public void WriteDiffIfChangedAndReadFloat_DiffSuitsDiffLimits_ReadValueShouldBeEqualToWritten()
152 | {
153 | var writer = new BitWriter();
154 |
155 | writer.WriteDiffIfChanged(1.0f, 1.1f, Limits.FloatLimit, DiffLimits.FloatLimit);
156 |
157 | writer.BitsCount.Should().Be(2 + 5);
158 | writer.Flush();
159 |
160 | var data = writer.Array;
161 |
162 | var reader = new BitReader(data);
163 |
164 | reader.ReadFloat(1.0f, Limits.FloatLimit, DiffLimits.FloatLimit).Should().BeApproximately(1.1f, DiffLimits.FloatLimit.Precision);
165 | }
166 |
167 | [Fact]
168 | public void WriteDiffIfChangedAndReadFloat_DiffDoesNotSuitDiffLimits_ReadValueShouldBeEqualToWritten()
169 | {
170 | var writer = new BitWriter();
171 |
172 | writer.WriteDiffIfChanged(1.0f, 5.0f, Limits.FloatLimit, DiffLimits.FloatLimit);
173 |
174 | writer.BitsCount.Should().Be(2 + 7);
175 | writer.Flush();
176 |
177 | var data = writer.Array;
178 |
179 | var reader = new BitReader(data);
180 |
181 | reader.ReadFloat(1.0f, Limits.FloatLimit, DiffLimits.FloatLimit).Should().BeApproximately(5.0f, DiffLimits.FloatLimit.Precision);
182 | }
183 |
184 | [Fact]
185 | public void WriteDiffIfChangedAndReadFloat_ValueIsNotChanged_ReadValueShouldBeEqualToBaseline()
186 | {
187 | var writer = new BitWriter();
188 |
189 | writer.WriteDiffIfChanged(1.0f, 1.0f, Limits.FloatLimit, DiffLimits.FloatLimit);
190 |
191 | writer.BitsCount.Should().Be(1);
192 | writer.Flush();
193 |
194 | var data = writer.Array;
195 |
196 | var reader = new BitReader(data);
197 |
198 | reader.ReadFloat(1.0f, Limits.FloatLimit, DiffLimits.FloatLimit).Should().BeApproximately(1.0f, DiffLimits.FloatLimit.Precision);
199 | }
200 |
201 | public static class Limits
202 | {
203 | public static IntLimit IntLimit = new IntLimit(0, 100);
204 |
205 | public static UIntLimit UIntLimit = new UIntLimit(0, 100);
206 |
207 | public static FloatLimit FloatLimit = new FloatLimit(0f, 10f, 0.1f);
208 | }
209 |
210 | public static class DiffLimits
211 | {
212 | public static IntLimit IntLimit = new IntLimit(-2, 2);
213 |
214 | public static FloatLimit FloatLimit = new FloatLimit(-1f, 1f, 0.1f);
215 | }
216 | }
--------------------------------------------------------------------------------
/NetCode.UnitTests/MathTests.cs:
--------------------------------------------------------------------------------
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 | }
--------------------------------------------------------------------------------
/NetCode.UnitTests/NetCode.UnitTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 |
7 | false
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | runtime; build; native; contentfiles; analyzers; buildtransitive
16 | all
17 |
18 |
19 | runtime; build; native; contentfiles; analyzers; buildtransitive
20 | all
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/NetCode.UnitTests/Quantization/BitReaderAndWriter.Float.Tests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using FluentAssertions;
3 | using Xunit;
4 |
5 | namespace NetCode.UnitTests.Quantization;
6 |
7 | public class BitReaderAndWriter_Float_Tests
8 | {
9 | [Theory]
10 | [InlineData(1f, 0f, 10f, 1f, 4)]
11 | [InlineData(1f, 0f, 10f, 0.1f, 7)]
12 | [InlineData(1f, 0f, 10f, 0.01f, 10)]
13 | [InlineData(1f, 0f, 10f, 0.001f, 14)]
14 | [InlineData(1f, 0f, 10f, 0.0001f, 17)]
15 |
16 | [InlineData(0f, 0f, 10f, 1f, 4)]
17 | [InlineData(0f, 0f, 10f, 0.1f, 7)]
18 | [InlineData(0f, 0f, 10f, 0.01f, 10)]
19 | [InlineData(0f, 0f, 10f, 0.001f, 14)]
20 | [InlineData(0f, 0f, 10f, 0.0001f, 17)]
21 |
22 | [InlineData(10f, 0f, 10f, 1f, 4)]
23 | [InlineData(10f, 0f, 10f, 0.1f, 7)]
24 | [InlineData(10f, 0f, 10f, 0.01f, 10)]
25 | [InlineData(10f, 0f, 10f, 0.001f, 14)]
26 | [InlineData(10f, 0f, 10f, 0.0001f, 17)]
27 |
28 | [InlineData(0f, -10f, 10f, 1f, 5)]
29 | [InlineData(0f, -10f, 10f, 0.1f, 8)]
30 | [InlineData(0f, -10f, 10f, 0.01f, 11)]
31 | [InlineData(0f, -10f, 10f, 0.001f, 15)]
32 | [InlineData(0f, -10f, 10f, 0.0001f, 18)]
33 |
34 | [InlineData(-1f, -10f, 0f, 1f, 4)]
35 | [InlineData(-1f, -10f, 0f, 0.1f, 7)]
36 | [InlineData(-1f, -10f, 0f, 0.01f, 10)]
37 | [InlineData(-1f, -10f, 0f, 0.001f, 14)]
38 | [InlineData(-1f, -10f, 0f, 0.0001f, 17)]
39 |
40 | [InlineData(-10f, -10f, 0f, 1f, 4)]
41 | [InlineData(-10f, -10f, 0f, 0.1f, 7)]
42 | [InlineData(-10f, -10f, 0f, 0.01f, 10)]
43 | [InlineData(-10f, -10f, 0f, 0.001f, 14)]
44 | [InlineData(-10f, -10f, 0f, 0.0001f, 17)]
45 |
46 | [InlineData(0f, -10f, 0f, 1f, 4)]
47 | [InlineData(0f, -10f, 0f, 0.1f, 7)]
48 | [InlineData(0f, -10f, 0f, 0.01f, 10)]
49 | [InlineData(0f, -10f, 0f, 0.001f, 14)]
50 | [InlineData(0f, -10f, 0f, 0.0001f, 17)]
51 |
52 | [InlineData(0f, 0f, 1f, 1f, 1)]
53 | [InlineData(0f, 0f, 1f, 0.1f, 4)]
54 | [InlineData(0f, 0f, 1f, 0.01f, 7)]
55 | [InlineData(0f, 0f, 1f, 0.001f, 10)]
56 | [InlineData(0f, 0f, 1f, 0.0001f, 14)]
57 |
58 | [InlineData(0f, -1f, 0f, 1f, 1)]
59 | [InlineData(0f, -1f, 0f, 0.1f, 4)]
60 | [InlineData(0f, -1f, 0f, 0.01f, 7)]
61 | [InlineData(0f, -1f, 0f, 0.001f, 10)]
62 | [InlineData(0f, -1f, 0f, 0.0001f, 14)]
63 |
64 | [InlineData(1f, 1f, 2f, 1f, 1)]
65 | [InlineData(1f, 1f, 2f, 0.1f, 4)]
66 | [InlineData(1f, 1f, 2f, 0.01f, 7)]
67 | [InlineData(1f, 1f, 2f, 0.001f, 10)]
68 | [InlineData(1f, 1f, 2f, 0.0001f, 14)]
69 |
70 | [InlineData(2f, 1f, 2f, 1f, 1)]
71 | [InlineData(2f, 1f, 2f, 0.1f, 4)]
72 | [InlineData(2f, 1f, 2f, 0.01f, 7)]
73 | [InlineData(2f, 1f, 2f, 0.001f, 10)]
74 | [InlineData(2f, 1f, 2f, 0.0001f, 14)]
75 |
76 | [InlineData(-2f, -2f, -1f, 1f, 1)]
77 | [InlineData(-2f, -2f, -1f, 0.1f, 4)]
78 | [InlineData(-2f, -2f, -1f, 0.01f, 7)]
79 | [InlineData(-2f, -2f, -1f, 0.001f, 10)]
80 | [InlineData(-2f, -2f, -1f, 0.0001f, 14)]
81 |
82 | [InlineData(-1f, -2f, -1f, 1f, 1)]
83 | [InlineData(-1f, -2f, -1f, 0.1f, 4)]
84 | [InlineData(-1f, -2f, -1f, 0.01f, 7)]
85 | [InlineData(-1f, -2f, -1f, 0.001f, 10)]
86 | [InlineData(-1f, -2f, -1f, 0.0001f, 14)]
87 | public void WriteReadFloat_PositiveCases_ValueShouldBeApproximatelyTheSame(float value, float min, float max, float precision, int bitCount)
88 | {
89 | var writer = new BitWriter();
90 |
91 | writer.Write(value, min, max, precision);
92 | writer.BitsCount.Should().Be(bitCount);
93 |
94 | writer.Flush();
95 |
96 | var data = writer.Array;
97 |
98 | var reader = new BitReader(data);
99 | reader.ReadFloat(min, max, precision).Should().BeApproximately(value, precision);
100 | }
101 |
102 | [Theory]
103 | [InlineData(0f, 0f, 0f, 1f)]
104 | [InlineData(0f, 0f, 0f, 0.1f)]
105 | [InlineData(0f, 0f, 0f, 0.01f)]
106 | [InlineData(0f, 0f, 0f, 0.001f)]
107 | [InlineData(0f, 0f, 0f, 0.0001f)]
108 |
109 | [InlineData(1f, 1f, 1f, 1f)]
110 | [InlineData(1f, 1f, 1f, 0.1f)]
111 | [InlineData(1f, 1f, 1f, 0.01f)]
112 | [InlineData(1f, 1f, 1f, 0.001f)]
113 | [InlineData(1f, 1f, 1f, 0.0001f)]
114 |
115 | [InlineData(-1f, -1f, -1f, 1f)]
116 | [InlineData(-1f, -1f, -1f, 0.1f)]
117 | [InlineData(-1f, -1f, -1f, 0.01f)]
118 | [InlineData(-1f, -1f, -1f, 0.001f)]
119 | [InlineData(-1f, -1f, -1f, 0.0001f)]
120 | public void WriteReadFloat_MinAndMaxAreTheSame_ExceptionExpected(float value, float min, float max, float precision)
121 | {
122 | var writer = new BitWriter();
123 |
124 | Action action = () => writer.Write(value, min, max, precision);
125 |
126 | action.Should().Throw();
127 | }
128 |
129 | [Theory]
130 | [InlineData(2f, 0f, 1f, 0.1f)]
131 | [InlineData(-1f, 0f, 1f, 0.1f)]
132 |
133 | [InlineData(-2f, -1f, 0f, 0.1f)]
134 | [InlineData(1f, -1f, 0f, 0.1f)]
135 |
136 | [InlineData(10f, 1f, 5f, 0.1f)]
137 | [InlineData(-10f, -5f, -1f, 0.1f)]
138 |
139 | [InlineData(-10f, -5f, 5f, 0.1f)]
140 | [InlineData(10f, -5f, 5f, 0.1f)]
141 | public void WriteFloat_ValueOutOfLimitRangeForDebug_ExceptionExpected(float value, float min, float max, float precision)
142 | {
143 | #if !DEBUG
144 | return;
145 | #endif
146 |
147 | var writer = new BitWriter();
148 |
149 | Action action = () => writer.Write(value, min, max, precision);
150 |
151 | action.Should().Throw();
152 | }
153 |
154 | [Theory]
155 | [InlineData(1f, 1f, 0f, 0.1f)]
156 | [InlineData(0f, 1f, 0f, 0.1f)]
157 | [InlineData(-1f, 0f, -1f, 0.1f)]
158 | [InlineData(0f, 0f, -1f, 0.1f)]
159 | public void WriteFloat_RangeIsNotValidForDebug_ExceptionExpected(float value, float min, float max, float precision)
160 | {
161 | var writer = new BitWriter();
162 |
163 | Action action = () => writer.Write(value, min, max, precision);
164 |
165 | action.Should().Throw();
166 | }
167 |
168 | [Theory]
169 | [InlineData(2f, 0f, 1f, 0.1f)]
170 | [InlineData(-1f, 0f, 1f, 0.1f)]
171 |
172 | [InlineData(-2f, -1f, 0f, 0.1f)]
173 | [InlineData(1f, -1f, 0f, 0.1f)]
174 |
175 | [InlineData(10f, 1f, 5f, 0.1f)]
176 | [InlineData(-10f, -5f, -1f, 0.1f)]
177 |
178 | [InlineData(-10f, -5f, 5f, 0.1f)]
179 | [InlineData(10f, -5f, 5f, 0.1f)]
180 | public void WriteFloat_ValueOutOfLimitRangeForRelease_ShouldNotThrow(float value, float min, float max, float precision)
181 | {
182 | #if DEBUG
183 | return;
184 | #endif
185 |
186 | var writer = new BitWriter();
187 |
188 | Action action = () =>
189 | {
190 | writer.Write(value, min, max, precision);
191 | writer.Flush();
192 | };
193 |
194 | action.Should().NotThrow();
195 | }
196 | }
--------------------------------------------------------------------------------
/NetCode.UnitTests/Quantization/BitReaderAndWriter.Int.Tests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using FluentAssertions;
3 | using Xunit;
4 |
5 | namespace NetCode.UnitTests.Quantization;
6 |
7 | public class BitReaderAndWriter_Int_Tests
8 | {
9 | [Theory]
10 | [InlineData(1, 0, 10, 4)]
11 | [InlineData(0, 0, 10, 4)]
12 | [InlineData(10, 0, 10, 4)]
13 |
14 | [InlineData(0, -10, 10, 5)]
15 |
16 | [InlineData(-1, -10, 0, 4)]
17 | [InlineData(-10, -10, 0, 4)]
18 | [InlineData(0, -10, 0, 4)]
19 |
20 | [InlineData(0, 0, 0, 1)]
21 | [InlineData(1, 1, 1, 1)]
22 | [InlineData(-1, -1, -1, 1)]
23 |
24 | [InlineData(0, 0, 1, 1)]
25 | [InlineData(0, -1, 0, 1)]
26 |
27 | [InlineData(1, 1, 2, 1)]
28 | [InlineData(2, 1, 2, 1)]
29 |
30 | [InlineData(-2, -2, -1, 1)]
31 | [InlineData(-1, -2, -1, 1)]
32 |
33 | [InlineData(0, 0, int.MaxValue, 31)]
34 | [InlineData(42, 0, int.MaxValue, 31)]
35 | [InlineData(int.MaxValue, 0, int.MaxValue, 31)]
36 |
37 | [InlineData(int.MinValue, int.MinValue, -1, 31)]
38 | [InlineData(-42, int.MinValue, -1, 31)]
39 | [InlineData(-1, int.MinValue, -1, 31)]
40 |
41 | [InlineData(0, int.MinValue, int.MaxValue, 32)]
42 | [InlineData(42, int.MinValue, int.MaxValue, 32)]
43 | [InlineData(-42, int.MinValue, int.MaxValue, 32)]
44 | [InlineData(int.MinValue, int.MinValue, int.MaxValue, 32)]
45 | [InlineData(int.MaxValue, int.MinValue, int.MaxValue, 32)]
46 | public void WriteReadInt_PositiveCases_ValueShouldBeTheSame(int value, int min, int max, int bitCount)
47 | {
48 | var writer = new BitWriter();
49 |
50 | writer.Write(value, min, max);
51 | writer.BitsCount.Should().Be(bitCount);
52 |
53 | writer.Flush();
54 |
55 | var data = writer.Array;
56 |
57 | var reader = new BitReader(data);
58 | reader.ReadInt(min, max).Should().Be(value);
59 | }
60 |
61 | [Theory]
62 | [InlineData(1, 0, 0)]
63 | [InlineData(-1, 0, 0)]
64 |
65 | [InlineData(2, 1, 1)]
66 | [InlineData(-2, -1, -1)]
67 |
68 | [InlineData(2, 0, 1)]
69 | [InlineData(-1, 0, 1)]
70 |
71 | [InlineData(-2, -1, 0)]
72 | [InlineData(1, -1, 0)]
73 |
74 | [InlineData(10, 1, 5)]
75 | [InlineData(-10, -5, -1)]
76 |
77 | [InlineData(-10, -5, 5)]
78 | [InlineData(10, -5, 5)]
79 | public void WriteInt_ValueOutOfLimitRangeForDebug_ExceptionExpected(int value, int min, int max)
80 | {
81 | #if !DEBUG
82 | return;
83 | #endif
84 |
85 | var writer = new BitWriter();
86 |
87 | Action action = () => writer.Write(value, min, max);
88 |
89 | action.Should().Throw();
90 | }
91 |
92 | [Theory]
93 | [InlineData(1, 1, 0)]
94 | [InlineData(0, 1, 0)]
95 | [InlineData(-1, 0, -1)]
96 | [InlineData(0, 0, -1)]
97 | public void WriteInt_RangeIsNotValidForDebug_ExceptionExpected(int value, int min, int max)
98 | {
99 | var writer = new BitWriter();
100 |
101 | Action action = () => writer.Write(value, min, max);
102 |
103 | action.Should().Throw();
104 | }
105 |
106 | [Theory]
107 | [InlineData(1, 0, 0)]
108 | [InlineData(-1, 0, 0)]
109 |
110 | [InlineData(2, 1, 1)]
111 | [InlineData(-2, -1, -1)]
112 |
113 | [InlineData(2, 0, 1)]
114 | [InlineData(-1, 0, 1)]
115 |
116 | [InlineData(-2, -1, 0)]
117 | [InlineData(1, -1, 0)]
118 |
119 | [InlineData(10, 1, 5)]
120 | [InlineData(-10, -5, -1)]
121 |
122 | [InlineData(-10, -5, 5)]
123 | [InlineData(10, -5, 5)]
124 | public void WriteInt_ValueOutOfLimitRangeForRelease_ShouldNotThrow(int value, int min, int max)
125 | {
126 | #if DEBUG
127 | return;
128 | #endif
129 |
130 | var writer = new BitWriter();
131 |
132 | Action action = () =>
133 | {
134 | writer.Write(value, min, max);
135 | writer.Flush();
136 | };
137 |
138 | action.Should().NotThrow();
139 | }
140 | }
--------------------------------------------------------------------------------
/NetCode.UnitTests/Quantization/BitReaderAndWriter.UInt.Tests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using FluentAssertions;
3 | using Xunit;
4 |
5 | namespace NetCode.UnitTests.Quantization;
6 |
7 | public class BitReaderAndWriter_UInt_Tests
8 | {
9 | [Theory]
10 | [InlineData(1, 0, 10, 4)]
11 | [InlineData(0, 0, 10, 4)]
12 | [InlineData(10, 0, 10, 4)]
13 |
14 | [InlineData(0, 0, 0, 1)]
15 | [InlineData(1, 1, 1, 1)]
16 |
17 | [InlineData(0, 0, 1, 1)]
18 |
19 | [InlineData(1, 1, 2, 1)]
20 | [InlineData(2, 1, 2, 1)]
21 |
22 | [InlineData(0, uint.MinValue, uint.MaxValue, 32)]
23 | [InlineData(42, uint.MinValue, uint.MaxValue, 32)]
24 | [InlineData(uint.MinValue, uint.MinValue, uint.MaxValue, 32)]
25 | [InlineData(uint.MaxValue, uint.MinValue, uint.MaxValue, 32)]
26 | public void WriteReadUInt_PositiveCases_ValueShouldBeTheSame(uint value, uint min, uint max, int bitCount)
27 | {
28 | var writer = new BitWriter();
29 |
30 | writer.Write(value, min, max);
31 | writer.BitsCount.Should().Be(bitCount);
32 |
33 | writer.Flush();
34 |
35 | var data = writer.Array;
36 |
37 | var reader = new BitReader(data);
38 | reader.ReadUInt(min, max).Should().Be(value);
39 | }
40 |
41 | [Theory]
42 | [InlineData(1, 0, 0)]
43 | [InlineData(2, 1, 1)]
44 | [InlineData(2, 0, 1)]
45 | [InlineData(10, 1, 5)]
46 | public void WriteUInt_ValueOutOfLimitRangeForDebug_ExceptionExpected(uint value, uint min, uint max)
47 | {
48 | #if !DEBUG
49 | return;
50 | #endif
51 |
52 | var writer = new BitWriter();
53 |
54 | Action action = () => writer.Write(value, min, max);
55 |
56 | action.Should().Throw();
57 | }
58 |
59 | [Theory]
60 | [InlineData(1, 1, 0)]
61 | [InlineData(0, 1, 0)]
62 | public void WriteUInt_RangeIsNotValidForDebug_ExceptionExpected(uint value, uint min, uint max)
63 | {
64 | var writer = new BitWriter();
65 |
66 | Action action = () => writer.Write(value, min, max);
67 |
68 | action.Should().Throw();
69 | }
70 |
71 | [Theory]
72 | [InlineData(1, 0, 0)]
73 | [InlineData(2, 1, 1)]
74 | [InlineData(2, 0, 1)]
75 | [InlineData(10, 1, 5)]
76 | public void WriteUInt_ValueOutOfLimitRangeForRelease_ShouldNotThrow(uint value, uint min, uint max)
77 | {
78 | #if DEBUG
79 | return;
80 | #endif
81 |
82 | var writer = new BitWriter();
83 |
84 | Action action = () =>
85 | {
86 | writer.Write(value, min, max);
87 | writer.Flush();
88 | };
89 |
90 | action.Should().NotThrow();
91 | }
92 | }
--------------------------------------------------------------------------------
/NetCode.UnitTests/Quantization/BitWriter.Int.Tests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using Xunit;
3 |
4 | namespace NetCode.UnitTests.Quantization;
5 |
6 | public class BitWriter_Int_Tests
7 | {
8 | [Fact]
9 | public void WriteValueMinMax_SmallValueIsLimitedTo8PossibleValues_ShouldWrite3Bits()
10 | {
11 | var writer = new BitWriter();
12 |
13 | writer.Write(5, 0, 7);
14 |
15 | writer.BitsCount.Should().Be(3);
16 | writer.Flush();
17 |
18 | writer.BytesCount.Should().Be(1);
19 |
20 | writer.Array[0].Should().Be(5);
21 | }
22 |
23 | [Fact]
24 | public void WriteValueMinMax_MiddleValueIsLimitedTo8PossibleValues_ShouldWrite3Bits()
25 | {
26 | var writer = new BitWriter();
27 |
28 | writer.Write(100501, 100500, 100507);
29 |
30 | writer.BitsCount.Should().Be(3);
31 | writer.Flush();
32 |
33 | writer.BytesCount.Should().Be(1);
34 |
35 | writer.Array[0].Should().Be(0b_001);
36 | }
37 |
38 | [Fact]
39 | public void WriteValueMinMax_BigValueIsLimitedTo8PossibleValues_ShouldWrite3Bits()
40 | {
41 | var writer = new BitWriter();
42 |
43 | writer.Write(int.MaxValue - 1, int.MaxValue - 7, int.MaxValue);
44 |
45 | writer.BitsCount.Should().Be(3);
46 | writer.Flush();
47 |
48 | writer.BytesCount.Should().Be(1);
49 |
50 | writer.Array[0].Should().Be(0b_110);
51 | }
52 |
53 | [Fact]
54 | public void WriteValueMinMax_NegativeValueIsLimitedTo8PossibleValues_ShouldWrite3Bits()
55 | {
56 | var writer = new BitWriter();
57 |
58 | writer.Write(int.MinValue + 1, int.MinValue, int.MinValue + 7);
59 |
60 | writer.BitsCount.Should().Be(3);
61 | writer.Flush();
62 |
63 | writer.BytesCount.Should().Be(1);
64 |
65 | writer.Array[0].Should().Be(0b_001);
66 | }
67 |
68 | [Fact]
69 | public void WriteValueMinMax_ValueIsLimitedTo256PossibleValues_ShouldWrite8Bits()
70 | {
71 | var writer = new BitWriter();
72 |
73 | writer.Write(254, 0, 255);
74 |
75 | writer.BitsCount.Should().Be(8);
76 | writer.Flush();
77 |
78 | writer.BytesCount.Should().Be(1);
79 |
80 | writer.Array[0].Should().Be(254);
81 | }
82 |
83 | [Fact]
84 | public void WriteValueMinMax_ValueIsLimitedTo257PossibleValues_ShouldWrite9Bits()
85 | {
86 | var writer = new BitWriter();
87 |
88 | writer.Write(255, 0, 256);
89 |
90 | writer.BitsCount.Should().Be(9);
91 | writer.Flush();
92 |
93 | writer.BytesCount.Should().Be(2);
94 |
95 | writer.Array[0].Should().Be(0b_1111_1111);
96 | writer.Array[1].Should().Be(0b_0000_0000);
97 | }
98 |
99 | [Fact]
100 | public void WriteValueMinMax_ValueIsLimitedTo258PossibleValues_ShouldWrite9Bits()
101 | {
102 | var writer = new BitWriter();
103 |
104 | writer.Write(256, 0, 257);
105 |
106 | writer.BitsCount.Should().Be(9);
107 | writer.Flush();
108 |
109 | writer.BytesCount.Should().Be(2);
110 |
111 | writer.Array[0].Should().Be(0b_0000_0000);
112 | writer.Array[1].Should().Be(0b_0000_0001);
113 | }
114 | }
--------------------------------------------------------------------------------
/NetCode.UnitTests/SevenBitEncodingTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using Xunit;
3 |
4 | namespace NetCode.UnitTests;
5 |
6 | public class SevenBitEncodingTests
7 | {
8 | [Fact]
9 | public void WriteCompressed_WriteUInt1ByteNumber_BytesCountShouldBe1()
10 | {
11 | var bitWriter = new BitWriter();
12 | bitWriter.WriteCompressed((uint)16);
13 | bitWriter.Flush();
14 |
15 | bitWriter.BytesCount.Should().Be(1);
16 | }
17 |
18 | [Fact]
19 | public void WriteCompressed_WriteUInt7BitValue_BytesCountShouldBe1()
20 | {
21 | var bitWriter = new BitWriter();
22 | bitWriter.WriteCompressed((uint)0b_00000000_00000000_00000000_01111111);
23 | bitWriter.Flush();
24 |
25 | bitWriter.BytesCount.Should().Be(1);
26 | }
27 |
28 | [Fact]
29 | public void WriteCompressed_WriteUInt8BitValue_BytesCountShouldBe2()
30 | {
31 | var bitWriter = new BitWriter();
32 | bitWriter.WriteCompressed((uint)0b_00000000_00000000_00000000_11111111);
33 | bitWriter.Flush();
34 |
35 | bitWriter.BytesCount.Should().Be(2);
36 | }
37 |
38 | [Fact]
39 | public void WriteCompressed_WriteUInt14BitValue_BytesCountShouldBe2()
40 | {
41 | var bitWriter = new BitWriter();
42 | bitWriter.WriteCompressed((uint)0b_00000000_00000000_00111111_11111111);
43 | bitWriter.Flush();
44 |
45 | bitWriter.BytesCount.Should().Be(2);
46 | }
47 |
48 | [Fact]
49 | public void WriteCompressed_WriteUInt15BitValue_BytesCountShouldBe3()
50 | {
51 | var bitWriter = new BitWriter();
52 | bitWriter.WriteCompressed((uint)0b_00000000_00000000_01111111_11111111);
53 | bitWriter.Flush();
54 |
55 | bitWriter.BytesCount.Should().Be(3);
56 | }
57 |
58 | [Fact]
59 | public void WriteCompressed_WriteUInt21BitValue_BytesCountShouldBe3()
60 | {
61 | var bitWriter = new BitWriter();
62 | bitWriter.WriteCompressed((uint)0b_00000000_00011111_11111111_11111111);
63 | bitWriter.Flush();
64 |
65 | bitWriter.BytesCount.Should().Be(3);
66 | }
67 |
68 | [Fact]
69 | public void WriteCompressed_WriteUInt22BitValue_BytesCountShouldBe4()
70 | {
71 | var bitWriter = new BitWriter();
72 | bitWriter.WriteCompressed((uint)0b_00000000_00111111_11111111_11111111);
73 | bitWriter.Flush();
74 |
75 | bitWriter.BytesCount.Should().Be(4);
76 | }
77 |
78 | [Fact]
79 | public void WriteCompressed_WriteUInt28BitValue_BytesCountShouldBe4()
80 | {
81 | var bitWriter = new BitWriter();
82 | bitWriter.WriteCompressed((uint)0b_00001111_11111111_11111111_11111111);
83 | bitWriter.Flush();
84 |
85 | bitWriter.BytesCount.Should().Be(4);
86 | }
87 |
88 | [Fact]
89 | public void WriteCompressed_WriteUInt29BitValue_BytesCountShouldBe5()
90 | {
91 | var bitWriter = new BitWriter();
92 | bitWriter.WriteCompressed((uint)0b_00011111_11111111_11111111_11111111);
93 | bitWriter.Flush();
94 |
95 | bitWriter.BytesCount.Should().Be(5);
96 | }
97 | }
--------------------------------------------------------------------------------
/NetCode.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetCode", "NetCode\NetCode.csproj", "{3B50FE16-4FA2-4B52-B659-CCD336EED494}"
4 | EndProject
5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetCode.UnitTests", "NetCode.UnitTests\NetCode.UnitTests.csproj", "{69A08C88-D01F-4993-BD77-11AEF581BE5B}"
6 | EndProject
7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetCode.Benchmarks", "NetCode.Benchmarks\NetCode.Benchmarks.csproj", "{05155866-8696-4C51-88DF-838EF4A38988}"
8 | EndProject
9 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetCode.Demo", "NetCode.Demo\NetCode.Demo.csproj", "{CB3ACB4A-6719-4F16-AEF7-1BF2C491F19F}"
10 | EndProject
11 | Global
12 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
13 | Debug|Any CPU = Debug|Any CPU
14 | Release|Any CPU = Release|Any CPU
15 | EndGlobalSection
16 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
17 | {3B50FE16-4FA2-4B52-B659-CCD336EED494}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
18 | {3B50FE16-4FA2-4B52-B659-CCD336EED494}.Debug|Any CPU.Build.0 = Debug|Any CPU
19 | {3B50FE16-4FA2-4B52-B659-CCD336EED494}.Release|Any CPU.ActiveCfg = Release|Any CPU
20 | {3B50FE16-4FA2-4B52-B659-CCD336EED494}.Release|Any CPU.Build.0 = Release|Any CPU
21 | {69A08C88-D01F-4993-BD77-11AEF581BE5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
22 | {69A08C88-D01F-4993-BD77-11AEF581BE5B}.Debug|Any CPU.Build.0 = Debug|Any CPU
23 | {69A08C88-D01F-4993-BD77-11AEF581BE5B}.Release|Any CPU.ActiveCfg = Release|Any CPU
24 | {69A08C88-D01F-4993-BD77-11AEF581BE5B}.Release|Any CPU.Build.0 = Release|Any CPU
25 | {05155866-8696-4C51-88DF-838EF4A38988}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26 | {05155866-8696-4C51-88DF-838EF4A38988}.Debug|Any CPU.Build.0 = Debug|Any CPU
27 | {05155866-8696-4C51-88DF-838EF4A38988}.Release|Any CPU.ActiveCfg = Release|Any CPU
28 | {05155866-8696-4C51-88DF-838EF4A38988}.Release|Any CPU.Build.0 = Release|Any CPU
29 | {CB3ACB4A-6719-4F16-AEF7-1BF2C491F19F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
30 | {CB3ACB4A-6719-4F16-AEF7-1BF2C491F19F}.Debug|Any CPU.Build.0 = Debug|Any CPU
31 | {CB3ACB4A-6719-4F16-AEF7-1BF2C491F19F}.Release|Any CPU.ActiveCfg = Release|Any CPU
32 | {CB3ACB4A-6719-4F16-AEF7-1BF2C491F19F}.Release|Any CPU.Build.0 = Release|Any CPU
33 | EndGlobalSection
34 | EndGlobal
35 |
--------------------------------------------------------------------------------
/NetCode/BitConverterNetStandart20.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | namespace NetCode;
4 |
5 | #if NETSTANDARD2_0
6 |
7 | public static class BitConverterNetstandard20
8 | {
9 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
10 | public static unsafe int SingleToInt32Bits(float value) => *((int*)&value);
11 |
12 | ///
13 | /// Converts the specified 32-bit unsigned integer to a single-precision floating point number.
14 | ///
15 | /// The number to convert.
16 | /// A single-precision floating point number whose bits are identical to .
17 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
18 | public static unsafe float Int32BitsToSingle(int value) => *((float*)&value);
19 |
20 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
21 | public static unsafe long DoubleToInt64Bits(double value) => *((long*)&value);
22 |
23 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
24 | public static unsafe double Int64BitsToDouble(long value) => *((double*)&value);
25 | }
26 |
27 | #endif
--------------------------------------------------------------------------------
/NetCode/BitReader.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | namespace NetCode;
4 |
5 | public sealed class BitReader : IBitReader
6 | {
7 | private static readonly uint[] Masks;
8 |
9 | private ByteReader _byteReader;
10 | private ulong _buffer;
11 | private int _bitsInBuffer;
12 |
13 | static BitReader()
14 | {
15 | Masks = new uint[33];
16 | for (int i = 1; i < Masks.Length - 1; i++)
17 | {
18 | var mask = (1u << i) - 1;
19 | Masks[i] = mask;
20 | }
21 |
22 | Masks[32] = uint.MaxValue;
23 | }
24 |
25 | public BitReader() : this(new ByteReader())
26 | {
27 | }
28 |
29 | public BitReader(byte[] data) : this(new ByteReader(data))
30 | {
31 | }
32 |
33 | public BitReader(ByteReader byteReader)
34 | {
35 | _byteReader = byteReader;
36 | }
37 |
38 | public int Start => _byteReader.Start;
39 |
40 | public int End => _byteReader.End;
41 |
42 | ///
43 | /// Returns the number of bytes and bits that can be read from this instance.
44 | /// Example. We read 3 bits from 2 bytes array: 0b_00001111_00001111. Tuple (Bytes: 1, Bits: 5) will be returned.
45 | /// ^
46 | ///
47 | public (int Bytes, int Bits) RemainingToRead
48 | {
49 | get
50 | {
51 | var (quotient, remainder) = Mathi.DivRem(_bitsInBuffer, 8);
52 |
53 | return (_byteReader.RemainingToRead + quotient, remainder);
54 | }
55 | }
56 |
57 | ///
58 | /// Returns the pointer with current position of reading value.
59 | /// Example. We read 3 bits from 2 bytes array: 0b_00001111_00001111. Tuple (Bytes: 0, Bits: 3) will be returned.
60 | /// ^
61 | ///
62 | public (int Bytes, int Bits) Head
63 | {
64 | get
65 | {
66 | var (quotient, remainder) = Mathi.DivRem(_bitsInBuffer, 8);
67 |
68 | if (remainder == 0)
69 | {
70 | return (_byteReader.Head - quotient, 0);
71 | }
72 |
73 | return (_byteReader.Head - quotient - 1, 8 - remainder);
74 | }
75 | }
76 |
77 | ///
78 | /// Returns current position.
79 | /// Example. We had offset 1 and read 3 bits from 2 bytes array: 0b_00001111_00001111. Tuple (Bytes: 0, Bits: 3) will be returned.
80 | /// ^
81 | ///
82 | public (int Bytes, int Bits) Position
83 | {
84 | get
85 | {
86 | var (quotient, remainder) = Mathi.DivRem(_bitsInBuffer, 8);
87 |
88 | if (remainder == 0)
89 | {
90 | return (_byteReader.Head - _byteReader.Start - quotient, 0);
91 | }
92 |
93 | return (_byteReader.Head - _byteReader.Start - quotient - 1, 8 - remainder);
94 | }
95 | }
96 |
97 | ///
98 | /// Returns Position.Bytes * 8 + Position.Bits.
99 | ///
100 | public int BitsPosition => (_byteReader.Head - _byteReader.Start) * 8 - _bitsInBuffer;
101 |
102 | public void SetArray(byte[] data) => SetArray(data, 0);
103 |
104 | public void SetArray(byte[] data, int offset) => SetArray(data, offset, data.Length - offset);
105 |
106 | public void SetArray(byte[] data, int start, int length)
107 | {
108 | _byteReader.SetArray(data, start, length);
109 |
110 | _buffer = 0;
111 | _bitsInBuffer = 0;
112 | }
113 |
114 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
115 | public void Reset()
116 | {
117 | _byteReader.Reset();
118 | _buffer = 0;
119 | _bitsInBuffer = 0;
120 | }
121 |
122 | public uint ReadBits(int bitCount)
123 | {
124 | if (bitCount > _bitsInBuffer)
125 | {
126 | FillBuffer(bitCount);
127 | }
128 |
129 | uint value = (uint)_buffer & Masks[bitCount];
130 | _buffer >>= bitCount;
131 | _bitsInBuffer -= bitCount;
132 | return value;
133 | }
134 |
135 | public bool ReadBool()
136 | {
137 | var bits = ReadBits(1);
138 | return bits == 1;
139 | }
140 |
141 | private void FillBuffer(int bitCount)
142 | {
143 | (ulong temp, int readBytes) = _byteReader.TryReadUInt();
144 | temp <<= _bitsInBuffer;
145 | _buffer |= temp;
146 | _bitsInBuffer += readBytes * 8;
147 | if (bitCount > _bitsInBuffer)
148 | {
149 | ThrowHelper.ThrowIndexOutOfRangeException();
150 | }
151 | }
152 |
153 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
154 | public byte ReadByte()
155 | {
156 | if (_bitsInBuffer == 0)
157 | {
158 | return _byteReader.ReadByte();
159 | }
160 |
161 | return (byte)ReadBits(8);
162 | }
163 |
164 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
165 | public ushort ReadUShort()
166 | {
167 | if (_bitsInBuffer == 0)
168 | {
169 | return _byteReader.ReadUShort();
170 | }
171 |
172 | return (ushort)ReadBits(16);
173 | }
174 |
175 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
176 | public short ReadShort()
177 | {
178 | if (_bitsInBuffer == 0)
179 | {
180 | return _byteReader.ReadShort();
181 | }
182 |
183 | return (short)ReadBits(16);
184 | }
185 |
186 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
187 | public uint ReadUInt()
188 | {
189 | if (_bitsInBuffer == 0)
190 | {
191 | return _byteReader.ReadUInt();
192 | }
193 |
194 | return ReadBits(32);
195 | }
196 |
197 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
198 | public int ReadInt()
199 | {
200 | if (_bitsInBuffer == 0)
201 | {
202 | return _byteReader.ReadInt();
203 | }
204 |
205 | return (int)ReadBits(32);
206 | }
207 | }
--------------------------------------------------------------------------------
/NetCode/BitWriter.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | namespace NetCode;
4 |
5 | public sealed class BitWriter : IBitWriter
6 | {
7 | private const int DefaultCapacity = 1500;
8 |
9 | private static readonly uint[] Masks;
10 |
11 | private readonly ByteWriter _byteWriter;
12 | private ulong _buffer;
13 | private int _bitsInBuffer;
14 |
15 | static BitWriter()
16 | {
17 | Masks = new uint[33];
18 | for (int i = 1; i < Masks.Length - 1; i++)
19 | {
20 | var mask = (1u << i) - 1;
21 | Masks[i] = mask;
22 | }
23 |
24 | Masks[32] = uint.MaxValue;
25 | }
26 |
27 | public BitWriter(int capacity = DefaultCapacity) : this(new ByteWriter(capacity))
28 | {
29 | }
30 |
31 | public BitWriter(byte[] data) : this(new ByteWriter(data))
32 | {
33 | }
34 |
35 | public BitWriter(ByteWriter byteWriter)
36 | {
37 | _byteWriter = byteWriter;
38 | }
39 |
40 | public int BitsCount => _byteWriter.Count * 8 + _bitsInBuffer;
41 |
42 | public int BytesCount => _byteWriter.Count + Mathi.Ceiling(_bitsInBuffer, 8);
43 |
44 | public (int Bytes, int Bits) Head
45 | {
46 | get
47 | {
48 | var (quotient, remainder) = Mathi.DivRem(_bitsInBuffer, 8);
49 |
50 | return (_byteWriter.Count + quotient, remainder);
51 | }
52 | }
53 |
54 | public (int Bytes, int Bits) Position
55 | {
56 | get
57 | {
58 | var (quotient, remainder) = Mathi.DivRem(_bitsInBuffer, 8);
59 |
60 | return (_byteWriter.Count - _byteWriter.Start + quotient, remainder);
61 | }
62 | }
63 |
64 | public int BitsPosition => (_byteWriter.Count - _byteWriter.Start) * 8 + _bitsInBuffer;
65 |
66 | public int Capacity => _byteWriter.Capacity;
67 |
68 | public byte[] Array => _bitsInBuffer == 0 ? _byteWriter.Array : throw new InvalidOperationException("Writer should be flushed first.");
69 |
70 | public void SetArray(byte[] data) => SetArray(data, 0);
71 |
72 | public void SetArray(byte[] data, int offset)
73 | {
74 | _byteWriter.SetArray(data, offset);
75 |
76 | _bitsInBuffer = 0;
77 | _buffer = 0;
78 | }
79 |
80 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
81 | public void Clear()
82 | {
83 | _byteWriter.Clear();
84 | _bitsInBuffer = 0;
85 | _buffer = 0;
86 | }
87 |
88 | ///
89 | /// Set bit at specific position. You can set only if you has written data to it position.
90 | ///
91 | public void SetAt(int bitPosition, bool value)
92 | {
93 | if (bitPosition < 0 || bitPosition >= _byteWriter.Capacity * 8)
94 | throw new IndexOutOfRangeException();
95 |
96 | if (bitPosition + 1 > BitsCount)
97 | throw new NotSupportedException("You can't set bit before you write value to it position!");
98 |
99 | var (byteIndex, bitIndex) = Mathi.DivRem(bitPosition, 8);
100 | var bitPositionInBuffer = bitPosition - _byteWriter.Count * 8;
101 |
102 | if (bitPositionInBuffer >= 0)
103 | {
104 | // set bit at the buffer
105 |
106 | ulong mask = 1ul << bitPositionInBuffer;
107 |
108 | if (value)
109 | {
110 | _buffer |= mask;
111 | }
112 | else
113 | {
114 | _buffer &= ~mask;
115 | }
116 | }
117 | else
118 | {
119 | // set bit at the array
120 |
121 | var @byte = _byteWriter.Array[byteIndex];
122 |
123 | byte mask = (byte)(1 << bitIndex);
124 | if (value)
125 | {
126 | @byte |= mask;
127 | }
128 | else
129 | {
130 | @byte = (byte)(@byte & ~mask);
131 | }
132 |
133 | _byteWriter.Array[byteIndex] = @byte;
134 | }
135 | }
136 |
137 | public void WriteBits(int bitCount, uint value)
138 | {
139 | value &= Masks[bitCount];
140 |
141 | _buffer |= (ulong)value << _bitsInBuffer;
142 | _bitsInBuffer += bitCount;
143 |
144 | if (_bitsInBuffer >= 32)
145 | {
146 | uint write = (uint)_buffer;
147 | _byteWriter.Write(write);
148 |
149 | _buffer >>= 32;
150 | _bitsInBuffer -= 32;
151 | }
152 | }
153 |
154 | public void Write(bool value) => WriteBits(1, value ? 1u : 0u);
155 |
156 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
157 | public void Write(byte value)
158 | {
159 | if (_bitsInBuffer == 0)
160 | {
161 | _byteWriter.Write(value);
162 | }
163 | else
164 | {
165 | WriteBits(8, value);
166 | }
167 | }
168 |
169 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
170 | public void Write(ushort value)
171 | {
172 | if (_bitsInBuffer == 0)
173 | {
174 | _byteWriter.Write(value);
175 | }
176 | else
177 | {
178 | WriteBits(16, value);
179 | }
180 | }
181 |
182 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
183 | public void Write(short value)
184 | {
185 | Write((ushort)value);
186 | }
187 |
188 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
189 | public void Write(uint value)
190 | {
191 | if (_bitsInBuffer == 0)
192 | {
193 | _byteWriter.Write(value);
194 | }
195 | else
196 | {
197 | WriteBits(32, value);
198 | }
199 | }
200 |
201 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
202 | public void Write(int value)
203 | {
204 | Write((uint)value);
205 | }
206 |
207 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
208 | public void Flush()
209 | {
210 | while (_bitsInBuffer > 0)
211 | {
212 | _byteWriter.Write((byte)_buffer);
213 |
214 | _buffer >>= 8;
215 | _bitsInBuffer -= 8;
216 | }
217 |
218 | _buffer = 0;
219 | _bitsInBuffer = 0;
220 | }
221 | }
--------------------------------------------------------------------------------
/NetCode/ByteReader.cs:
--------------------------------------------------------------------------------
1 | using System.Buffers.Binary;
2 | using System.Runtime.CompilerServices;
3 |
4 | namespace NetCode;
5 |
6 | public sealed class ByteReader : IByteReader
7 | {
8 | private byte[] _data;
9 | private int _start;
10 | private int _end;
11 | private int _head;
12 |
13 | public ByteReader() : this(Array.Empty())
14 | {
15 | }
16 |
17 | public ByteReader(byte[] data)
18 | {
19 | _data = data;
20 | _end = _data.Length;
21 | _head = _start = 0;
22 | }
23 |
24 | public int Start => _start;
25 |
26 | public int End => _end;
27 |
28 | public int RemainingToRead => _end - _head;
29 |
30 | public int Head => _head;
31 |
32 | public void SetArray(byte[] data) => SetArray(data, 0, data.Length);
33 |
34 | public void SetArray(byte[] data, int start, int length)
35 | {
36 | if (start + length > data.Length)
37 | {
38 | ThrowHelper.ThrowArgumentOutOfRangeException();
39 | }
40 |
41 | _data = data;
42 | _end = length + start;
43 | _head = _start = start;
44 | }
45 |
46 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
47 | public void Reset()
48 | {
49 | _head = _start;
50 | }
51 |
52 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
53 | public byte ReadByte()
54 | {
55 | var size = 1;
56 | if (_head + size > _end)
57 | {
58 | ThrowHelper.ThrowIndexOutOfRangeException();
59 | }
60 |
61 | var value = _data[_head];
62 | _head += size;
63 | return value;
64 | }
65 |
66 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
67 | public short ReadShort()
68 | {
69 | var result = Read();
70 | if (!BitConverter.IsLittleEndian)
71 | {
72 | result = BinaryPrimitives.ReverseEndianness(result);
73 | }
74 | return result;
75 | }
76 |
77 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
78 | public ushort ReadUShort()
79 | {
80 | var result = Read();
81 | if (!BitConverter.IsLittleEndian)
82 | {
83 | result = BinaryPrimitives.ReverseEndianness(result);
84 | }
85 | return result;
86 | }
87 |
88 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
89 | public int ReadInt()
90 | {
91 | var result = Read();
92 | if (!BitConverter.IsLittleEndian)
93 | {
94 | result = BinaryPrimitives.ReverseEndianness(result);
95 | }
96 | return result;
97 | }
98 |
99 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
100 | public uint ReadUInt()
101 | {
102 | var result = Read();
103 | if (!BitConverter.IsLittleEndian)
104 | {
105 | result = BinaryPrimitives.ReverseEndianness(result);
106 | }
107 | return result;
108 | }
109 |
110 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
111 | public (uint Value, int ReadBytes) TryReadUInt()
112 | {
113 | var (value, readBits) = TryRead();
114 | if (!BitConverter.IsLittleEndian)
115 | {
116 | value = BinaryPrimitives.ReverseEndianness(value);
117 | }
118 | return (value, readBits);
119 | }
120 |
121 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
122 | public long ReadLong()
123 | {
124 | var result = Read();
125 | if (!BitConverter.IsLittleEndian)
126 | {
127 | result = BinaryPrimitives.ReverseEndianness(result);
128 | }
129 | return result;
130 | }
131 |
132 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
133 | public ulong ReadULong()
134 | {
135 | var result = Read();
136 | if (!BitConverter.IsLittleEndian)
137 | {
138 | result = BinaryPrimitives.ReverseEndianness(result);
139 | }
140 | return result;
141 | }
142 |
143 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
144 | private T Read()
145 | where T : unmanaged
146 | {
147 | var size = Unsafe.SizeOf();
148 | if (_head + size > _end)
149 | {
150 | ThrowHelper.ThrowIndexOutOfRangeException();
151 | }
152 |
153 | var value = Unsafe.ReadUnaligned(ref _data[_head]);
154 | _head += size;
155 | return value;
156 | }
157 |
158 | ///
159 | /// Can return 8, 16, 24 or 32 read bits for int parameter.
160 | ///
161 | ///
162 | ///
163 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
164 | private (T Value, int ReadBytes) TryRead()
165 | where T : unmanaged
166 | {
167 | var size = Unsafe.SizeOf();
168 | if (RemainingToRead < size)
169 | {
170 | size = RemainingToRead;
171 |
172 | if (size == 0)
173 | {
174 | return (default, 0);
175 | }
176 | }
177 |
178 | var value = Unsafe.ReadUnaligned(ref _data[_head]);
179 | _head += size;
180 | return (value, size);
181 | }
182 | }
--------------------------------------------------------------------------------
/NetCode/ByteWriter.cs:
--------------------------------------------------------------------------------
1 | using System.Buffers.Binary;
2 | using System.Runtime.CompilerServices;
3 |
4 | namespace NetCode;
5 |
6 | public sealed class ByteWriter : IByteWriter
7 | {
8 | private const int DefaultCapacity = 1500;
9 |
10 | private byte[] _data;
11 | private int _capacity;
12 | private int _count;
13 | private int _start;
14 |
15 | public ByteWriter(int capacity = DefaultCapacity):this(new byte[capacity])
16 | {
17 | }
18 |
19 | public ByteWriter(byte[] data)
20 | {
21 | _data = data;
22 | _capacity = _data.Length;
23 | _start = _count = 0;
24 | }
25 |
26 | public int Start => _start;
27 |
28 | public int Capacity => _capacity;
29 |
30 | public int Count => _count;
31 |
32 | public byte[] Array => _data;
33 |
34 | public void SetArray(byte[] data) => SetArray(data, 0);
35 |
36 | public void SetArray(byte[] data, int offset)
37 | {
38 | if (offset > data.Length)
39 | {
40 | ThrowHelper.ThrowArgumentOutOfRangeException();
41 | }
42 |
43 | _data = data;
44 | _capacity = _data.Length;
45 | _start = _count = offset;
46 | }
47 |
48 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
49 | public void Clear()
50 | {
51 | _count = _start;
52 | }
53 |
54 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
55 | public void Write(byte value)
56 | {
57 | var size = 1;
58 | if (size + Count > _capacity)
59 | {
60 | ThrowHelper.ThrowIndexOutOfRangeException();
61 | }
62 | _data[Count] = value;
63 | _count += size;
64 | }
65 |
66 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
67 | public void Write(short value)
68 | {
69 | if (!BitConverter.IsLittleEndian)
70 | {
71 | value = BinaryPrimitives.ReverseEndianness(value);
72 | }
73 |
74 | WriteInternal(value);
75 | }
76 |
77 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
78 | public void Write(ushort value)
79 | {
80 | if (!BitConverter.IsLittleEndian)
81 | {
82 | value = BinaryPrimitives.ReverseEndianness(value);
83 | }
84 |
85 | WriteInternal(value);
86 | }
87 |
88 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
89 | public void Write(int value)
90 | {
91 | if (!BitConverter.IsLittleEndian)
92 | {
93 | value = BinaryPrimitives.ReverseEndianness(value);
94 | }
95 |
96 | WriteInternal(value);
97 | }
98 |
99 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
100 | public void Write(uint value)
101 | {
102 | if (!BitConverter.IsLittleEndian)
103 | {
104 | value = BinaryPrimitives.ReverseEndianness(value);
105 | }
106 |
107 | WriteInternal(value);
108 | }
109 |
110 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
111 | public void Write(long value)
112 | {
113 | if (!BitConverter.IsLittleEndian)
114 | {
115 | value = BinaryPrimitives.ReverseEndianness(value);
116 | }
117 |
118 | WriteInternal(value);
119 | }
120 |
121 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
122 | public void Write(ulong value)
123 | {
124 | if (!BitConverter.IsLittleEndian)
125 | {
126 | value = BinaryPrimitives.ReverseEndianness(value);
127 | }
128 |
129 | WriteInternal(value);
130 | }
131 |
132 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
133 | private void WriteInternal(T value)
134 | where T : unmanaged
135 | {
136 | var size = Unsafe.SizeOf();
137 | if (size + _count > _capacity)
138 | {
139 | ThrowHelper.ThrowIndexOutOfRangeException();
140 | }
141 |
142 | Unsafe.WriteUnaligned(ref _data[_count], value);
143 | _count += size;
144 | }
145 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/BitReader.Byte.Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | using NetCode.Limits;
3 |
4 | namespace NetCode;
5 |
6 | public static class BitReaderByteExtensions
7 | {
8 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
9 | public static byte ReadByte(this BitReader reader, ByteLimit limit)
10 | {
11 | var value = (byte)reader.ReadBits(limit.BitCount);
12 | return (byte)(value + limit.Min);
13 | }
14 |
15 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
16 | public static byte ReadByte(this BitReader reader, byte baseline)
17 | {
18 | var isChanged = reader.ReadBool();
19 | if (isChanged)
20 | {
21 | return reader.ReadByte();
22 | }
23 |
24 | return baseline;
25 | }
26 |
27 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
28 | public static byte ReadByte(this BitReader reader, byte baseline, ByteLimit limit)
29 | {
30 | var isChanged = reader.ReadBool();
31 | if (isChanged)
32 | {
33 | return reader.ReadByte(limit);
34 | }
35 |
36 | return baseline;
37 | }
38 |
39 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
40 | public static byte ReadByte(this BitReader reader, byte baseline, ByteLimit limit, ByteLimit diffLimit)
41 | {
42 | var isChanged = reader.ReadBool();
43 | if (isChanged)
44 | {
45 | var isDiff = reader.ReadBool();
46 |
47 | if (isDiff)
48 | {
49 | var diff = reader.ReadByte(diffLimit);
50 | return (byte)(baseline + diff);
51 | }
52 |
53 | return reader.ReadByte(limit);
54 | }
55 |
56 | return baseline;
57 | }
58 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/BitReader.Double.Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | namespace NetCode;
4 |
5 | public static class BitReaderDoubleExtensions
6 | {
7 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
8 | public static double ReadDouble(this BitReader reader)
9 | {
10 | #if NETSTANDARD2_0
11 | return BitConverterNetstandard20.Int64BitsToDouble(reader.ReadLong());
12 | #else
13 | return BitConverter.Int64BitsToDouble(reader.ReadLong());
14 | #endif
15 | }
16 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/BitReader.Float.Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | using NetCode.Limits;
3 |
4 | namespace NetCode;
5 |
6 | public static class BitReaderFloatExtensions
7 | {
8 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
9 | public static float ReadFloat(this BitReader reader)
10 | {
11 | #if NETSTANDARD2_0
12 | return BitConverterNetstandard20.Int32BitsToSingle(reader.ReadInt());
13 | #else
14 | return BitConverter.Int32BitsToSingle(reader.ReadInt());
15 | #endif
16 | }
17 |
18 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
19 | public static float ReadFloat(this BitReader reader, float min, float max, float precision)
20 | {
21 | var result = reader.ReadFloat(new FloatLimit(min, max, precision));
22 | return result;
23 | }
24 |
25 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
26 | public static float ReadFloat(this BitReader reader, FloatLimit limit)
27 | {
28 | uint integerValue = reader.ReadBits(limit.BitCount);
29 | float normalizedValue = integerValue / (float)limit.MaxIntegerValue;
30 |
31 | return normalizedValue * limit.Delta + limit.Min;
32 | }
33 |
34 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
35 | public static float ReadFloat(this BitReader reader, float baseline)
36 | {
37 | var isChanged = reader.ReadBool();
38 | if (isChanged)
39 | {
40 | return reader.ReadFloat();
41 | }
42 |
43 | return baseline;
44 | }
45 |
46 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
47 | public static float ReadFloat(this BitReader reader, float baseline, FloatLimit limit)
48 | {
49 | var isChanged = reader.ReadBool();
50 | if (isChanged)
51 | {
52 | return reader.ReadFloat(limit);
53 | }
54 |
55 | return baseline;
56 | }
57 |
58 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
59 | public static float ReadFloat(this BitReader reader, float baseline, FloatLimit limit, FloatLimit diffLimit)
60 | {
61 | var isChanged = reader.ReadBool();
62 | if (isChanged)
63 | {
64 | var isDiff = reader.ReadBool();
65 |
66 | if (isDiff)
67 | {
68 | var diff = reader.ReadFloat(diffLimit);
69 | return baseline + diff;
70 | }
71 |
72 | return reader.ReadFloat(limit);
73 | }
74 |
75 | return baseline;
76 | }
77 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/BitReader.Int.Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | using NetCode.Limits;
3 |
4 | namespace NetCode;
5 |
6 | public static class BitReaderIntExtensions
7 | {
8 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
9 | public static int ReadInt(this BitReader reader, int min, int max) => reader.ReadInt(new IntLimit(min, max));
10 |
11 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
12 | public static int ReadInt(this BitReader reader, IntLimit limit)
13 | {
14 | var value = (int)reader.ReadBits(limit.BitCount);
15 | return value + limit.Min;
16 | }
17 |
18 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
19 | public static int ReadInt(this BitReader reader, int baseline)
20 | {
21 | var isChanged = reader.ReadBool();
22 | if (isChanged)
23 | {
24 | return reader.ReadInt();
25 | }
26 |
27 | return baseline;
28 | }
29 |
30 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
31 | public static int ReadInt(this BitReader reader, int baseline, IntLimit limit)
32 | {
33 | var isChanged = reader.ReadBool();
34 | if (isChanged)
35 | {
36 | return reader.ReadInt(limit);
37 | }
38 |
39 | return baseline;
40 | }
41 |
42 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
43 | public static int ReadInt(this BitReader reader, int baseline, IntLimit limit, IntLimit diffLimit)
44 | {
45 | var isChanged = reader.ReadBool();
46 | if (isChanged)
47 | {
48 | var isDiff = reader.ReadBool();
49 |
50 | if (isDiff)
51 | {
52 | var diff = reader.ReadInt(diffLimit);
53 | return baseline + diff;
54 | }
55 |
56 | return reader.ReadInt(limit);
57 | }
58 |
59 | return baseline;
60 | }
61 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/BitReader.Long.Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | namespace NetCode;
4 |
5 | public static class BitReaderLongExtensions
6 | {
7 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
8 | public static long ReadLong(this BitReader reader)
9 | {
10 | uint high = reader.ReadUInt();
11 | uint low = reader.ReadUInt();
12 |
13 | long value = high;
14 | value = value << 32;
15 | value = value | low;
16 |
17 | return value;
18 | }
19 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/BitReader.SevenBitEncoding.Extensions.cs:
--------------------------------------------------------------------------------
1 | namespace NetCode;
2 |
3 | public static class BitReaderSevenBitEncodingExtensions
4 | {
5 | public static int ReadCompressedInt(this BitReader reader)
6 | {
7 | uint value = reader.ReadCompressedUInt();
8 | int zagzig = (int)((value >> 1) ^ (-(int)(value & 1)));
9 |
10 | return zagzig;
11 | }
12 |
13 | public static uint ReadCompressedUInt(this BitReader reader)
14 | {
15 | uint buffer = 0b_0u;
16 | uint value = 0b_0u;
17 | int shift = 0;
18 |
19 | do {
20 | buffer = reader.ReadBits(8);
21 |
22 | value |= (buffer & 0b_111_1111u) << shift;
23 | shift += 7;
24 | }
25 | while ((buffer & 0b_1000_0000u) > 0);
26 |
27 | return value;
28 | }
29 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/BitReader.Short.Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | using NetCode.Limits;
3 |
4 | namespace NetCode;
5 |
6 | public static class BitReaderShortExtensions
7 | {
8 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
9 | public static short ReadShort(this BitReader reader, ShortLimit limit)
10 | {
11 | var value = (short)reader.ReadBits(limit.BitCount);
12 | return (short)(value + limit.Min);
13 | }
14 |
15 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
16 | public static short ReadShort(this BitReader reader, short baseline)
17 | {
18 | var isChanged = reader.ReadBool();
19 | if (isChanged)
20 | {
21 | return reader.ReadShort();
22 | }
23 |
24 | return baseline;
25 | }
26 |
27 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
28 | public static short ReadShort(this BitReader reader, short baseline, ShortLimit limit)
29 | {
30 | var isChanged = reader.ReadBool();
31 | if (isChanged)
32 | {
33 | return reader.ReadShort(limit);
34 | }
35 |
36 | return baseline;
37 | }
38 |
39 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
40 | public static short ReadShort(this BitReader reader, short baseline, ShortLimit limit, ShortLimit diffLimit)
41 | {
42 | var isChanged = reader.ReadBool();
43 | if (isChanged)
44 | {
45 | var isDiff = reader.ReadBool();
46 |
47 | if (isDiff)
48 | {
49 | var diff = reader.ReadShort(diffLimit);
50 | return (short)(baseline + diff);
51 | }
52 |
53 | return reader.ReadShort(limit);
54 | }
55 |
56 | return baseline;
57 | }
58 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/BitReader.String.Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Buffers;
2 |
3 | namespace NetCode;
4 |
5 | public static class BitReaderStringExtensions
6 | {
7 | public static string ReadString(this BitReader reader) => ReadUtf8String(reader);
8 |
9 | public static string ReadString(this BitReader reader, string baseline)
10 | {
11 | var isChanged = reader.ReadBool();
12 | if (isChanged)
13 | {
14 | return reader.ReadString();
15 | }
16 |
17 | return baseline;
18 | }
19 |
20 | public static string ReadUtf8String(this BitReader reader)
21 | {
22 | var length = reader.ReadCompressedInt();
23 |
24 | #if NETSTANDARD2_0
25 |
26 | var chars = ArrayPool.Shared.Rent(length);
27 | for (int i = 0; i < length; i++)
28 | {
29 | chars[i] = (char)reader.ReadByte();
30 | }
31 | var s = new string(chars, 0, length);
32 | ArrayPool.Shared.Return(chars);
33 |
34 | #else
35 |
36 | var s = string.Create(length, reader, (span, bitReader) =>
37 | {
38 | for (int i = 0; i < span.Length; i++)
39 | {
40 | span[i] = (char)bitReader.ReadByte();
41 | }
42 | });
43 |
44 | #endif
45 |
46 | return string.Intern(s);
47 | }
48 |
49 | public static string ReadUnicodeString(this BitReader reader)
50 | {
51 | var length = reader.ReadCompressedInt();
52 |
53 | #if NETSTANDARD2_0
54 |
55 | var chars = ArrayPool.Shared.Rent(length);
56 | for (int i = 0; i < length; i++)
57 | {
58 | chars[i] = (char)reader.ReadUShort();
59 | }
60 | var s = new string(chars, 0, length);
61 | ArrayPool.Shared.Return(chars);
62 |
63 | #else
64 | var s = string.Create(length, reader, (span, bitReader) =>
65 | {
66 | for (int i = 0; i < span.Length; i++)
67 | {
68 | span[i] = (char)bitReader.ReadUShort();
69 | }
70 | });
71 |
72 | #endif
73 |
74 | return string.Intern(s);
75 | }
76 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/BitReader.UInt.Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | using NetCode.Limits;
3 |
4 | namespace NetCode;
5 |
6 | public static class BitReaderUIntExtensions
7 | {
8 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
9 | public static uint ReadUInt(this BitReader reader, uint min, uint max) => reader.ReadUInt(new UIntLimit(min, max));
10 |
11 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
12 | public static uint ReadUInt(this BitReader reader, UIntLimit limit)
13 | {
14 | var value = reader.ReadBits(limit.BitCount);
15 | return value + limit.Min;
16 | }
17 |
18 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
19 | public static uint ReadUInt(this BitReader reader, uint baseline)
20 | {
21 | var isChanged = reader.ReadBool();
22 | if (isChanged)
23 | {
24 | return reader.ReadUInt();
25 | }
26 |
27 | return baseline;
28 | }
29 |
30 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
31 | public static uint ReadUInt(this BitReader reader, uint baseline, UIntLimit limit)
32 | {
33 | var isChanged = reader.ReadBool();
34 | if (isChanged)
35 | {
36 | return reader.ReadUInt(limit);
37 | }
38 |
39 | return baseline;
40 | }
41 |
42 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
43 | public static uint ReadUInt(this BitReader reader, uint baseline, UIntLimit limit, UIntLimit diffLimit)
44 | {
45 | var isChanged = reader.ReadBool();
46 | if (isChanged)
47 | {
48 | var isDiff = reader.ReadBool();
49 |
50 | if (isDiff)
51 | {
52 | var diff = reader.ReadUInt(diffLimit);
53 | return baseline + diff;
54 | }
55 |
56 | return reader.ReadUInt(limit);
57 | }
58 |
59 | return baseline;
60 | }
61 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/BitReader.ULong.Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | namespace NetCode;
4 |
5 | public static class BitReaderULongExtensions
6 | {
7 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
8 | public static ulong ReadULong(this BitReader reader)
9 | {
10 | uint high = reader.ReadUInt();
11 | uint low = reader.ReadUInt();
12 |
13 | ulong value = high;
14 | value = value << 32;
15 | value = value | low;
16 |
17 | return value;
18 | }
19 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/BitReader.UShort.Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | using NetCode.Limits;
3 |
4 | namespace NetCode;
5 |
6 | public static class BitReaderUShortExtensions
7 | {
8 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
9 | public static ushort ReadUShort(this BitReader reader, UShortLimit limit)
10 | {
11 | var value = (ushort)reader.ReadBits(limit.BitCount);
12 | return (ushort)(value + limit.Min);
13 | }
14 |
15 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
16 | public static ushort ReadUShort(this BitReader reader, ushort baseline)
17 | {
18 | var isChanged = reader.ReadBool();
19 | if (isChanged)
20 | {
21 | return reader.ReadUShort();
22 | }
23 |
24 | return baseline;
25 | }
26 |
27 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
28 | public static ushort ReadUShort(this BitReader reader, ushort baseline, UShortLimit limit)
29 | {
30 | var isChanged = reader.ReadBool();
31 | if (isChanged)
32 | {
33 | return reader.ReadUShort(limit);
34 | }
35 |
36 | return baseline;
37 | }
38 |
39 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
40 | public static ushort ReadUShort(this BitReader reader, ushort baseline, UShortLimit limit, UShortLimit diffLimit)
41 | {
42 | var isChanged = reader.ReadBool();
43 | if (isChanged)
44 | {
45 | var isDiff = reader.ReadBool();
46 |
47 | if (isDiff)
48 | {
49 | var diff = reader.ReadUShort(diffLimit);
50 | return (ushort)(baseline + diff);
51 | }
52 |
53 | return reader.ReadUShort(limit);
54 | }
55 |
56 | return baseline;
57 | }
58 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/BitReader.Vector3.Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Numerics;
2 | using System.Runtime.CompilerServices;
3 | using NetCode.Limits;
4 |
5 | namespace NetCode;
6 |
7 | public static class BitReaderVector3Extensions
8 | {
9 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
10 | public static Vector3 ReadVector3(this BitReader reader)
11 | {
12 | return new Vector3(reader.ReadFloat(), reader.ReadFloat(), reader.ReadFloat());
13 | }
14 |
15 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
16 | public static Vector3 ReadVector3(this BitReader reader, Vector3Limit limit)
17 | {
18 | return new Vector3(reader.ReadFloat(limit.X), reader.ReadFloat(limit.Y), reader.ReadFloat(limit.Z));
19 | }
20 |
21 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
22 | public static Vector3 ReadVector3(this BitReader reader, Vector3 baseline)
23 | {
24 | var isChanged = reader.ReadBool();
25 | if (isChanged)
26 | {
27 | return reader.ReadVector3();
28 | }
29 |
30 | return baseline;
31 | }
32 |
33 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
34 | public static Vector3 ReadVector3(this BitReader reader, Vector3 baseline, Vector3Limit limit)
35 | {
36 | var isChanged = reader.ReadBool();
37 | if (isChanged)
38 | {
39 | return reader.ReadVector3(limit);
40 | }
41 |
42 | return baseline;
43 | }
44 |
45 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
46 | public static Vector3 ReadVector3(this BitReader reader, Vector3 baseline, Vector3Limit limit, Vector3Limit diffLimit)
47 | {
48 | var isChanged = reader.ReadBool();
49 | if (isChanged)
50 | {
51 | var isDiff = reader.ReadBool();
52 |
53 | if (isDiff)
54 | {
55 | var diff = reader.ReadVector3(diffLimit);
56 | return baseline + diff;
57 | }
58 |
59 | return reader.ReadVector3(limit);
60 | }
61 |
62 | return baseline;
63 | }
64 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/BitWriter.Byte.Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | using NetCode.Limits;
3 |
4 | namespace NetCode;
5 |
6 | public static class BitWriterByteExtensions
7 | {
8 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
9 | public static void Write(this BitWriter writer, byte value, ByteLimit limit)
10 | {
11 | #if DEBUG
12 | if (value < limit.Min || value > limit.Max)
13 | {
14 | ThrowHelper.ThrowArgumentOutOfRangeException();
15 | }
16 | #endif
17 |
18 | writer.WriteBits(limit.BitCount, (uint)(value - limit.Min));
19 | }
20 |
21 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
22 | public static void WriteValueIfChanged(this BitWriter writer, byte baseline, byte updated)
23 | {
24 | if (baseline == updated)
25 | {
26 | writer.Write(false);
27 | }
28 | else
29 | {
30 | writer.Write(true);
31 | writer.Write(updated);
32 | }
33 | }
34 |
35 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
36 | public static void WriteValueIfChanged(this BitWriter writer, byte baseline, byte updated, ByteLimit limit)
37 | {
38 | if (baseline.Equals(updated))
39 | {
40 | writer.Write(false);
41 | }
42 | else
43 | {
44 | writer.Write(true);
45 | writer.Write(updated, limit);
46 | }
47 | }
48 |
49 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
50 | public static void WriteDiffIfChanged(this BitWriter writer, byte baseline, byte updated, ByteLimit limit, ByteLimit diffLimit)
51 | {
52 | if (baseline.Equals(updated))
53 | {
54 | writer.Write(false);
55 | }
56 | else
57 | {
58 | writer.Write(true);
59 |
60 | var diff = (byte)(updated - baseline);
61 |
62 | if (diffLimit.Min < diff && diff < diffLimit.Max)
63 | {
64 | // if diff inside of diff limit, then we will write diff
65 | writer.Write(true);
66 | writer.Write(diff, diffLimit);
67 | }
68 | else
69 | {
70 | // otherwise we will write updated value
71 | writer.Write(false);
72 | writer.Write(updated, limit);
73 | }
74 | }
75 | }
76 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/BitWriter.Double.Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | namespace NetCode;
4 |
5 | public static class BitWriterDoubleExtensions
6 | {
7 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
8 | public static void Write(this BitWriter writer, double value)
9 | {
10 | #if NETSTANDARD2_0
11 | writer.Write(BitConverterNetstandard20.DoubleToInt64Bits(value));
12 | #else
13 | writer.Write(BitConverter.DoubleToInt64Bits(value));
14 | #endif
15 | }
16 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/BitWriter.Float.Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | using NetCode.Limits;
3 |
4 | namespace NetCode;
5 |
6 | public static class BitWriterFloatExtensions
7 | {
8 | public const float DefaultFloatPrecision = 0.0000001f;
9 |
10 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
11 | public static void Write(this BitWriter writer, float value)
12 | {
13 | #if NETSTANDARD2_0
14 | writer.Write(BitConverterNetstandard20.SingleToInt32Bits(value));
15 | #else
16 | writer.Write(BitConverter.SingleToInt32Bits(value));
17 | #endif
18 | }
19 |
20 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
21 | public static void Write(this BitWriter writer, float value, float min, float max, float precision)
22 | {
23 | writer.Write(value, new FloatLimit(min, max, precision));
24 | }
25 |
26 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
27 | public static void Write(this BitWriter writer, float value, FloatLimit limit)
28 | {
29 | #if DEBUG
30 | if (value < limit.Min || value > limit.Max)
31 | {
32 | ThrowHelper.ThrowArgumentOutOfRangeException();
33 | }
34 | #endif
35 |
36 | float normalizedValue = Mathf.Clamp((value - limit.Min) / limit.Delta, 0, 1);
37 | uint integerValue = (uint)Math.Floor(normalizedValue * limit.MaxIntegerValue + 0.5f);
38 |
39 | writer.WriteBits(limit.BitCount, integerValue);
40 | }
41 |
42 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
43 | public static void WriteValueIfChanged(this BitWriter writer, float baseline, float updated)
44 | {
45 | var diff = updated - baseline;
46 | if (Math.Abs(diff) < DefaultFloatPrecision)
47 | {
48 | writer.Write(false);
49 | }
50 | else
51 | {
52 | writer.Write(true);
53 | writer.Write(updated);
54 | }
55 | }
56 |
57 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
58 | public static void WriteValueIfChanged(this BitWriter writer, float baseline, float updated, FloatLimit limit)
59 | {
60 | if (Math.Abs(updated - baseline) < limit.Precision)
61 | {
62 | writer.Write(false);
63 | }
64 | else
65 | {
66 | writer.Write(true);
67 | writer.Write(updated, limit);
68 | }
69 | }
70 |
71 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
72 | public static void WriteDiffIfChanged(this BitWriter writer, float baseline, float updated, FloatLimit limit, FloatLimit diffLimit)
73 | {
74 | var diff = updated - baseline;
75 | if (Math.Abs(diff) < limit.Precision)
76 | {
77 | writer.Write(false);
78 | }
79 | else
80 | {
81 | writer.Write(true);
82 |
83 | if (diffLimit.Min < diff && diff < diffLimit.Max)
84 | {
85 | // if diff inside of diff limit, then we will write diff
86 | writer.Write(true);
87 | writer.Write(diff, diffLimit);
88 | }
89 | else
90 | {
91 | // otherwise we will write updated value
92 | writer.Write(false);
93 | writer.Write(updated, limit);
94 | }
95 | }
96 | }
97 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/BitWriter.Int.Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Numerics;
2 | using System.Runtime.CompilerServices;
3 | using NetCode.Limits;
4 |
5 | namespace NetCode;
6 |
7 | public static class BitWriterIntExtensions
8 | {
9 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
10 | public static void Write(this BitWriter writer, int value, int min, int max)
11 | {
12 | writer.Write(value, new IntLimit(min, max));
13 | }
14 |
15 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
16 | public static void Write(this BitWriter writer, int value, IntLimit limit)
17 | {
18 | #if DEBUG
19 | if (value < limit.Min || value > limit.Max)
20 | {
21 | ThrowHelper.ThrowArgumentOutOfRangeException();
22 | }
23 | #endif
24 |
25 | writer.WriteBits(limit.BitCount, (uint)(value - limit.Min));
26 | }
27 |
28 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
29 | public static void WriteValueIfChanged(this BitWriter writer, int baseline, int updated)
30 | {
31 | if (baseline.Equals(updated))
32 | {
33 | writer.Write(false);
34 | }
35 | else
36 | {
37 | writer.Write(true);
38 | writer.Write(updated);
39 | }
40 | }
41 |
42 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
43 | public static void WriteValueIfChanged(this BitWriter writer, int baseline, int updated, IntLimit limit)
44 | {
45 | if (baseline.Equals(updated))
46 | {
47 | writer.Write(false);
48 | }
49 | else
50 | {
51 | writer.Write(true);
52 | writer.Write(updated, limit);
53 | }
54 | }
55 |
56 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
57 | public static void WriteDiffIfChanged(this BitWriter writer, int baseline, int updated, IntLimit limit, IntLimit diffLimit)
58 | {
59 | if (baseline.Equals(updated))
60 | {
61 | writer.Write(false);
62 | }
63 | else
64 | {
65 | writer.Write(true);
66 |
67 | var diff = updated - baseline;
68 |
69 | if (diffLimit.Min < diff && diff < diffLimit.Max)
70 | {
71 | // if diff inside of diff limit, then we will write diff
72 | writer.Write(true);
73 | writer.Write(diff, diffLimit);
74 | }
75 | else
76 | {
77 | // otherwise we will write updated value
78 | writer.Write(false);
79 | writer.Write(updated, limit);
80 | }
81 | }
82 | }
83 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/BitWriter.Long.Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | namespace NetCode;
4 |
5 | public static class BitWriterLongExtensions
6 | {
7 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
8 | public static void Write(this BitWriter writer, long value)
9 | {
10 | uint low = (uint)value;
11 | uint high = (uint)(value >> 32);
12 | writer.Write(high);
13 | writer.Write(low);
14 | }
15 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/BitWriter.SevenBitEncoding.Extensions.cs:
--------------------------------------------------------------------------------
1 | namespace NetCode;
2 |
3 | public static class BitWriterSevenBitEncodingExtensions
4 | {
5 | public static void WriteCompressed(this BitWriter writer, int value)
6 | {
7 | uint zigzag = (uint)((value << 1) ^ (value >> 31));
8 |
9 | WriteCompressed(writer, zigzag);
10 | }
11 |
12 | public static void WriteCompressed(this BitWriter writer, uint value)
13 | {
14 |
15 | do {
16 | uint buffer = value & 0b_111_1111u;
17 | value >>= 7;
18 |
19 | if (value > 0)
20 | buffer |= 0b_1000_0000u;
21 |
22 | writer.WriteBits(8, buffer);
23 | }
24 |
25 | while (value > 0);
26 | }
27 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/BitWriter.Short.Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | using NetCode.Limits;
3 |
4 | namespace NetCode;
5 |
6 | public static class BitWriterShortExtensions
7 | {
8 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
9 | public static void Write(this BitWriter writer, short value, ShortLimit limit)
10 | {
11 | #if DEBUG
12 | if (value < limit.Min || value > limit.Max)
13 | {
14 | ThrowHelper.ThrowArgumentOutOfRangeException();
15 | }
16 | #endif
17 |
18 | writer.WriteBits(limit.BitCount, (uint)(value - limit.Min));
19 | }
20 |
21 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
22 | public static void WriteValueIfChanged(this BitWriter writer, short baseline, short updated)
23 | {
24 | if (baseline == updated)
25 | {
26 | writer.Write(false);
27 | }
28 | else
29 | {
30 | writer.Write(true);
31 | writer.Write(updated);
32 | }
33 | }
34 |
35 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
36 | public static void WriteValueIfChanged(this BitWriter writer, short baseline, short updated, ShortLimit limit)
37 | {
38 | if (baseline.Equals(updated))
39 | {
40 | writer.Write(false);
41 | }
42 | else
43 | {
44 | writer.Write(true);
45 | writer.Write(updated, limit);
46 | }
47 | }
48 |
49 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
50 | public static void WriteDiffIfChanged(this BitWriter writer, short baseline, short updated, ShortLimit limit, ShortLimit diffLimit)
51 | {
52 | if (baseline.Equals(updated))
53 | {
54 | writer.Write(false);
55 | }
56 | else
57 | {
58 | writer.Write(true);
59 |
60 | var diff = (short)(updated - baseline);
61 |
62 | if (diffLimit.Min < diff && diff < diffLimit.Max)
63 | {
64 | // if diff inside of diff limit, then we will write diff
65 | writer.Write(true);
66 | writer.Write(diff, diffLimit);
67 | }
68 | else
69 | {
70 | // otherwise we will write updated value
71 | writer.Write(false);
72 | writer.Write(updated, limit);
73 | }
74 | }
75 | }
76 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/BitWriter.String.Extensions.cs:
--------------------------------------------------------------------------------
1 | namespace NetCode;
2 |
3 | public static class BitWriterStringExtensions
4 | {
5 | public static void Write(this BitWriter writer, string value) => writer.WriteUtf8String(value);
6 |
7 | public static void WriteValueIfChanged(this BitWriter writer, string baseline, string updated)
8 | {
9 | if (baseline == updated)
10 | {
11 | writer.Write(false);
12 | }
13 | else
14 | {
15 | writer.Write(true);
16 | writer.Write(updated);
17 | }
18 | }
19 |
20 | public static void WriteUtf8String(this BitWriter writer, string value)
21 | {
22 | writer.WriteCompressed(value.Length);
23 |
24 | for (var i = 0; i < value.Length; i++)
25 | {
26 | var byteValue = Convert.ToByte(value[i]);
27 | writer.Write(byteValue);
28 | }
29 | }
30 |
31 | public static void WriteUnicodeString(this BitWriter writer, string value)
32 | {
33 | writer.WriteCompressed(value.Length);
34 |
35 | for (var i = 0; i < value.Length; i++)
36 | {
37 | writer.Write(value[i]);
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/BitWriter.UInt.Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | using NetCode.Limits;
3 |
4 | namespace NetCode;
5 |
6 | public static class BitWriterUIntExtensions
7 | {
8 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
9 | public static void Write(this BitWriter writer, uint value, uint min, uint max)
10 | {
11 | writer.Write(value, new UIntLimit(min, max));
12 | }
13 |
14 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
15 | public static void Write(this BitWriter writer, uint value, UIntLimit limit)
16 | {
17 | #if DEBUG
18 | if (value < limit.Min || value > limit.Max)
19 | {
20 | ThrowHelper.ThrowArgumentOutOfRangeException();
21 | }
22 | #endif
23 | writer.WriteBits(limit.BitCount, value - limit.Min);
24 | }
25 |
26 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
27 | public static void WriteValueIfChanged(this BitWriter writer, uint baseline, uint updated)
28 | {
29 | if (baseline.Equals(updated))
30 | {
31 | writer.Write(false);
32 | }
33 | else
34 | {
35 | writer.Write(true);
36 | writer.Write(updated);
37 | }
38 | }
39 |
40 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
41 | public static void WriteValueIfChanged(this BitWriter writer, uint baseline, uint updated, UIntLimit limit)
42 | {
43 | if (baseline.Equals(updated))
44 | {
45 | writer.Write(false);
46 | }
47 | else
48 | {
49 | writer.Write(true);
50 | writer.Write(updated, limit);
51 | }
52 | }
53 |
54 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
55 | public static void WriteDiffIfChanged(this BitWriter writer, uint baseline, uint updated, UIntLimit limit, UIntLimit diffLimit)
56 | {
57 | if (baseline.Equals(updated))
58 | {
59 | writer.Write(false);
60 | }
61 | else
62 | {
63 | writer.Write(true);
64 |
65 | var diff = updated - baseline;
66 |
67 | if (diffLimit.Min < diff && diff < diffLimit.Max)
68 | {
69 | // if diff inside of diff limit, then we will write diff
70 | writer.Write(true);
71 | writer.Write(diff, diffLimit);
72 | }
73 | else
74 | {
75 | // otherwise we will write updated value
76 | writer.Write(false);
77 | writer.Write(updated, limit);
78 | }
79 | }
80 | }
81 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/BitWriter.ULong.Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | namespace NetCode;
4 |
5 | public static class BitWriterULongExtensions
6 | {
7 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
8 | public static void Write(this BitWriter writer, ulong value)
9 | {
10 | uint low = (uint)value;
11 | uint high = (uint)(value >> 32);
12 | writer.Write(high);
13 | writer.Write(low);
14 | }
15 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/BitWriter.UShort.Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | using NetCode.Limits;
3 |
4 | namespace NetCode;
5 |
6 | public static class BitWriterUShortExtensions
7 | {
8 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
9 | public static void Write(this BitWriter writer, ushort value, UShortLimit limit)
10 | {
11 | #if DEBUG
12 | if (value < limit.Min || value > limit.Max)
13 | {
14 | ThrowHelper.ThrowArgumentOutOfRangeException();
15 | }
16 | #endif
17 |
18 | writer.WriteBits(limit.BitCount, (uint)(value - limit.Min));
19 | }
20 |
21 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
22 | public static void WriteValueIfChanged(this BitWriter writer, ushort baseline, ushort updated)
23 | {
24 | if (baseline == updated)
25 | {
26 | writer.Write(false);
27 | }
28 | else
29 | {
30 | writer.Write(true);
31 | writer.Write(updated);
32 | }
33 | }
34 |
35 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
36 | public static void WriteValueIfChanged(this BitWriter writer, ushort baseline, ushort updated, UShortLimit limit)
37 | {
38 | if (baseline.Equals(updated))
39 | {
40 | writer.Write(false);
41 | }
42 | else
43 | {
44 | writer.Write(true);
45 | writer.Write(updated, limit);
46 | }
47 | }
48 |
49 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
50 | public static void WriteDiffIfChanged(this BitWriter writer, ushort baseline, ushort updated, UShortLimit limit, UShortLimit diffLimit)
51 | {
52 | if (baseline.Equals(updated))
53 | {
54 | writer.Write(false);
55 | }
56 | else
57 | {
58 | writer.Write(true);
59 |
60 | var diff = (ushort)(updated - baseline);
61 |
62 | if (diffLimit.Min < diff && diff < diffLimit.Max)
63 | {
64 | // if diff inside of diff limit, then we will write diff
65 | writer.Write(true);
66 | writer.Write(diff, diffLimit);
67 | }
68 | else
69 | {
70 | // otherwise we will write updated value
71 | writer.Write(false);
72 | writer.Write(updated, limit);
73 | }
74 | }
75 | }
76 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/BitWriter.Vector3.Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Numerics;
2 | using System.Runtime.CompilerServices;
3 | using NetCode.Limits;
4 |
5 | namespace NetCode;
6 |
7 | public static class BitWriterVector3Extensions
8 | {
9 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
10 | public static void Write(this BitWriter writer, Vector3 value)
11 | {
12 | writer.Write(value.X);
13 | writer.Write(value.Y);
14 | writer.Write(value.Z);
15 | }
16 |
17 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
18 | public static void Write(this BitWriter writer, Vector3 value, Vector3Limit limit)
19 | {
20 | #if DEBUG
21 | if (value.X < limit.X.Min
22 | || value.X > limit.X.Max
23 | || value.Y < limit.Y.Min
24 | || value.Y > limit.Y.Max
25 | || value.Z < limit.Z.Min
26 | || value.Z > limit.Z.Max)
27 | {
28 | ThrowHelper.ThrowArgumentOutOfRangeException();
29 | }
30 | #endif
31 |
32 | writer.Write(value.X, limit.X);
33 | writer.Write(value.Y, limit.Y);
34 | writer.Write(value.Z, limit.Z);
35 | }
36 |
37 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
38 | public static void WriteValueIfChanged(this BitWriter writer, Vector3 baseline, Vector3 updated)
39 | {
40 | var diff = updated - baseline;
41 | if (diff.Length() < BitWriterFloatExtensions.DefaultFloatPrecision)
42 | {
43 | writer.Write(false);
44 | }
45 | else
46 | {
47 | writer.Write(true);
48 | writer.Write(updated);
49 | }
50 | }
51 |
52 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
53 | public static void WriteValueIfChanged(this BitWriter writer, Vector3 baseline, Vector3 updated, Vector3Limit limit)
54 | {
55 | var diff = updated - baseline;
56 | if (diff.Length() < limit.Precision)
57 | {
58 | writer.Write(false);
59 | }
60 | else
61 | {
62 | writer.Write(true);
63 | writer.Write(updated, limit);
64 | }
65 | }
66 |
67 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
68 | public static void WriteDiffIfChanged(this BitWriter writer, Vector3 baseline, Vector3 updated, Vector3Limit limit, Vector3Limit diffLimit)
69 | {
70 | var diff = updated - baseline;
71 | if (diff.LengthSquared() < limit.PrecisionSquare)
72 | {
73 | writer.Write(false);
74 | }
75 | else
76 | {
77 | writer.Write(true);
78 |
79 | if ((diffLimit.X.Min < diff.X && diff.X < diffLimit.X.Max)
80 | && (diffLimit.Y.Min < diff.Y && diff.Y < diffLimit.Y.Max)
81 | && (diffLimit.Z.Min < diff.Z && diff.Z < diffLimit.Z.Max))
82 | {
83 | // if diff inside of diff limit, then we will write diff
84 | writer.Write(true);
85 | writer.Write(diff, diffLimit);
86 | }
87 | else
88 | {
89 | // otherwise we will write updated value
90 | writer.Write(false);
91 | writer.Write(updated, limit);
92 | }
93 | }
94 | }
95 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/ByteReader.Double.Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | namespace NetCode;
4 |
5 | public static class ByteReaderDoubleExtensions
6 | {
7 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
8 | public static double ReadDouble(this ByteReader reader)
9 | {
10 | #if NETSTANDARD2_0
11 | return BitConverterNetstandard20.Int64BitsToDouble(reader.ReadLong());
12 | #else
13 | return BitConverter.Int64BitsToDouble(reader.ReadLong());
14 | #endif
15 | }
16 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/ByteReader.Float.Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | namespace NetCode;
4 |
5 | public static class ByteReaderFloatExtensions
6 | {
7 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
8 | public static float ReadFloat(this ByteReader reader)
9 | {
10 | #if NETSTANDARD2_0
11 | return BitConverterNetstandard20.Int32BitsToSingle(reader.ReadInt());
12 | #else
13 | return BitConverter.Int32BitsToSingle(reader.ReadInt());
14 | #endif
15 | }
16 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/ByteReader.Vector3.Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Numerics;
2 | using System.Runtime.CompilerServices;
3 |
4 | namespace NetCode;
5 |
6 | public static class ByteReaderVector3Extensions
7 | {
8 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
9 | public static Vector3 ReadVector3(this ByteReader reader)
10 | {
11 | return new Vector3(reader.ReadFloat(), reader.ReadFloat(), reader.ReadFloat());
12 | }
13 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/ByteWriter.Double.Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | namespace NetCode;
4 |
5 | public static class ByteWriterDoubleExtensions
6 | {
7 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
8 | public static void Write(this ByteWriter writer, double value)
9 | {
10 | #if NETSTANDARD2_0
11 | writer.Write(BitConverterNetstandard20.DoubleToInt64Bits(value));
12 | #else
13 | writer.Write(BitConverter.DoubleToInt64Bits(value));
14 | #endif
15 | }
16 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/ByteWriter.Float.Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | namespace NetCode;
4 |
5 | public static class ByteWriterFloatExtensions
6 | {
7 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
8 | public static void Write(this ByteWriter writer, float value)
9 | {
10 | #if NETSTANDARD2_0
11 | writer.Write(BitConverterNetstandard20.SingleToInt32Bits(value));
12 | #else
13 | writer.Write(BitConverter.SingleToInt32Bits(value));
14 | #endif
15 | }
16 | }
--------------------------------------------------------------------------------
/NetCode/Extensions/ByteWriter.Vector3.Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Numerics;
2 | using System.Runtime.CompilerServices;
3 |
4 | namespace NetCode;
5 |
6 | public static class ByteWriterVector3Extensions
7 | {
8 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
9 | public static void Write(this ByteWriter writer, Vector3 value)
10 | {
11 | writer.Write(value.X);
12 | writer.Write(value.Y);
13 | writer.Write(value.Z);
14 | }
15 | }
--------------------------------------------------------------------------------
/NetCode/IBitReader.cs:
--------------------------------------------------------------------------------
1 | namespace NetCode;
2 |
3 | public interface IBitReader
4 | {
5 | int Start { get; }
6 |
7 | int End { get; }
8 |
9 | (int Bytes, int Bits) RemainingToRead { get; }
10 |
11 | (int Bytes, int Bits) Head { get; }
12 |
13 | void SetArray(byte[] data);
14 |
15 | void SetArray(byte[] data, int offset);
16 |
17 | void SetArray(byte[] data, int start, int length);
18 |
19 | void Reset();
20 |
21 | uint ReadBits(int bitCount);
22 |
23 | bool ReadBool();
24 |
25 | byte ReadByte();
26 |
27 | ushort ReadUShort();
28 |
29 | short ReadShort();
30 |
31 | uint ReadUInt();
32 |
33 | int ReadInt();
34 | }
--------------------------------------------------------------------------------
/NetCode/IBitWriter.cs:
--------------------------------------------------------------------------------
1 | namespace NetCode;
2 |
3 | public interface IBitWriter
4 | {
5 | int BitsCount { get; }
6 |
7 | int BytesCount { get; }
8 |
9 | int Capacity { get; }
10 |
11 | byte[] Array { get; }
12 |
13 | void SetArray(byte[] data);
14 |
15 | void SetArray(byte[] data, int offset);
16 |
17 | void Clear();
18 |
19 | void WriteBits(int bitCount, uint value);
20 |
21 | void Write(bool value);
22 |
23 | void Write(byte value);
24 |
25 | void Write(ushort value);
26 |
27 | void Write(short value);
28 |
29 | void Write(uint value);
30 |
31 | void Write(int value);
32 |
33 | void Flush();
34 | }
--------------------------------------------------------------------------------
/NetCode/IByteReader.cs:
--------------------------------------------------------------------------------
1 | namespace NetCode;
2 |
3 | public interface IByteReader
4 | {
5 | int Start { get; }
6 |
7 | int End { get; }
8 |
9 | int RemainingToRead { get; }
10 |
11 | int Head { get; }
12 |
13 | void SetArray(byte[] data);
14 |
15 | void SetArray(byte[] data, int start, int length);
16 |
17 | void Reset();
18 |
19 | byte ReadByte();
20 |
21 | short ReadShort();
22 |
23 | ushort ReadUShort();
24 |
25 | int ReadInt();
26 |
27 | uint ReadUInt();
28 |
29 | long ReadLong();
30 |
31 | ulong ReadULong();
32 |
33 | (uint Value, int ReadBytes) TryReadUInt();
34 | }
--------------------------------------------------------------------------------
/NetCode/IByteWriter.cs:
--------------------------------------------------------------------------------
1 | namespace NetCode;
2 |
3 | public interface IByteWriter
4 | {
5 | int Capacity { get; }
6 |
7 | int Count { get; }
8 |
9 | byte[] Array { get; }
10 |
11 | void SetArray(byte[] data);
12 |
13 | void SetArray(byte[] data, int offset);
14 |
15 | void Clear();
16 |
17 | void Write(byte value);
18 |
19 | void Write(short value);
20 |
21 | void Write(ushort value);
22 |
23 | void Write(int value);
24 |
25 | void Write(uint value);
26 |
27 | void Write(long value);
28 |
29 | void Write(ulong value);
30 | }
--------------------------------------------------------------------------------
/NetCode/Limits/ByteLimit.cs:
--------------------------------------------------------------------------------
1 | namespace NetCode.Limits;
2 |
3 | public struct ByteLimit
4 | {
5 | public readonly byte Min;
6 |
7 | public readonly byte Max;
8 |
9 | public readonly int BitCount;
10 |
11 | public ByteLimit(byte min, byte max)
12 | {
13 | if (min > max)
14 | {
15 | ThrowHelper.ThrowArgumentException();
16 | }
17 |
18 | var range = max - min;
19 | BitCount = Mathi.BitsRequired((uint)range);
20 | Min = min;
21 | Max = max;
22 | }
23 | }
--------------------------------------------------------------------------------
/NetCode/Limits/FloatLimit.cs:
--------------------------------------------------------------------------------
1 | namespace NetCode.Limits;
2 |
3 | public struct FloatLimit
4 | {
5 | public readonly float Min;
6 |
7 | public readonly float Max;
8 |
9 | public readonly float Precision;
10 |
11 | public readonly float Delta;
12 |
13 | public readonly uint MaxIntegerValue;
14 |
15 | public readonly int BitCount;
16 |
17 | public FloatLimit(float min, float max, float precision)
18 | {
19 | if (min >= max)
20 | {
21 | ThrowHelper.ThrowArgumentException();
22 | }
23 |
24 | Min = min;
25 | Max = max;
26 | Precision = precision;
27 | Delta = max - min;
28 | float values = Delta / precision;
29 | MaxIntegerValue = (uint)Math.Ceiling(values);
30 | BitCount = Mathi.BitsRequired(MaxIntegerValue);
31 | }
32 | }
--------------------------------------------------------------------------------
/NetCode/Limits/IntLimit.cs:
--------------------------------------------------------------------------------
1 | namespace NetCode.Limits;
2 |
3 | public struct IntLimit
4 | {
5 | public readonly int Min;
6 |
7 | public readonly int Max;
8 |
9 | public readonly int BitCount;
10 |
11 | public IntLimit(int min, int max)
12 | {
13 | if (min > max)
14 | {
15 | ThrowHelper.ThrowArgumentException();
16 | }
17 |
18 | var range = max - min;
19 | BitCount = Mathi.BitsRequired((uint)range);
20 | Min = min;
21 | Max = max;
22 | }
23 | }
--------------------------------------------------------------------------------
/NetCode/Limits/ShortLimit.cs:
--------------------------------------------------------------------------------
1 | namespace NetCode.Limits;
2 |
3 | public struct ShortLimit
4 | {
5 | public readonly short Min;
6 |
7 | public readonly short Max;
8 |
9 | public readonly int BitCount;
10 |
11 | public ShortLimit(short min, short max)
12 | {
13 | if (min > max)
14 | {
15 | ThrowHelper.ThrowArgumentException();
16 | }
17 |
18 | var range = max - min;
19 | BitCount = Mathi.BitsRequired((uint)range);
20 | Min = min;
21 | Max = max;
22 | }
23 | }
--------------------------------------------------------------------------------
/NetCode/Limits/UIntLimit.cs:
--------------------------------------------------------------------------------
1 | namespace NetCode.Limits;
2 |
3 | public struct UIntLimit
4 | {
5 | public readonly uint Min;
6 |
7 | public readonly uint Max;
8 |
9 | public readonly int BitCount;
10 |
11 | public UIntLimit(uint min, uint max)
12 | {
13 | if (min > max)
14 | {
15 | ThrowHelper.ThrowArgumentException();
16 | }
17 |
18 | var range = max - min;
19 | BitCount = Mathi.BitsRequired(range);
20 | Min = min;
21 | Max = max;
22 | }
23 | }
--------------------------------------------------------------------------------
/NetCode/Limits/UShortLimit.cs:
--------------------------------------------------------------------------------
1 | namespace NetCode.Limits;
2 |
3 | public struct UShortLimit
4 | {
5 | public readonly ushort Min;
6 |
7 | public readonly ushort Max;
8 |
9 | public readonly int BitCount;
10 |
11 | public UShortLimit(ushort min, ushort max)
12 | {
13 | if (min > max)
14 | {
15 | ThrowHelper.ThrowArgumentException();
16 | }
17 |
18 | var range = max - min;
19 | BitCount = Mathi.BitsRequired((uint)range);
20 | Min = min;
21 | Max = max;
22 | }
23 | }
--------------------------------------------------------------------------------
/NetCode/Limits/Vector3Limit.cs:
--------------------------------------------------------------------------------
1 | namespace NetCode.Limits;
2 |
3 | public struct Vector3Limit
4 | {
5 | public readonly float Precision;
6 |
7 | public readonly float PrecisionSquare;
8 |
9 | public readonly FloatLimit X;
10 |
11 | public readonly FloatLimit Y;
12 |
13 | public readonly FloatLimit Z;
14 |
15 | public Vector3Limit(FloatLimit x, FloatLimit y, FloatLimit z)
16 | {
17 | X = x;
18 | Y = y;
19 | Z = z;
20 |
21 | Precision = Math.Max(Math.Max(X.Precision, Y.Precision), Z.Precision);
22 | PrecisionSquare = Precision * Precision;
23 | }
24 | }
--------------------------------------------------------------------------------
/NetCode/Mathf.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | namespace NetCode;
4 |
5 | internal static class Mathf
6 | {
7 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
8 | public static float Clamp(float value, float min, float max)
9 | {
10 | if (min > max)
11 | {
12 | ThrowHelper.ThrowArgumentException();
13 | }
14 |
15 | if (value < min)
16 | {
17 | return min;
18 | }
19 | else if (value > max)
20 | {
21 | return max;
22 | }
23 |
24 | return value;
25 | }
26 | }
--------------------------------------------------------------------------------
/NetCode/Mathi.cs:
--------------------------------------------------------------------------------
1 | using System.Numerics;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | namespace NetCode;
6 |
7 | public static class Mathi
8 | {
9 | private static ReadOnlySpan Log2DeBruijn => new byte[32]
10 | {
11 | 00, 09, 01, 10, 13, 21, 02, 29,
12 | 11, 14, 16, 18, 22, 25, 03, 30,
13 | 08, 12, 20, 28, 15, 17, 24, 07,
14 | 19, 27, 23, 06, 26, 05, 04, 31
15 | };
16 |
17 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
18 | public static int Ceiling(int a, int b)
19 | {
20 | var (quotient, remainder) = DivRem(a, b);
21 | if (remainder == 0)
22 | {
23 | return quotient;
24 | }
25 |
26 | return quotient + 1;
27 | }
28 |
29 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
30 | public static (int Quotient, int Remainder) DivRem(int left, int right)
31 | {
32 | int quotient = left / right;
33 | return (quotient, left - (quotient * right));
34 | }
35 |
36 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
37 | public static int BitsRequired(uint range)
38 | {
39 | #if NETSTANDARD2_0 || NETSTANDARD2_1
40 | return range == 0 ? 1 : Log2(range) + 1;
41 | #else
42 | return range == 0 ? 1 : BitOperations.Log2(range) + 1;
43 | #endif
44 | }
45 |
46 | public static int Log2(uint value)
47 | {
48 | // The 0->0 contract is fulfilled by setting the LSB to 1.
49 | // Log(1) is 0, and setting the LSB for values > 1 does not change the log2 result.
50 | value |= 1;
51 |
52 | // No AggressiveInlining due to large method size
53 | // Has conventional contract 0->0 (Log(0) is undefined)
54 |
55 | // Fill trailing zeros with ones, eg 00010010 becomes 00011111
56 | value |= value >> 01;
57 | value |= value >> 02;
58 | value |= value >> 04;
59 | value |= value >> 08;
60 | value |= value >> 16;
61 |
62 | // uint.MaxValue >> 27 is always in range [0 - 31] so we use Unsafe.AddByteOffset to avoid bounds check
63 | return Unsafe.AddByteOffset(
64 | // Using deBruijn sequence, k=2, n=5 (2^5=32) : 0b_0000_0111_1100_0100_1010_1100_1101_1101u
65 | ref MemoryMarshal.GetReference(Log2DeBruijn),
66 | // uint|long -> IntPtr cast on 32-bit platforms does expensive overflow checks not needed here
67 | (IntPtr)(int)((value * 0x07C4ACDDu) >> 27));
68 | }
69 | }
--------------------------------------------------------------------------------
/NetCode/NetCode.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0;net8.0;netstandard2.1;netstandard2.0
5 | enable
6 | 12.0
7 | enable
8 | true
9 |
10 |
11 |
12 | NetCode
13 | 1.1.5
14 | Eugene Levchenkov
15 | NetCode
16 | Fast and light BitWriter and BitReader
17 | https://github.com/Levchenkov/NetCode
18 | https://github.com/Levchenkov/NetCode/blob/main/LICENSE
19 | bitreader bitwriter netcode serialization quantization
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/NetCode/NetCode.csproj.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | True
--------------------------------------------------------------------------------
/NetCode/ThrowHelper.cs:
--------------------------------------------------------------------------------
1 | namespace NetCode;
2 |
3 | public static class ThrowHelper
4 | {
5 | public static void ThrowIndexOutOfRangeException() => throw new IndexOutOfRangeException();
6 |
7 | public static void ThrowArgumentException() => throw new ArgumentException();
8 |
9 | public static void ThrowArgumentOutOfRangeException() => throw new ArgumentOutOfRangeException();
10 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NetCode
2 |
3 | ### What is NetCode?
4 |
5 | Light and Fast bit and byte serialization for .NET Standard 2.0, .NET Standard 2.1, .NET 6 and .NET 8 (Mono, .NET Core, .NET Framework, Unity)
6 |
7 | ### How do I get started?
8 |
9 | ##### Write and read bits:
10 |
11 | ```csharp
12 | var bitWriter = new BitWriter();
13 |
14 | bitWriter.WriteBits(3, 0b_101);
15 | bitWriter.Flush();
16 |
17 | byte[] data = bitWriter.Array;
18 |
19 | var bitReader = new BitReader(data);
20 | uint value = bitReader.ReadBits(3);
21 |
22 | Console.WriteLine(value); // output: 5
23 | Console.WriteLine(Convert.ToString(value, 2)); // output: 101
24 | ```
25 |
26 | ##### Quantization Example:
27 |
28 | ```csharp
29 | var bitWriter = new BitWriter();
30 | bitWriter.Write(value: 1f, min: 0f, max: 10f, precision: 0.1f);
31 | Console.WriteLine(bitWriter.BitsCount); // 7
32 |
33 | bitWriter.Flush();
34 | Console.WriteLine(bitWriter.BitsCount); // 8
35 |
36 | var data = bitWriter.Array;
37 | var bitReader = new BitReader(data);
38 | var value = bitReader.ReadFloat(min: 0f, max: 10f, precision: 0.1f);
39 |
40 | Console.WriteLine(Math.Abs(value - 1f) < 0.1f); // True
41 | Console.WriteLine(value); // 1
42 | ```
43 |
44 | ##### Use alloc-free binary serialization and deserialization:
45 | ```csharp
46 | var serializer = new TransformComponentSerializer();
47 | var deserializer = new TransformComponentDeserializer();
48 |
49 | var before = new TransformComponent { Position = new Vector3(10f, 5f, 10f), Pitch = 30f, Yaw = 60f };
50 | var after = new TransformComponent { Position = new Vector3(10.5f, 5.5f, 10.5f), Pitch = 30f, Yaw = 60f };
51 |
52 | var serializedComponent = serializer.Serialize(before, after);
53 | Console.WriteLine(serializedComponent.Length); // 3
54 |
55 | var updated = deserializer.Deserialize(before, serializedComponent.Array);
56 |
57 | serializedComponent.Dispose();
58 |
59 | Console.WriteLine(updated); // Position: <10.5, 5.5, 10.5>, Yaw: 60, Pitch: 30
60 |
61 | public record struct TransformComponent (Vector3 Position, float Yaw, float Pitch );
62 |
63 | public struct SerializedComponent
64 | {
65 | private readonly ArrayPool _arrayPool;
66 |
67 | public byte[] Array { get; }
68 |
69 | public int Length { get; }
70 |
71 | public SerializedComponent(ArrayPool arrayPool, byte[] array, int length)
72 | {
73 | _arrayPool = arrayPool;
74 | Array = array;
75 | Length = length;
76 | }
77 |
78 | public void Dispose()
79 | {
80 | _arrayPool.Return(Array);
81 | }
82 | }
83 |
84 | public static class Limits
85 | {
86 | public static readonly FloatLimit Rotation = new FloatLimit(0, 360, 0.1f);
87 |
88 | public static readonly Vector3Limit AbsolutePosition = new Vector3Limit(new FloatLimit(-100f, 100f, 0.1f), new FloatLimit(-10f, 10f, 0.1f), new FloatLimit(-100f, 100f, 0.1f));
89 |
90 | public static readonly Vector3Limit DiffPosition = new Vector3Limit(new FloatLimit(-1f, 1f, 0.1f), new FloatLimit(-1f, 1f, 0.1f), new FloatLimit(-1f, 1f, 0.1f));
91 | }
92 |
93 | public class TransformComponentSerializer
94 | {
95 | private const int MTU = 1500;
96 |
97 | private readonly BitWriter _bitWriter = new BitWriter();
98 | private readonly ArrayPool _arrayPool = ArrayPool.Shared;
99 |
100 | public SerializedComponent Serialize(TransformComponent baseline, TransformComponent updated)
101 | {
102 | var array = _arrayPool.Rent(MTU);
103 | _bitWriter.SetArray(array);
104 |
105 | _bitWriter.WriteDiffIfChanged(baseline.Position.X, updated.Position.X, Limits.AbsolutePosition.X, Limits.DiffPosition.X);
106 | _bitWriter.WriteDiffIfChanged(baseline.Position.Y, updated.Position.Y, Limits.AbsolutePosition.Y, Limits.DiffPosition.Y);
107 | _bitWriter.WriteDiffIfChanged(baseline.Position.Z, updated.Position.Z, Limits.AbsolutePosition.Z, Limits.DiffPosition.Z);
108 |
109 | _bitWriter.WriteValueIfChanged(baseline.Yaw, updated.Yaw, Limits.Rotation);
110 | _bitWriter.WriteValueIfChanged(baseline.Pitch, updated.Pitch, Limits.Rotation);
111 |
112 | _bitWriter.Flush();
113 |
114 | return new SerializedComponent(_arrayPool, _bitWriter.Array, _bitWriter.BytesCount);
115 | }
116 | }
117 |
118 | public class TransformComponentDeserializer
119 | {
120 | private readonly BitReader _bitReader = new BitReader();
121 |
122 | public TransformComponent Deserialize(TransformComponent before, byte[] array)
123 | {
124 | _bitReader.SetArray(array);
125 |
126 | TransformComponent result = default;
127 |
128 | result.Position = new Vector3(
129 | _bitReader.ReadFloat(before.Position.X, Limits.AbsolutePosition.X, Limits.DiffPosition.X),
130 | _bitReader.ReadFloat(before.Position.Y, Limits.AbsolutePosition.Y, Limits.DiffPosition.Y),
131 | _bitReader.ReadFloat(before.Position.Z, Limits.AbsolutePosition.Z, Limits.DiffPosition.Z));
132 |
133 | result.Yaw = _bitReader.ReadFloat(before.Yaw, Limits.Rotation);
134 | result.Pitch = _bitReader.ReadFloat(before.Pitch, Limits.Rotation);
135 |
136 | return result;
137 | }
138 | }
139 |
140 | ```
141 |
142 | ### Where can I get it?
143 |
144 | ```
145 | PM> Install-Package NetCode
146 | ```
147 |
148 | or
149 |
150 | ```
151 | dotnet add package NetCode
152 | ```
153 |
154 | ### Features
155 |
156 | - Fast
157 | - Performance focused
158 | - Uses [high-perf](https://docs.microsoft.com/en-us/dotnet/api/system.buffers.binary.binaryprimitives) memory accessors
159 | - No array copying
160 | - Zero memory allocations
161 | - No array creation
162 | - Reusable class instances
163 | - Has a lot of [benchmarks](https://github.com/Levchenkov/NetCode/tree/main/NetCode.Benchmarks)
164 | - Read \ Write bit values
165 | - Read \ Write with delta compression
166 | - Read \ Write quantized values
167 | - Safe to use
168 | - Covered by [unit](https://github.com/Levchenkov/NetCode/tree/main/NetCode.UnitTests) tests
169 | - Supports a lot of frameworks:
170 | - .NET Standard 2.0
171 | - .NET Standard 2.1
172 | - .NET 6
173 | - .NET 8
174 |
175 | ### Why NetCode?
176 |
177 | ##### 1. High Performance and Alloc-free for read and write operations:
178 |
179 | Read benchmarks you can find [here](https://github.com/Levchenkov/NetCode/blob/main/NetCode.Benchmarks/ByteReaderBenchmark.cs).
180 |
181 | ```
182 | BenchmarkDotNet=v0.13.1, OS=macOS Big Sur 11.5.2 (20G95) [Darwin 20.6.0]
183 | Intel Core i7-9750H CPU 2.60GHz, 1 CPU, 12 logical and 6 physical cores
184 | .NET SDK=6.0.100
185 | [Host] : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
186 | DefaultJob : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
187 |
188 | | Method | Mean | Error | StdDev | Ratio | RatioSD | Allocated |
189 | |----------------- |-----------:|--------:|--------:|------:|--------:|----------:|
190 | | BinaryPrimitives | 328.4 ns | 1.24 ns | 1.10 ns | 1.00 | 0.00 | - |
191 | | ByteReader | 329.4 ns | 1.71 ns | 1.60 ns | 1.00 | 0.01 | - |
192 | | BitReader | 457.8 ns | 1.09 ns | 0.91 ns | 1.39 | 0.01 | - |
193 | | BinaryReader | 1,205.8 ns | 6.75 ns | 6.31 ns | 3.67 | 0.02 | - |
194 | ```
195 |
196 | Write benchmarks you can find [here](https://github.com/Levchenkov/NetCode/blob/main/NetCode.Benchmarks/ByteWriterBenchmark.cs).
197 |
198 | ```
199 | BenchmarkDotNet=v0.13.1, OS=macOS Big Sur 11.5.2 (20G95) [Darwin 20.6.0]
200 | Intel Core i7-9750H CPU 2.60GHz, 1 CPU, 12 logical and 6 physical cores
201 | .NET SDK=6.0.100
202 | [Host] : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
203 | DefaultJob : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
204 |
205 |
206 | | Method | Mean | Error | StdDev | Ratio | RatioSD | Allocated |
207 | |----------------- |-----------:|---------:|---------:|------:|--------:|----------:|
208 | | BinaryPrimitives | 325.7 ns | 1.39 ns | 1.30 ns | 1.00 | 0.00 | - |
209 | | ByteWriter | 330.3 ns | 1.67 ns | 1.48 ns | 1.01 | 0.01 | - |
210 | | BitWriter | 338.0 ns | 1.69 ns | 1.58 ns | 1.04 | 0.00 | - |
211 | | BinaryWriter | 2,344.7 ns | 12.08 ns | 10.71 ns | 7.20 | 0.03 | - |
212 | ```
213 |
214 | ##### 2. Supports aligned data [writing](https://github.com/Levchenkov/NetCode/blob/main/NetCode.Benchmarks/BitWriter_WriteByte_Benchmark.cs) and [reading](https://github.com/Levchenkov/NetCode/blob/main/NetCode.Benchmarks/BitReader_ReadByte_Benchmark.cs).
215 |
216 | ##### 3. Faster than other bit serialization libraries (see [this](https://github.com/Levchenkov/NetCode/blob/main/NetCode.Benchmarks/BitReader_ReadBits_Benchmark.cs) and [this](https://github.com/Levchenkov/NetCode/blob/main/NetCode.Benchmarks/BitWriter_WriteBits_Benchmark.cs)).
--------------------------------------------------------------------------------