Skip to content

Commit 46a72c9

Browse files
committed
Add new methods for the people not using streams
1 parent 9b04ae5 commit 46a72c9

File tree

7 files changed

+193
-23
lines changed

7 files changed

+193
-23
lines changed

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@ For example, classes and structs in C# cannot have ignored members when marshall
1616
The `EndianBinaryPrimitives` static class which resembles `System.Buffers.Binary.BinaryPrimitives` is an API that converts to/from data types using `Span<T>`/`ReadOnlySpan<T>` with specific endianness, rather than streams.
1717

1818
----
19-
## Changelog For v2.0.0.0
20-
Be sure to check the comment at https://github.com/Kermalis/EndianBinaryIO/pull/28!
19+
## Changelog For v2.0.1
20+
Check the comment on [the release page](https://github.com/Kermalis/EndianBinaryIO/releases/tag/v2.0.1)!
21+
22+
## Changelog For v2.0.0
23+
Check the comment on [the release page](https://github.com/Kermalis/EndianBinaryIO/releases/tag/v2.0.0)!
2124

2225
----
2326
## 🚀 Usage:

Source/EndianBinaryIO.csproj

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<Title>EndianBinaryIO</Title>
1414
<PackageId>EndianBinaryIO</PackageId>
1515
<AssemblyName>EndianBinaryIO</AssemblyName>
16-
<Version>2.0.0</Version>
16+
<Version>2.0.1</Version>
1717
<RepositoryUrl>https://github.com/Kermalis/EndianBinaryIO</RepositoryUrl>
1818
<RepositoryType>git</RepositoryType>
1919
<Description>This .NET library provides a simple API to read/write bytes from/to streams and spans using user-specified endianness.
@@ -28,15 +28,13 @@ Project URL and Samples ― https://github.com/Kermalis/EndianBinaryIO</Descript
2828
<PackageTags>Serialization;Reflection;Endianness;LittleEndian;BigEndian;EndianBinaryIO</PackageTags>
2929
<PackageReadmeFile>README.md</PackageReadmeFile>
3030
<PackageLicenseFile>LICENSE.md</PackageLicenseFile>
31-
<PackageReleaseNotes>* Rewritten with Span&lt;T&gt; and performance in mind. No allocations unless absolutely necessary
32-
* The compiler will now inline certain methods. For example, ReadEnum&lt;TEnum&gt;() will only include code that will be executed for the given enum size. So passing a TEnum that is the size of a byte will condense down to just a ReadByte() call with no size/type checks
33-
* Implemented reading and writing for Half, DateOnly, TimeOnly, Vector2, Vector3, Vector4, Quaternion, and Matrix4x4
34-
* Removed bloated overloads (with array offset/count, alternate Encoding/BooleanSize, null termination, etc.). The reader/writer now respects its state (such as whether to use ASCII, and which BooleanSize to use) which you can change at any time
35-
* decimal int order now matches with .net APIs
36-
* Removed EndianBitConverter in favor of EndianBinaryPrimitives, which has similar API while using modern programming like Span&lt;T&gt;
37-
* API uses nullable notations
38-
* You can now ReadObject() and WriteObject() with primitives and other supported types like DateTime, Vector3, etc.
39-
* Removed Encoding usage. The whole thing was very complicated before, and it barely functioned. Now you have ASCII and .net (UTF16-LE) support by default, and can add your own requirements either by extension methods or inheriting the reader/writer</PackageReleaseNotes>
31+
<PackageReleaseNotes>Version 2.0.1 Changelog:
32+
* Added TrimNullTerminators(ref char[] chars) and TrimNullTerminators(ref Span&lt;char&gt; chars) to EndianBinaryPrimitives which will remove all '\0' from the end
33+
* Added ReadSBytes(ReadOnlySpan&lt;byte&gt; src, Span&lt;sbyte&gt; dest) and WriteSBytes(Span&lt;byte&gt; dest, ReadOnlySpan&lt;sbyte&gt; src) to EndianBinaryPrimitives
34+
* Added heavily optimized enum methods to EndianBinaryPrimitives that use the same optimization techniques as the ones in EndianBinaryReader and EndianBinaryWriter
35+
* Added PeekBytes(Span&lt;byte&gt; dest) to EndianBinaryReader
36+
37+
No breaking changes from v2.0.0</PackageReleaseNotes>
4038
</PropertyGroup>
4139

4240
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">

Source/EndianBinaryPrimitives.cs

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
22
using System.Buffers.Binary;
33
using System.Numerics;
4+
using System.Runtime.CompilerServices;
5+
using System.Runtime.InteropServices;
46

57
namespace Kermalis.EndianBinaryIO
68
{
@@ -19,8 +21,33 @@ public static int GetBytesForBooleanSize(BooleanSize boolSize)
1921
}
2022
}
2123

24+
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
25+
public static void TrimNullTerminators(ref char[] chars)
26+
{
27+
int i = Array.IndexOf(chars, '\0');
28+
if (i != -1)
29+
{
30+
Array.Resize(ref chars, i);
31+
}
32+
}
33+
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
34+
public static void TrimNullTerminators(ref Span<char> chars)
35+
{
36+
int i = chars.IndexOf('\0');
37+
if (i != -1)
38+
{
39+
chars = chars.Slice(0, i);
40+
}
41+
}
42+
2243
#region Read
2344

45+
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
46+
public static void ReadSBytes(ReadOnlySpan<byte> src, Span<sbyte> dest)
47+
{
48+
src.CopyTo(MemoryMarshal.Cast<sbyte, byte>(dest));
49+
}
50+
2451
public static short ReadInt16(ReadOnlySpan<byte> src, Endianness endianness)
2552
{
2653
return endianness == Endianness.LittleEndian
@@ -196,6 +223,68 @@ public static void ReadBooleans(ReadOnlySpan<byte> src, Span<bool> dest, Endiann
196223
}
197224
}
198225

226+
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
227+
public static TEnum ReadEnum<TEnum>(ReadOnlySpan<byte> src, Endianness endianness) where TEnum : unmanaged, Enum
228+
{
229+
int size = Unsafe.SizeOf<TEnum>();
230+
if (size == 1)
231+
{
232+
byte b = src[0];
233+
return Unsafe.As<byte, TEnum>(ref b);
234+
}
235+
if (size == 2)
236+
{
237+
ushort s = ReadUInt16(src, endianness);
238+
return Unsafe.As<ushort, TEnum>(ref s);
239+
}
240+
if (size == 4)
241+
{
242+
uint i = ReadUInt32(src, endianness);
243+
return Unsafe.As<uint, TEnum>(ref i);
244+
}
245+
ulong l = ReadUInt64(src, endianness);
246+
return Unsafe.As<ulong, TEnum>(ref l);
247+
}
248+
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
249+
public static void ReadEnums<TEnum>(ReadOnlySpan<byte> src, Span<TEnum> dest, Endianness endianness) where TEnum : unmanaged, Enum
250+
{
251+
int size = Unsafe.SizeOf<TEnum>();
252+
if (size == 1)
253+
{
254+
src.CopyTo(MemoryMarshal.Cast<TEnum, byte>(dest));
255+
}
256+
else if (size == 2)
257+
{
258+
ReadUInt16s(src, MemoryMarshal.Cast<TEnum, ushort>(dest), endianness);
259+
}
260+
else if (size == 4)
261+
{
262+
ReadUInt32s(src, MemoryMarshal.Cast<TEnum, uint>(dest), endianness);
263+
}
264+
else
265+
{
266+
ReadUInt64s(src, MemoryMarshal.Cast<TEnum, ulong>(dest), endianness);
267+
}
268+
}
269+
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
270+
public static object ReadEnum(ReadOnlySpan<byte> src, Endianness endianness, Type enumType)
271+
{
272+
// Type.IsEnum is also false for base Enum type, so don't worry about it
273+
Type underlyingType = Enum.GetUnderlyingType(enumType);
274+
switch (Type.GetTypeCode(underlyingType))
275+
{
276+
case TypeCode.SByte: return Enum.ToObject(enumType, (sbyte)src[0]);
277+
case TypeCode.Byte: return Enum.ToObject(enumType, src[0]);
278+
case TypeCode.Int16: return Enum.ToObject(enumType, ReadInt16(src, endianness));
279+
case TypeCode.UInt16: return Enum.ToObject(enumType, ReadUInt16(src, endianness));
280+
case TypeCode.Int32: return Enum.ToObject(enumType, ReadInt32(src, endianness));
281+
case TypeCode.UInt32: return Enum.ToObject(enumType, ReadUInt32(src, endianness));
282+
case TypeCode.Int64: return Enum.ToObject(enumType, ReadInt64(src, endianness));
283+
case TypeCode.UInt64: return Enum.ToObject(enumType, ReadUInt64(src, endianness));
284+
}
285+
throw new ArgumentOutOfRangeException(nameof(enumType), enumType, null);
286+
}
287+
199288
public static DateTime ReadDateTime(ReadOnlySpan<byte> src, Endianness endianness)
200289
{
201290
return DateTime.FromBinary(ReadInt64(src, endianness));
@@ -330,6 +419,12 @@ public static void ReadMatrix4x4s(ReadOnlySpan<byte> src, Span<Matrix4x4> dest,
330419

331420
#region Write
332421

422+
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
423+
public static void WriteSBytes(Span<byte> dest, ReadOnlySpan<sbyte> src)
424+
{
425+
MemoryMarshal.Cast<sbyte, byte>(src).CopyTo(dest);
426+
}
427+
333428
public static void WriteInt16(Span<byte> dest, short value, Endianness endianness)
334429
{
335430
if (endianness == Endianness.LittleEndian)
@@ -553,6 +648,69 @@ public static void WriteBooleans(Span<byte> dest, ReadOnlySpan<bool> src, Endian
553648
}
554649
}
555650

651+
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
652+
public static void WriteEnum<TEnum>(Span<byte> dest, TEnum value, Endianness endianness) where TEnum : unmanaged, Enum
653+
{
654+
int size = Unsafe.SizeOf<TEnum>();
655+
if (size == 1)
656+
{
657+
dest[0] = Unsafe.As<TEnum, byte>(ref value);
658+
}
659+
else if (size == 2)
660+
{
661+
WriteUInt16(dest, Unsafe.As<TEnum, ushort>(ref value), endianness);
662+
}
663+
else if (size == 4)
664+
{
665+
WriteUInt32(dest, Unsafe.As<TEnum, uint>(ref value), endianness);
666+
}
667+
else
668+
{
669+
WriteUInt64(dest, Unsafe.As<TEnum, ulong>(ref value), endianness);
670+
}
671+
}
672+
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
673+
public static void WriteEnums<TEnum>(Span<byte> dest, ReadOnlySpan<TEnum> src, Endianness endianness) where TEnum : unmanaged, Enum
674+
{
675+
int size = Unsafe.SizeOf<TEnum>();
676+
if (size == 1)
677+
{
678+
MemoryMarshal.Cast<TEnum, byte>(src).CopyTo(dest);
679+
}
680+
else if (size == 2)
681+
{
682+
WriteUInt16s(dest, MemoryMarshal.Cast<TEnum, ushort>(src), endianness);
683+
}
684+
else if (size == 4)
685+
{
686+
WriteUInt32s(dest, MemoryMarshal.Cast<TEnum, uint>(src), endianness);
687+
}
688+
else
689+
{
690+
WriteUInt64s(dest, MemoryMarshal.Cast<TEnum, ulong>(src), endianness);
691+
}
692+
}
693+
// #13 - Allow writing the abstract "Enum" type
694+
// For example, EndianBinaryPrimitives.WriteEnum((Enum)Enum.Parse(enumType, value))
695+
// Don't allow writing Enum[] though, since there is no way to read that
696+
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
697+
public static void WriteEnum(Span<byte> dest, Enum value, Endianness endianness)
698+
{
699+
Type underlyingType = Enum.GetUnderlyingType(value.GetType());
700+
ref byte data = ref Utils.GetRawData(value); // Use memory tricks to skip object header of generic Enum
701+
switch (Type.GetTypeCode(underlyingType))
702+
{
703+
case TypeCode.SByte:
704+
case TypeCode.Byte: dest[0] = data; break;
705+
case TypeCode.Int16:
706+
case TypeCode.UInt16: WriteUInt16(dest, Unsafe.As<byte, ushort>(ref data), endianness); break;
707+
case TypeCode.Int32:
708+
case TypeCode.UInt32: WriteUInt32(dest, Unsafe.As<byte, uint>(ref data), endianness); break;
709+
case TypeCode.Int64:
710+
case TypeCode.UInt64: WriteUInt64(dest, Unsafe.As<byte, ulong>(ref data), endianness); break;
711+
}
712+
}
713+
556714
public static void WriteDateTime(Span<byte> dest, DateTime value, Endianness endianness)
557715
{
558716
WriteInt64(dest, value.ToBinary(), endianness);

Source/EndianBinaryReader.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,14 @@ public byte PeekByte()
102102
Stream.Position = offset;
103103
return buffer[0];
104104
}
105+
public void PeekBytes(Span<byte> dest)
106+
{
107+
long offset = Stream.Position;
108+
109+
ReadBytes(dest);
110+
111+
Stream.Position = offset;
112+
}
105113

106114
public sbyte ReadSByte()
107115
{
@@ -412,7 +420,7 @@ public char[] ReadChars_TrimNullTerminators(int charCount)
412420
{
413421
char[] chars = new char[charCount];
414422
ReadChars(chars);
415-
Utils.TrimNullTerminators(ref chars);
423+
EndianBinaryPrimitives.TrimNullTerminators(ref chars);
416424
return chars;
417425
}
418426
public string ReadString_Count(int charCount)

Source/EndianBinaryWriter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ public void WriteEnums<TEnum>(ReadOnlySpan<TEnum> values) where TEnum : unmanage
309309
}
310310
}
311311
// #13 - Allow writing the abstract "Enum" type
312-
// For example, writer.Write((Enum)Enum.Parse(enumType, value))
312+
// For example, writer.WriteEnum((Enum)Enum.Parse(enumType, value))
313313
// Don't allow writing Enum[] though, since there is no way to read that
314314
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
315315
public void WriteEnum(Enum value)

Source/Utils.cs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,13 @@ private sealed class RawData
1111
{
1212
public byte Data;
1313
}
14-
public static ref byte GetRawData(object value)
14+
// This is a copy of what Enum uses internally
15+
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
16+
public static ref byte GetRawData<T>(T value) where T : class
1517
{
1618
return ref Unsafe.As<RawData>(value).Data; // Skip object header
1719
}
1820

19-
public static void TrimNullTerminators(ref char[] chars)
20-
{
21-
int i = Array.IndexOf(chars, '\0');
22-
if (i != -1)
23-
{
24-
Array.Resize(ref chars, i);
25-
}
26-
}
2721
private static bool TryConvertToInt32(object? obj, out int value)
2822
{
2923
try

Testing/BasicTests.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,5 +192,14 @@ public void WriteManually()
192192

193193
Assert.True(bytes.SequenceEqual(_bytes));
194194
}
195+
196+
[Fact]
197+
public void SpanIsProperlyTrimmed()
198+
{
199+
Span<char> test = stackalloc char[] { 'K', 'e', 'r', 'm', 'a', 'l', 'i', 's', '\0', '\0', };
200+
EndianBinaryPrimitives.TrimNullTerminators(ref test);
201+
202+
Assert.True(test.SequenceEqual("Kermalis"));
203+
}
195204
}
196205
}

0 commit comments

Comments
 (0)