├── .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)). --------------------------------------------------------------------------------