├── .vscode
├── settings.json
├── extensions.json
└── launch.json
├── .vsconfig
├── CHANGELOG.md.meta
├── README.md.meta
├── license.md.meta
├── package.json.meta
├── Runtime.meta
├── Tests.meta
├── Tests
├── Editor.meta
└── Editor
│ ├── abledbody.quickbin.Editor.Tests.asmdef.meta
│ ├── Tests.cs.meta
│ ├── abledbody.quickbin.Editor.Tests.asmdef
│ └── Tests.cs
├── Runtime
├── abledbody.quickbin.asmdef.meta
├── Deserializer.cs.meta
├── Serializer.cs.meta
├── UnityTypes.cs.meta
├── ChainExtensions.cs.meta
├── abledbody.quickbin.asmdef
├── ChainExtensions.cs
├── UnityTypes.cs
├── Serializer.cs
└── Deserializer.cs
├── .editorconfig
├── CHANGELOG.md
├── license.md
├── package.json
├── README.md
└── Samples~
└── QuickBin Example Script
└── ExampleClass.cs
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "dotnet.defaultSolution": "QuickBin.sln"
3 | }
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "visualstudiotoolsforunity.vstuc"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/.vsconfig:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0",
3 | "components": [
4 | "Microsoft.VisualStudio.Workload.ManagedGame"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/CHANGELOG.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: c799255ef6dfcd6419ada87f5540c012
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 4bb78caff1f64cb4fafaa42bf597053d
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/license.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 9576f6b7542d7a74ca20b44410ab491f
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/package.json.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: bc7de9c3ec76eaf488ef3777e215bb6a
3 | PackageManifestImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Runtime.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: d4851e9eac3c85f4e98928d1f2274a82
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Tests.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 0f70f3d9b9e93a740835aa0ac99b38b3
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Tests/Editor.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: c2407cc13e41cfa4a95047d0c8c5ca85
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Runtime/abledbody.quickbin.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: f87a650e8a9647b4b95caf55cbc02346
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Attach to Unity",
6 | "type": "vstuc",
7 | "request": "attach",
8 | }
9 | ]
10 | }
--------------------------------------------------------------------------------
/Tests/Editor/abledbody.quickbin.Editor.Tests.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: dee87f276674e1840ab2c2920655ff7c
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Runtime/Deserializer.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 6c903de57e5fb2f4db48f6ab26dcb257
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Runtime/Serializer.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: f55425ee170ffe54db05c176811a5549
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Runtime/UnityTypes.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 81f98c208b6644f418c13a21ced17991
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Tests/Editor/Tests.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: b76e8b5c6062710478908a163a78f011
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Runtime/ChainExtensions.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 70f6d3df8bd90f543897c11c23bbe51f
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Runtime/abledbody.quickbin.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "abledbody.quickbin",
3 | "rootNamespace": "",
4 | "references": [],
5 | "includePlatforms": [],
6 | "excludePlatforms": [],
7 | "allowUnsafeCode": false,
8 | "overrideReferences": false,
9 | "precompiledReferences": [],
10 | "autoReferenced": true,
11 | "defineConstraints": [],
12 | "versionDefines": [],
13 | "noEngineReferences": false
14 | }
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*.cs]
4 | dotnet_diagnostic.IDE1006.severity = none
5 | dotnet_diagnostic.UNT0023.severity = none
6 | dotnet_diagnostic.CA1852.severity = suggestion
7 | indent_style = tab
8 | trim_trailing_whitespace = true
9 | insert_final_newline = false
10 | csharp_new_line_before_open_brace = false
11 | csharp_new_line_before_else = true
12 | csharp_new_line_before_catch = true
13 | csharp_new_line_before_finally = true
14 | csharp_indent_preprocessor_if = outdent
15 | csharp_indent_preprocessor_region = outdent
16 | csharp_indent_preprocessor_other = outdent
--------------------------------------------------------------------------------
/Tests/Editor/abledbody.quickbin.Editor.Tests.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "abledbody.quickbin.Editor.Tests",
3 | "rootNamespace": "",
4 | "references": [
5 | "UnityEngine.TestRunner",
6 | "UnityEditor.TestRunner",
7 | "abledbody.quickbin"
8 | ],
9 | "includePlatforms": [
10 | "Editor"
11 | ],
12 | "excludePlatforms": [],
13 | "allowUnsafeCode": false,
14 | "overrideReferences": true,
15 | "precompiledReferences": [
16 | "nunit.framework.dll"
17 | ],
18 | "autoReferenced": false,
19 | "defineConstraints": [
20 | "UNITY_INCLUDE_TESTS"
21 | ],
22 | "versionDefines": [],
23 | "noEngineReferences": false
24 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## v1.5.2
2 | - Restructured the project as a Unity package
3 | - Updated the license agreement
4 |
5 | ## v1.5.1
6 | - Added an option to force a new byte when writing/reading flags
7 |
8 | ## v1.5
9 | - Added flag serialization for bools
10 |
11 | ## v1.4
12 | - Slightly better documentation.
13 | - Then methods for Serializer and Deserializer.
14 | - Length property for Serializer.
15 |
16 | ## v1.3.1
17 | - Found and documented a better way to extend the Serializer/Deserialzier classes
18 | - Included readme and license in the package.
19 |
20 | ## v1.3
21 | - Added a ForEach method to both the Serializer and Deserializer
22 | - Added a Return method to the Deserializer
23 |
24 | ## v1.2
25 | - Added a Clear function to the Serializer
26 | - Added the char, DateTime, TimeSpan, and Version types by default.
--------------------------------------------------------------------------------
/license.md:
--------------------------------------------------------------------------------
1 | # License Agreement
2 | ## TL;DR
3 | Use how ever you like; Just don't claim that you made it, and don't change the license agreement.
4 |
5 | ## Freedom of Use
6 | You are hereby granted the freedom to use, modify, and integrate this code into your own projects in any manner you deem appropriate.
7 |
8 | ## Attribution
9 | Attribution to the original developer is not required. However, you are strictly prohibited from falsely claiming or implying exclusive authorship of the source code provided in this package.
10 |
11 | ## Licensing Restrictions
12 | This code can be integrated into any project, regardless of its license. However, any source code derived from this code must retain this license. This code must not be re-licensed under a different license when redistributed, either in its original form or as part of another project.
13 |
14 | ## Disclaimer of Warranty
15 | This code is provided "as is", without warranty of any kind, express or implied. No representation is made about the suitability of this code for any purpose. You acknowledge that any use of this code is at your sole risk and discretion. The original developer is not liable for any consequences arising from the use of this code.
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "com.abledbody.quickbin",
3 | "version": "2.0.0",
4 | "description": "QuickBin is intended to make binary serialization and deserialization nearly thoughtless.\n\nSerialization and deserialization are made to be mirror images of each other, so it's easy to see what you're doing, and make sure you serialize and deserialize your data the exact same way. The heavy focus on method chaining makes it easy to read, and quick to implement.",
5 | "displayName": "QuickBin",
6 | "author": {
7 | "name": "abledbody",
8 | "email": "abledbody.gamedev@gmail.com",
9 | "url": "https://github.com/abledbody"
10 | },
11 | "documentationUrl": "https://github.com/abledbody/QuickBin/blob/main/README.md",
12 | "licensesUrl": "https://github.com/abledbody/QuickBin/blob/main/license.md",
13 | "samples": [
14 | {
15 | "displayName": "Example script",
16 | "description": "A short, heavily commented script which demonstrates how QuickBin is commonly used.",
17 | "path": "Samples~/QuickBin Example Script"
18 | }
19 | ],
20 | "keywords": [
21 | "binary",
22 | "serialization",
23 | "deserialization",
24 | "serialize",
25 | "deserialize",
26 | "quick",
27 | "bin",
28 | "quickbin",
29 | "abledbody",
30 | "IO",
31 | "data",
32 | "save",
33 | "load",
34 | "read",
35 | "write",
36 | "file",
37 | "network",
38 | "networking",
39 | "multiplayer",
40 | "functional",
41 | "chaining",
42 | "lightweight"
43 | ]
44 | }
--------------------------------------------------------------------------------
/Runtime/ChainExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Runtime.CompilerServices;
4 |
5 | namespace QuickBin.ChainExtensions {
6 | public static class ChainExtensions {
7 | /// Executes an action.
8 | /// The action to execute.
9 | /// @this
10 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
11 | public static TChain Then(this TChain @this, Action action) {
12 | action();
13 | return @this;
14 | }
15 |
16 | /// Assigns a value to the out parameter.
17 | /// The value to assign.
18 | /// The variable to assign to.
19 | /// @this
20 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
21 | public static TChain Assign(this TChain @this, T value, out T variable) {
22 | variable = value;
23 | return @this;
24 | }
25 |
26 | /// Returns an unrelated object.
27 | /// value
28 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
29 | public static T Output(this object _, T value) => value;
30 |
31 | /// Executes an action if a condition is met.
32 | /// The condition to check.
33 | /// The action to execute.
34 | /// @this
35 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
36 | public static TChain When(this TChain @this, bool condition, Action action) {
37 | if (condition) action(@this);
38 | return @this;
39 | }
40 |
41 | /// Executes an action for each value in the specified IEnumerable.
42 | /// The IEnumerable of values to act on.
43 | /// The action to execute on each value.
44 | /// This serializer.
45 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
46 | public static TChain ForEach(this TChain @this, IEnumerable values, Action action) {
47 | foreach (var value in values)
48 | action(value);
49 | return @this;
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # QuickBin
2 | QuickBin is intended to make binary serialization and deserialization nearly thoughtless.
3 |
4 | Serialization and deserialization are made to be mirror images of each other, so it's easy to see what you're doing, and make sure you serialize and deserialize your data the exact same way. The heavy focus on method chaining makes it easy to read, and quick to implement.
5 |
6 | ## Unity Installation
7 | To install QuickBin, go to the package manager, press the plus button in the top left, select `Add package by git URL...` and enter `https://github.com/abledbody/QuickBin.git`
8 |
9 | ## The Serializer class
10 | The serializer is a wrapper for a `List`. Each time you call `Serializer.Write` it picks the appropriate overload method for the provided type, and adds the bytes to the list. Once you're ready to use the produced bytes, like to write them to a file, you can simply put the reference to the serializer into an argument or field of type `byte[]`, and it will implicitly convert itself.
11 | ```cs
12 | var helloWorld = "Hello, QuickBin!";
13 |
14 | var buffer = new Serializer()
15 | .Write(10)
16 | .Write(18.5f)
17 | .Write(helloWorld, Serializer.Len_u16);
18 |
19 | byte[] bytes = buffer;
20 | ```
21 |
22 | ## The Deserializer class
23 | The deserializer is primarily a wrapper around a `byte[] buffer`, with an `int ReadIndex`, an `int ForbiddenIndex`. The deserializer will never mutate the byte array, and can effectively only read between `ReadIndex` and `ForbiddenIndex`. Every time you call `Deserializer.Read`, `ReadIndex` will increment by the number of bytes read for the specified type. If you attempt to read data beyond the end of the buffer, or beyond `ForbiddenIndex`, it will stop producing meaningful data, and `Deserializer.Overflowed` will be set to true.
24 |
25 | Each overload for `Deserializer.Read` provides an `out` argument. Using initialization syntax or providing an existing typed field is how the deserializer selects the correct overload method for converting bytes into a type.
26 | ```cs
27 | new Deserializer(bytes)
28 | .Read(out int firstNumber)
29 | .Read(out float secondNumber)
30 | .Read(out helloWorld, Deserializer.Len_u16);
31 |
32 | Debug.Log($"firstNumber: {firstNumber}, secondNumber: {secondNumber}, helloWorld: {helloWorld}");
33 | ```
34 |
35 | ## Custom extensions
36 | Extending the serializer and deserializer is fairly easy. Just make a static class (I recommend it also be partial) and write a `Write` method for `Serializer`, and a `Read` method for `Deserializer`.
37 | ```cs
38 | public static partial class QuickBinExtensions {
39 | public static Serializer Write(this Serializer buffer, ExampleClass value) => buffer
40 | .Write(value.foo)
41 | .Write(value.bar);
42 |
43 | public static Deserializer Read(this Deserializer buffer, out ExampleClass produced) => buffer
44 | .Read(out int foo)
45 | .Read(out double bar)
46 | .Validate(() => new(foo, bar), out produced);
47 | }
48 | ```
49 |
--------------------------------------------------------------------------------
/Samples~/QuickBin Example Script/ExampleClass.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using QuickBin.ChainExtensions;
3 |
4 | namespace QuickBin.Example {
5 | public class ExampleClass {
6 | // All of these types are easily serialized in QuickBin.
7 | public string name;
8 | private short id;
9 | public Vector2 velocity;
10 |
11 |
12 | public ExampleClass(string name, short id, Vector2 velocity) {
13 | this.name = name;
14 | this.id = id;
15 | this.velocity = velocity;
16 | }
17 |
18 | // Notice that below this type, we have a partial class that extends Serializer and Deserializer.
19 | // We still define the methods here so that id, a private field, can be serialized.
20 | // It also confines the complexity of the extension to this type.
21 |
22 | // It's recommended you follow these patterns for Serialize and Deserialize
23 | // in your types to make using QuickBin's chaining easier.
24 | public Serializer Serialize(Serializer buffer) =>
25 | // You can chain off each Write or Read call, and you can also split up
26 | // the calls to insert some functionality in between.
27 | // Both serialization and deserialization are done in the same order.
28 |
29 | // When writing a string, you can specify a length writer which will write the length
30 | // of the string as a specific integer type just before the string itself.
31 | buffer.Write(name, Serializer.Len_u16)
32 | .Write(id)
33 | .Write(velocity);
34 |
35 | public static Deserializer Deserialize(Deserializer buffer, out ExampleClass produced) =>
36 | // Thanks to the "out" keyword, you can inline the declaration of variables,
37 | // and immediately use them in the next method call. It also allows you to specify
38 | // which type you want to extract with each call without the use of generics or verbose method names.
39 |
40 | // For demonstration purposes, we're skipping the use of Deserializer.Len_u16 here, and
41 | // reading the string length directly from the buffer. This is a perfectly valid way to do it,
42 | // although it's recommended that you make serializer/deserializer symmetric for readability.
43 | buffer.Read(out ushort nameLength)
44 | .Read(out string name, nameLength)
45 | .Read(out short id)
46 | .Read(out Vector2 velocity)
47 | // It's possible for the Deserializer to overflow the buffer. If this happens,
48 | // calling Validate will not execute the constructor, and will instead perform an
49 | // alternative action, or just output a default value if one is not provided.
50 | .Validate(
51 | () => new(name, id, velocity),
52 | out produced,
53 | () => new(null, -1, Vector2.zero)
54 | );
55 |
56 | // Note that this is not necessarily a smart way to do a Clone,
57 | // but it's a good example of how to use QuickBin at the top level.
58 | public ExampleClass Clone() {
59 | var serializer = new Serializer().Write(this);
60 |
61 | // Serializer can be implicitly cast to a byte array so you don't have to think about it.
62 | // A byte array is also what the constructor for Deserializer takes, so this becomes a dead simple operation.
63 | var deserializer = new Deserializer(serializer).Read(out ExampleClass produced);
64 |
65 | // At any point in the call stack you can check buffer.Overflowed to see
66 | // if there was enough data to produce a valid object.
67 | if (deserializer.Overflowed)
68 | throw new System.Exception("Oh no! Not enough data.");
69 |
70 | return produced;
71 | }
72 | }
73 |
74 | // Take note that this is a partial class. By doing this, you can put the extensions alongside
75 | // the type that they handle without having to come up with a unique name for each extension class.
76 | public static partial class QuickBinExtensions {
77 | public static Serializer Write(this Serializer buffer, ExampleClass value) =>
78 | value.Serialize(buffer);
79 |
80 | public static Deserializer Read(this Deserializer buffer, out ExampleClass produced) =>
81 | ExampleClass.Deserialize(buffer, out produced);
82 | }
83 | }
--------------------------------------------------------------------------------
/Runtime/UnityTypes.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 |
3 | namespace QuickBin {
4 | public sealed partial class Serializer {
5 | public Serializer Write(Vector2 value) =>
6 | Write(value.x)
7 | .Write(value.y);
8 |
9 | public Serializer Write(Vector3 value) =>
10 | Write(value.x)
11 | .Write(value.y)
12 | .Write(value.z);
13 |
14 | public Serializer Write(Vector4 value) =>
15 | Write(value.x)
16 | .Write(value.y)
17 | .Write(value.z)
18 | .Write(value.w);
19 |
20 | public Serializer Write(Vector2Int value) =>
21 | Write(value.x)
22 | .Write(value.y);
23 |
24 | public Serializer Write(Vector3Int value) =>
25 | Write(value.x)
26 | .Write(value.y)
27 | .Write(value.z);
28 |
29 | public Serializer Write(Quaternion value) =>
30 | Write(value.x)
31 | .Write(value.y)
32 | .Write(value.z)
33 | .Write(value.w);
34 |
35 | public Serializer Write(Color value) =>
36 | Write(value.r)
37 | .Write(value.g)
38 | .Write(value.b)
39 | .Write(value.a);
40 |
41 | public Serializer Write(Color32 value) =>
42 | Write(value.r)
43 | .Write(value.g)
44 | .Write(value.b)
45 | .Write(value.a);
46 |
47 | public Serializer Write(Rect value) =>
48 | Write(value.x)
49 | .Write(value.y)
50 | .Write(value.width)
51 | .Write(value.height);
52 |
53 | public Serializer Write(RectInt value) =>
54 | Write(value.x)
55 | .Write(value.y)
56 | .Write(value.width)
57 | .Write(value.height);
58 |
59 | public Serializer Write(Bounds value) =>
60 | Write(value.center)
61 | .Write(value.size);
62 |
63 | public Serializer Write(BoundsInt value) =>
64 | Write(value.center)
65 | .Write(value.size);
66 |
67 | public Serializer Write(Matrix4x4 value) =>
68 | Write(value.GetColumn(0))
69 | .Write(value.GetColumn(1))
70 | .Write(value.GetColumn(2))
71 | .Write(value.GetColumn(3));
72 | }
73 |
74 |
75 |
76 | public sealed partial class Deserializer {
77 | public Deserializer Read(out Vector2 produced) =>
78 | Read(out float x)
79 | .Read(out float y)
80 | .Validate(() => new(x, y), out produced);
81 |
82 | public Deserializer Read(out Vector3 produced) =>
83 | Read(out float x)
84 | .Read(out float y)
85 | .Read(out float z)
86 | .Validate(() => new(x,y,z), out produced);
87 |
88 | public Deserializer Read(out Vector4 produced) =>
89 | Read(out float x)
90 | .Read(out float y)
91 | .Read(out float z)
92 | .Read(out float w)
93 | .Validate(() => new(x, y, z, w), out produced);
94 |
95 | public Deserializer Read(out Vector2Int produced) =>
96 | Read(out int x)
97 | .Read(out int y)
98 | .Validate(() => new(x, y), out produced);
99 |
100 | public Deserializer Read(out Vector3Int produced) =>
101 | Read(out int x)
102 | .Read(out int y)
103 | .Read(out int z)
104 | .Validate(() => new(x, y), out produced);
105 |
106 | public Deserializer Read(out Quaternion produced) =>
107 | Read(out float x)
108 | .Read(out float y)
109 | .Read(out float z)
110 | .Read(out float w)
111 | .Validate(() => new(x, y, z, w), out produced);
112 |
113 | public Deserializer Read(out Color produced) =>
114 | Read(out float r)
115 | .Read(out float g)
116 | .Read(out float b)
117 | .Read(out float a)
118 | .Validate(() => new(r, g, b, a), out produced);
119 |
120 | public Deserializer Read(out Color32 produced) =>
121 | Read(out byte r)
122 | .Read(out byte g)
123 | .Read(out byte b)
124 | .Read(out byte a)
125 | .Validate(() => new(r, g, b, a), out produced);
126 |
127 | public Deserializer Read(out Matrix4x4 produced) =>
128 | Read(out Vector4 c1)
129 | .Read(out Vector4 c2)
130 | .Read(out Vector4 c3)
131 | .Read(out Vector4 c4)
132 | .Validate(() => new(c1, c2, c3, c4), out produced);
133 |
134 | public Deserializer Read(out Rect produced) =>
135 | Read(out float x)
136 | .Read(out float y)
137 | .Read(out float width)
138 | .Read(out float height)
139 | .Validate(() => new(x, y, width, height), out produced);
140 |
141 | public Deserializer Read(out RectInt produced) =>
142 | Read(out int x)
143 | .Read(out int y)
144 | .Read(out int width)
145 | .Read(out int height)
146 | .Validate(() => new(x, y, width, height), out produced);
147 |
148 | public Deserializer Read(out Bounds produced) =>
149 | Read(out Vector3 center)
150 | .Read(out Vector3 size)
151 | .Validate(() => new(center, size), out produced);
152 |
153 | public Deserializer Read(out BoundsInt produced) =>
154 | Read(out Vector3Int center)
155 | .Read(out Vector3Int size)
156 | .Validate(() => new(center, size), out produced);
157 | }
158 | }
--------------------------------------------------------------------------------
/Tests/Editor/Tests.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using QuickBin;
3 |
4 | using TextEncoding = System.Text.Encoding;
5 |
6 | public class Tests {
7 | [Test]
8 | public static void FlagBools() {
9 | var testString = "Hello, world!";
10 |
11 | var buffer = new Serializer()
12 | .WriteFlag(false)
13 | .WriteFlag(true)
14 | .WriteFlag(true)
15 | .WriteFlag(false)
16 | .WriteFlag(true)
17 | .WriteFlag(false)
18 | .WriteFlag(false)
19 | .WriteFlag(false)
20 | .WriteFlag(true)
21 | .WriteFlag(true)
22 | .WriteFlag(false, true)
23 | .WriteFlag(true)
24 | .Write(10)
25 | .WriteFlag(true)
26 | .Write(testString, Serializer.Len_i32)
27 | .WriteFlag(false)
28 | .WriteFlag(true);
29 |
30 | Assert.AreEqual(((byte[])buffer)[0], (byte)0b0001_0110);
31 | Assert.AreEqual(((byte[])buffer)[1], (byte)0b0000_0011);
32 | Assert.AreEqual(((byte[])buffer)[2], (byte)0b0000_0010);
33 |
34 | new Deserializer(buffer)
35 | .ReadFlag(out bool a)
36 | .ReadFlag(out bool b)
37 | .ReadFlag(out bool c)
38 | .ReadFlag(out bool d)
39 | .ReadFlag(out bool e)
40 | .ReadFlag(out bool f)
41 | .ReadFlag(out bool g)
42 | .ReadFlag(out bool h)
43 | .ReadFlag(out bool i)
44 | .ReadFlag(out bool j)
45 | .ReadFlag(out bool k, true)
46 | .ReadFlag(out bool l)
47 | .Read(out int m)
48 | .ReadFlag(out bool n)
49 | .Read(out int length)
50 | .Read(out string o, length)
51 | .ReadFlag(out bool p)
52 | .ReadFlag(out bool q);
53 |
54 | Assert.IsFalse(a);
55 | Assert.IsTrue(b);
56 | Assert.IsTrue(c);
57 | Assert.IsFalse(d);
58 | Assert.IsTrue(e);
59 | Assert.IsFalse(f);
60 | Assert.IsFalse(g);
61 | Assert.IsFalse(h);
62 | Assert.IsTrue(i);
63 | Assert.IsTrue(j);
64 | Assert.IsFalse(k);
65 | Assert.IsTrue(l);
66 | Assert.AreEqual(10, m);
67 | Assert.IsTrue(n);
68 | Assert.AreEqual(testString, o);
69 | Assert.IsFalse(p);
70 | Assert.IsTrue(q);
71 | }
72 |
73 | [Test]
74 | public static void StringEncoding() {
75 | var testString = "Hello, World!";
76 |
77 | var buffer = new Serializer()
78 | .Write(testString, Serializer.Len_i64)
79 | .Write(testString, Serializer.Len_i32)
80 | .Write(testString, Serializer.Len_i16)
81 | .Write(testString, Serializer.Len_i8)
82 | .Write(testString, Serializer.Len_u64)
83 | .Write(testString, Serializer.Len_u32)
84 | .Write(testString, Serializer.Len_u16)
85 | .Write(testString, Serializer.Len_u8)
86 | .Write(testString, TextEncoding.ASCII, Serializer.Len_i32)
87 | .Write(testString, TextEncoding.BigEndianUnicode, Serializer.Len_i32)
88 | .Write(testString, TextEncoding.Unicode, Serializer.Len_i32)
89 | .Write(testString, TextEncoding.UTF32, Serializer.Len_i32)
90 | .Write(testString, TextEncoding.UTF7, Serializer.Len_i32)
91 | .Write(testString, TextEncoding.UTF8, Serializer.Len_i32);
92 |
93 | new Deserializer(buffer)
94 | .Read(out string str_long, Deserializer.Len_i64)
95 | .Read(out string str_int, Deserializer.Len_i32)
96 | .Read(out string str_short, Deserializer.Len_i16)
97 | .Read(out string str_sbyte, Deserializer.Len_i8)
98 | .Read(out string str_ulong, Deserializer.Len_u64)
99 | .Read(out string str_uint, Deserializer.Len_u32)
100 | .Read(out string str_ushort, Deserializer.Len_u16)
101 | .Read(out string str_byte, Deserializer.Len_u8)
102 | .Read(out string ascii, TextEncoding.ASCII, Deserializer.Len_i32)
103 | .Read(out string bigEndianUnicode, TextEncoding.BigEndianUnicode, Deserializer.Len_i32)
104 | .Read(out string unicode, TextEncoding.Unicode, Deserializer.Len_i32)
105 | .Read(out string utf32, TextEncoding.UTF32, Deserializer.Len_i32)
106 | .Read(out string utf7, TextEncoding.UTF7, Deserializer.Len_i32)
107 | .Read(out string utf8, TextEncoding.UTF8, Deserializer.Len_i32);
108 |
109 | Assert.AreEqual(testString, str_long);
110 | Assert.AreEqual(testString, str_int);
111 | Assert.AreEqual(testString, str_short);
112 | Assert.AreEqual(testString, str_sbyte);
113 | Assert.AreEqual(testString, str_ulong);
114 | Assert.AreEqual(testString, str_uint);
115 | Assert.AreEqual(testString, str_ushort);
116 | Assert.AreEqual(testString, str_byte);
117 |
118 | Assert.AreEqual(testString, ascii);
119 | Assert.AreEqual(testString, bigEndianUnicode);
120 | Assert.AreEqual(testString, unicode);
121 | Assert.AreEqual(testString, utf32);
122 | Assert.AreEqual(testString, utf7);
123 | Assert.AreEqual(testString, utf8);
124 | }
125 |
126 | [Test]
127 | public static void Overflow() {
128 | var overflowed = false;
129 |
130 | var buffer = new Serializer()
131 | .Write(10)
132 | .Write("Foo", Serializer.Len_i32);
133 |
134 | var reader = new Deserializer(buffer)
135 | .Read(out int good_a)
136 | .Read(out string good_b, Deserializer.Len_i32)
137 | .Validate(
138 | () => (good_a, good_b),
139 | out var goodProduced,
140 | () => {overflowed = true; return default;}
141 | );
142 |
143 | Assert.AreEqual(10, good_a);
144 | Assert.AreEqual("Foo", good_b);
145 | Assert.AreEqual(goodProduced, (10, "Foo"));
146 | Assert.IsFalse(overflowed);
147 | Assert.IsFalse(reader.Overflowed);
148 |
149 | reader = new Deserializer(buffer)
150 | .Read(out int bad_a)
151 | .Read(out string bad_b, Deserializer.Len_i32)
152 | .Read(out int bad_c)
153 | .Read(out string bad_d, Deserializer.Len_i32)
154 | .Validate(
155 | () => (bad_a, bad_b, bad_c, bad_d),
156 | out var badProduced,
157 | () => {overflowed = true; return default;}
158 | );
159 |
160 | Assert.AreEqual(10, bad_a);
161 | Assert.AreEqual("Foo", bad_b);
162 | Assert.AreEqual(default(int), bad_c);
163 | Assert.AreEqual(default(string), bad_d);
164 | Assert.AreEqual(badProduced, default((int, string, int, string)));
165 | Assert.IsTrue(overflowed);
166 | Assert.IsTrue(reader.Overflowed);
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/Runtime/Serializer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using TextEncoding = System.Text.Encoding;
4 |
5 | namespace QuickBin {
6 | public sealed partial class Serializer {
7 | readonly List bytes;
8 | int boolPlace = 0;
9 |
10 | ///
11 | /// The number of bytes in the Serializer.
12 | ///
13 | public int Length => bytes.Count;
14 |
15 | ///
16 | /// Generates a Serializer, initializing an empty list with a capacity of 0.
17 | ///
18 | public Serializer() => bytes = new List();
19 | ///
20 | /// Generates a Serializer, initializing an empty list with the specified capacity.
21 | ///
22 | /// The capacity of the list. See documentation for System.Collections.Generic.List(int capacity) for details.
23 | public Serializer(int capacity) => bytes = new List(capacity);
24 |
25 | public static implicit operator byte[](Serializer serializer) => serializer.bytes.ToArray();
26 | public static implicit operator List(Serializer serializer) => serializer.bytes;
27 |
28 | ///
29 | /// Clears the internal List so that the Serializer can be reused.
30 | ///
31 | /// This Serializer.
32 | public Serializer Clear() {
33 | bytes.Clear();
34 | return this;
35 | }
36 |
37 | private Serializer WriteGeneric(T value, Func f) {
38 | bytes.Add(f(value));
39 | boolPlace = 0;
40 | return this;
41 | }
42 |
43 | private Serializer WriteGeneric(T value, Func f) {
44 | bytes.AddRange(f(value));
45 | boolPlace = 0;
46 | return this;
47 | }
48 |
49 | public Serializer Write(bool value) => WriteGeneric(value, x => x ? (byte)1 : (byte)0);
50 | public Serializer Write(byte value) => WriteGeneric(value, x => x);
51 | public Serializer Write(sbyte value) => WriteGeneric(value, x => (byte)x);
52 | public Serializer Write(char value) => WriteGeneric(value, BitConverter.GetBytes);
53 | public Serializer Write(short value) => WriteGeneric(value, BitConverter.GetBytes);
54 | public Serializer Write(ushort value) => WriteGeneric(value, BitConverter.GetBytes);
55 | public Serializer Write(int value) => WriteGeneric(value, BitConverter.GetBytes);
56 | public Serializer Write(uint value) => WriteGeneric(value, BitConverter.GetBytes);
57 | public Serializer Write(long value) => WriteGeneric(value, BitConverter.GetBytes);
58 | public Serializer Write(ulong value) => WriteGeneric(value, BitConverter.GetBytes);
59 | public Serializer Write(float value) => WriteGeneric(value, BitConverter.GetBytes);
60 | public Serializer Write(double value) => WriteGeneric(value, BitConverter.GetBytes);
61 |
62 | /// A method that writes the length of a byte array to the Serializer.
63 | /// The Serializer to write the length to.
64 | /// The byte array to write the length of.
65 | /// This Serializer.
66 | public delegate Serializer LengthWriter(Serializer buffer, byte[] value);
67 | public static Serializer Len_i64(Serializer buffer, byte[] value) => buffer.Write(value.LongLength);
68 | public static Serializer Len_u64(Serializer buffer, byte[] value) => buffer.Write((ulong)value.LongLength);
69 | public static Serializer Len_i32(Serializer buffer, byte[] value) => buffer.Write(value.Length);
70 | public static Serializer Len_u32(Serializer buffer, byte[] value) => buffer.Write((uint)value.LongLength);
71 | public static Serializer Len_i16(Serializer buffer, byte[] value) => buffer.Write((short)value.Length);
72 | public static Serializer Len_u16(Serializer buffer, byte[] value) => buffer.Write((ushort)value.Length);
73 | public static Serializer Len_i8(Serializer buffer, byte[] value) => buffer.Write((sbyte)value.Length);
74 | public static Serializer Len_u8(Serializer buffer, byte[] value) => buffer.Write((byte)value.Length);
75 |
76 | /// Writes a byte array to the Serializer.
77 | /// The byte array to write.
78 | public Serializer Write(byte[] value) => WriteGeneric(value, x => x);
79 |
80 | /// Writes a byte array to the Serializer.
81 | /// The byte array to write.
82 | /// The method to use to write the length of the byte array. (e.g. Len_i32)
83 | public Serializer Write(byte[] value, LengthWriter writeLen) => writeLen(this, value).Write(value);
84 |
85 |
86 | /// Writes a string to the Serializer.
87 | /// The string to write.
88 | /// The encoding to use when converting the string to bytes.
89 | public Serializer Write(string value, TextEncoding encoding) => Write(encoding.GetBytes(value));
90 |
91 | /// Writes a string to the Serializer.
92 | /// The string to write.
93 | /// The encoding to use when converting the string to bytes.
94 | /// The method to use to write the length of the string. (e.g. Len_i32)
95 | public Serializer Write(string value, TextEncoding encoding, LengthWriter writeLen) => Write(encoding.GetBytes(value), writeLen);
96 |
97 | /// Writes a string to the Serializer using UTF-8 encoding.
98 | /// The string to write.
99 | public Serializer Write(string value) => Write(value, TextEncoding.UTF8);
100 |
101 | /// Writes a string to the Serializer using UTF-8 encoding.
102 | /// The string to write.
103 | /// The method to use to write the length of the string. (e.g. Len_i32)
104 | public Serializer Write(string value, LengthWriter writeLen) => Write(value, TextEncoding.UTF8, writeLen);
105 |
106 |
107 | public Serializer Write(DateTime value) => Write(value.Ticks);
108 | public Serializer Write(TimeSpan value) => Write(value.Ticks);
109 |
110 | ///
111 | /// Writes booleans into the same byte if possible.
112 | ///
113 | /// The boolean to write.
114 | /// Whether to force writing a new byte, even if there's still space for flags in the current byte.
115 | /// This Serializer.
116 | public Serializer WriteFlag(bool value, bool forceNewByte = false) {
117 | if (forceNewByte)
118 | boolPlace = 0;
119 |
120 | if (boolPlace == 0)
121 | Write(value);
122 | else
123 | bytes[^1] |= (byte)(value ? 1 << boolPlace : 0);
124 |
125 | boolPlace++;
126 | boolPlace %= 8;
127 | return this;
128 | }
129 |
130 | public Serializer Write(Version value) =>
131 | Write(value.Major)
132 | .Write(value.Minor)
133 | .Write(value.Build)
134 | .Write(value.Revision);
135 | }
136 | }
--------------------------------------------------------------------------------
/Runtime/Deserializer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using TextEncoding = System.Text.Encoding;
4 | using QuickBin.ChainExtensions;
5 |
6 | namespace QuickBin {
7 |
8 | public sealed partial class Deserializer {
9 | readonly byte[] buffer;
10 | int boolPlace = 0;
11 | byte flagByte = 0;
12 |
13 | /// The next index in the buffer that will be read from.
14 | public int ReadIndex {get; private set;}
15 | /// The index of the first byte that is not readable by this Deserializer.
16 | public int ForbiddenIndex {get;}
17 |
18 | /// Whether or not the Deserializer has read all of the bytes that it is allowed to.
19 | public bool IsExhausted => ReadIndex >= ForbiddenIndex;
20 |
21 | /// The length of the buffer, including bytes that are not readable by this Deserializer.
22 | public int InternalLength => buffer.Length;
23 | /// The number of remaining bytes that can be read by this Deserializer.
24 | public int Remaining => ForbiddenIndex - ReadIndex;
25 | /// Whether the Deserializer has attempted to read more bytes than are in the buffer.
26 | public bool Overflowed {get; private set;}
27 | /// Indexes the buffer offset by the ReadIndex.
28 | /// The index of the byte to get.
29 | /// The byte the specified number of indices after the ReadIndex.
30 | public byte this[int index] => buffer[ReadIndex + index];
31 |
32 | /// Creates a Deserializer from a byte array.
33 | /// The byte array to deserialize from.
34 | /// The index to start reading from.
35 | /// The index of the first byte that is not readable by this Deserializer.
36 | public Deserializer(byte[] buffer, int readIndex = 0, int? forbiddenIndex = null) {
37 | this.buffer = buffer;
38 | ReadIndex = readIndex;
39 | ForbiddenIndex = forbiddenIndex ?? buffer.Length;
40 | }
41 |
42 | public static implicit operator byte[](Deserializer deserializer) => deserializer.buffer;
43 | public static implicit operator List(Deserializer deserializer) => new(deserializer.buffer);
44 |
45 | /// Clones the remaining bytes in the buffer.
46 | /// A new byte array containing the remaining bytes in the buffer.
47 | public byte[] CloneOutArray() {
48 | var result = new byte[Remaining];
49 | Array.Copy(buffer, ReadIndex, result, 0, Remaining);
50 | return result;
51 | }
52 |
53 | public Deserializer Validate(Func constructor, out T variable, Func onOverflow = null) =>
54 | this.Assign(Overflowed ? (onOverflow == null ? default : onOverflow()) : constructor(), out variable);
55 |
56 |
57 | private static byte[] Extract(byte[] buffer, int index, int length) => buffer[index..(index + length)];
58 |
59 | // These ReadGeneric methods are the core of the Deserializer.
60 | // They make it possible to make every primitive Read method a single line.
61 | private Deserializer ReadGeneric(int width, Func f, out T produced) {
62 | var nextIndex = ReadIndex + width;
63 | // It's okay if ReadIndex + width == buffer.Length, because index represents the next byte to read, not the last byte read.
64 | if (nextIndex > buffer.Length || nextIndex > ForbiddenIndex) {
65 | Overflowed = true;
66 | produced = default;
67 | return this;
68 | }
69 |
70 | produced = f(buffer, ReadIndex);
71 | ReadIndex = nextIndex;
72 | boolPlace = 0;
73 | return this;
74 | }
75 |
76 | private Deserializer ReadGeneric(int? byteLength, Func f, out T produced) =>
77 | this.Assign(byteLength ?? Remaining, out var width)
78 | .ReadGeneric(width, (b, i) => f(b, i, width), out produced);
79 |
80 | public Deserializer Read(out bool produced) => ReadGeneric(sizeof(bool), BitConverter.ToBoolean, out produced);
81 | public Deserializer Read(out byte produced) => ReadGeneric(sizeof(byte), (b,i) => b[i], out produced);
82 | public Deserializer Read(out sbyte produced) => ReadGeneric(sizeof(sbyte), (b,i) => (sbyte)b[i], out produced);
83 | public Deserializer Read(out char produced) => ReadGeneric(sizeof(char), BitConverter.ToChar, out produced);
84 | public Deserializer Read(out short produced) => ReadGeneric(sizeof(short), BitConverter.ToInt16, out produced);
85 | public Deserializer Read(out ushort produced) => ReadGeneric(sizeof(ushort), BitConverter.ToUInt16, out produced);
86 | public Deserializer Read(out int produced) => ReadGeneric(sizeof(int), BitConverter.ToInt32, out produced);
87 | public Deserializer Read(out uint produced) => ReadGeneric(sizeof(uint), BitConverter.ToUInt32, out produced);
88 | public Deserializer Read(out long produced) => ReadGeneric(sizeof(long), BitConverter.ToInt64, out produced);
89 | public Deserializer Read(out ulong produced) => ReadGeneric(sizeof(ulong), BitConverter.ToUInt64, out produced);
90 | public Deserializer Read(out float produced) => ReadGeneric(sizeof(float), BitConverter.ToSingle, out produced);
91 | public Deserializer Read(out double produced) => ReadGeneric(sizeof(double), BitConverter.ToDouble, out produced);
92 |
93 | /// A method that reads the length of a byte array from the Deserializer.
94 | /// The Deserializer to read the length from.
95 | /// The length of the byte array.
96 | /// Whether the Deserializer overflowed.
97 | public delegate bool LengthReader(Deserializer buffer, out int len);
98 | public static bool Len_i64(Deserializer buffer, out int len) => buffer.Read(out long _len).Assign((int)_len, out len).Output(buffer.Overflowed);
99 | public static bool Len_u64(Deserializer buffer, out int len) => buffer.Read(out ulong _len).Assign((int)_len, out len).Output(buffer.Overflowed);
100 | public static bool Len_i32(Deserializer buffer, out int len) => buffer.Read(out int _len).Assign( _len, out len).Output(buffer.Overflowed);
101 | public static bool Len_u32(Deserializer buffer, out int len) => buffer.Read(out uint _len).Assign((int)_len, out len).Output(buffer.Overflowed);
102 | public static bool Len_i16(Deserializer buffer, out int len) => buffer.Read(out short _len).Assign( _len, out len).Output(buffer.Overflowed);
103 | public static bool Len_u16(Deserializer buffer, out int len) => buffer.Read(out ushort _len).Assign( _len, out len).Output(buffer.Overflowed);
104 | public static bool Len_i8 (Deserializer buffer, out int len) => buffer.Read(out sbyte _len).Assign( _len, out len).Output(buffer.Overflowed);
105 | public static bool Len_u8 (Deserializer buffer, out int len) => buffer.Read(out byte _len).Assign( _len, out len).Output(buffer.Overflowed);
106 |
107 |
108 | /// Reads a string from the Deserializer.
109 | /// The string that was read.
110 | /// The encoding to use.
111 | /// The length of the string in bytes. Defaults to the remaining bytes in the buffer.
112 | public Deserializer Read(out string produced, TextEncoding encoding, int? length = null) =>
113 | ReadGeneric(length, encoding.GetString, out produced);
114 |
115 | /// Reads a UTF-8 string from the Deserializer.
116 | /// The string that was read.
117 | /// The length of the string in bytes.
118 | public Deserializer Read(out string produced, int? length = null) =>
119 | Read(out produced, TextEncoding.UTF8, length);
120 |
121 | /// Reads a byte array from the Deserializer.
122 | /// The byte array that was read.
123 | /// The length of the byte array in bytes. Defaults to the remaining bytes in the buffer.
124 | public Deserializer Read(out byte[] produced, int? length = null) =>
125 | ReadGeneric(length, Extract, out produced);
126 |
127 |
128 | /// Reads a string from the Deserializer.
129 | /// The string that was read.
130 | /// The encoding to use.
131 | /// The method to read out the length of the string. (e.g. Len_i32)
132 | public Deserializer Read(out string produced, TextEncoding encoding, LengthReader readLen) {
133 | if (readLen(this, out var len)) {
134 | produced = default;
135 | return this;
136 | }
137 | return Read(out produced, encoding, len);
138 | }
139 |
140 | /// Reads a UTF-8 string from the Deserializer.
141 | /// The string that was read.
142 | /// The method to read out the length of the string. (e.g. Len_i32)
143 | public Deserializer Read(out string produced, LengthReader readLen) =>
144 | Read(out produced, TextEncoding.UTF8, readLen);
145 |
146 | /// Reads a byte array from the Deserializer.
147 | /// The byte array that was read.
148 | /// The method to read out the length of the byte array. (e.g. Len_i32)
149 | public Deserializer Read(out byte[] produced, LengthReader readLen) {
150 | if (readLen(this, out var len)) {
151 | produced = default;
152 | return this;
153 | }
154 | return Read(out produced, len);
155 | }
156 |
157 |
158 | ///
159 | /// Reads booleans from the same byte if possible.
160 | ///
161 | /// The boolean that was read.
162 | /// Whether force reading from the next byte, even if there's still space for flags in the current byte.
163 | /// This Deserializer.
164 | public Deserializer ReadFlag(out bool produced, bool forceNewByte = false) {
165 | if (forceNewByte)
166 | boolPlace = 0;
167 |
168 | if (boolPlace == 0)
169 | Read(out flagByte);
170 |
171 | produced = (flagByte & (1 << boolPlace)) != 0;
172 |
173 | boolPlace++;
174 | boolPlace %= 8;
175 | return this;
176 | }
177 |
178 | public Deserializer Read(out DateTime produced) =>
179 | Read(out long ticks)
180 | .Validate(() => new(ticks), out produced);
181 |
182 | public Deserializer Read(out TimeSpan produced) =>
183 | Read(out long ticks)
184 | .Validate(() => new(ticks), out produced);
185 |
186 | public Deserializer Read(out Version produced) =>
187 | Read(out int major)
188 | .Read(out int minor)
189 | .Read(out int build)
190 | .Read(out int revision)
191 | .Validate(() => new(major, minor, build, revision), out produced);
192 | }
193 | }
--------------------------------------------------------------------------------