Created
October 5, 2023 04:45
-
-
Save NoelFB/df26949122815d63a3f5b230635baca6 to your computer and use it in GitHub Desktop.
Stack based UTF8 string in C#
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Buffers; | |
using System.Buffers.Text; | |
using System.Runtime.CompilerServices; | |
using System.Runtime.InteropServices; | |
using System.Text; | |
/// <summary> | |
/// Stack-based UTF8 string for ImGui. | |
/// I frequently want to do short string manipulation (ex. $"{Icon} Play!") and this | |
/// avoids allocating on the new strings every single frame. | |
/// Text appended more than StackUtf8.Capacity will be ignored / not appended. | |
/// </summary> | |
[InterpolatedStringHandler] | |
[SkipLocalsInit] | |
public unsafe struct StackUtf8 | |
{ | |
public const int Capacity = 128; | |
public fixed byte Buffer[Capacity]; | |
public int Length { get; private set; } | |
public StackUtf8() { Buffer[0] = 0; Length = 0; } | |
public StackUtf8(int literalLength, int formattedCount) : this() { } | |
public StackUtf8(in string str) : this() { Append(str); } | |
public StackUtf8(in ReadOnlySpan<char> str) : this() { Append(str); } | |
public static implicit operator StackUtf8(in string str) => new(str); | |
public static implicit operator StackUtf8(in ReadOnlySpan<char> str) => new(str); | |
public ReadOnlySpan<byte> Span => MemoryMarshal.CreateReadOnlySpan(ref Buffer[0], Length); | |
public Span<byte> Available => Length >= Capacity - 1 ? Span<byte>.Empty : MemoryMarshal.CreateSpan(ref Buffer[0], Capacity)[Length..]; | |
public void Append(in StackUtf8 s) => Append(s.Span); | |
public void Append(ReadOnlySpan<char> s) => AppendAvailable(s); | |
public void Append(ReadOnlySpan<byte> s) | |
{ | |
if (Available.Length > 0) | |
{ | |
if (s.Length > Available.Length) | |
s = s[0..Available.Length]; | |
s.CopyTo(Available); | |
Length += s.Length; | |
Buffer[Length] = 0; | |
} | |
} | |
public void AppendLiteral(string s) => AppendAvailable(s); | |
public void AppendFormatted<T>(T value) => AppendFormatted(value, null); | |
public void AppendFormatted<T>(T value, string? format) | |
{ | |
if (value is string vString) | |
AppendAvailable(vString.AsSpan()); | |
else | |
throw new NotImplementedException(); | |
} | |
public void AppendFormatted(int value) => AppendFormatted(value, null); | |
public void AppendFormatted(int value, string? format) | |
{ | |
Utf8Formatter.TryFormat(value, Available, out int appended, GetFormat(format)); | |
Length += appended; | |
Buffer[Length] = 0; | |
} | |
public void AppendFormatted(long value) => AppendFormatted(value, null); | |
public void AppendFormatted(long value, string? format) | |
{ | |
Utf8Formatter.TryFormat(value, Available, out int appended, GetFormat(format)); | |
Length += appended; | |
Buffer[Length] = 0; | |
} | |
public void AppendFormatted(float value) => AppendFormatted(value, null); | |
public void AppendFormatted(float value, string? format) | |
{ | |
Utf8Formatter.TryFormat(value, Available, out int appended, GetFormat(format)); | |
Length += appended; | |
Buffer[Length] = 0; | |
} | |
public void AppendFormatted(double value) => AppendFormatted(value, null); | |
public void AppendFormatted(double value, string? format) | |
{ | |
Utf8Formatter.TryFormat(value, Available, out int appended, GetFormat(format)); | |
Length += appended; | |
Buffer[Length] = 0; | |
} | |
private void AppendAvailable(ReadOnlySpan<char> append) | |
{ | |
if (Available.Length <= 0) | |
return; | |
var len = Encoding.UTF8.GetByteCount(append); | |
if (len > Available.Length) | |
{ | |
Span<byte> buf = stackalloc byte[len]; | |
len = Available.Length; | |
Encoding.UTF8.GetBytes(append, buf); | |
buf[0..len].CopyTo(Available); | |
Length += len; | |
} | |
else | |
{ | |
Encoding.UTF8.GetBytes(append, Available); | |
Length += len; | |
} | |
Buffer[Length] = 0; | |
} | |
public void Clear() | |
{ | |
Length = 0; | |
Buffer[0] = 0; | |
} | |
public override string ToString() | |
{ | |
fixed (byte* ptr = Buffer) | |
return Encoding.UTF8.GetString(ptr, Length); | |
} | |
private static StandardFormat GetFormat(string? format) | |
{ | |
StandardFormat sf = default; | |
if (format != null && StandardFormat.TryParse(format, out var standard)) | |
sf = standard; | |
return sf; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment