Skip to content

Instantly share code, notes, and snippets.

@NoelFB
Created October 5, 2023 04:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NoelFB/df26949122815d63a3f5b230635baca6 to your computer and use it in GitHub Desktop.
Save NoelFB/df26949122815d63a3f5b230635baca6 to your computer and use it in GitHub Desktop.
Stack based UTF8 string in C#
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