mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 10:17:22 +01:00
Manual overloads for ImGui functions accepting text (#2319)
* wip2 * Implement AutoUtf8Buffer * reformat * Work on manual bindings * restructure * Name scripts properly * Update utility functions to use ImU8String * add overloads * Add more overloads * Use ImGuiWindow from gen, support AddCallback * Use LibraryImport for custom ImGuiNative functinos * Make manual overloads for string-returning functinos * Make all overloads with self as its first parameter extension methods * Fix overload resolution by removing unnecessary * in => scoped in * Fix compilation errors
This commit is contained in:
parent
0c63541864
commit
c69329f592
293 changed files with 61312 additions and 754 deletions
378
imgui/Dalamud.Bindings.ImGui/ImU8String.cs
Normal file
378
imgui/Dalamud.Bindings.ImGui/ImU8String.cs
Normal file
|
|
@ -0,0 +1,378 @@
|
|||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.Unicode;
|
||||
|
||||
namespace Dalamud.Bindings.ImGui;
|
||||
|
||||
[InterpolatedStringHandler]
|
||||
public ref struct ImU8String : IDisposable
|
||||
{
|
||||
public const int AllocFreeBufferSize = 512;
|
||||
private const int MinimumRentSize = AllocFreeBufferSize * 2;
|
||||
|
||||
private IFormatProvider? formatProvider;
|
||||
private byte[]? rentedBuffer;
|
||||
private ref readonly byte externalFirstByte;
|
||||
private State state;
|
||||
private FixedBufferContainer fixedBuffer;
|
||||
|
||||
[Flags]
|
||||
private enum State : byte
|
||||
{
|
||||
None = 0,
|
||||
Initialized = 1 << 0,
|
||||
NullTerminated = 1 << 1,
|
||||
Interpolation = 1 << 2,
|
||||
}
|
||||
|
||||
public ImU8String()
|
||||
{
|
||||
Unsafe.SkipInit(out this.fixedBuffer);
|
||||
this.FixedBufferByteRef = 0;
|
||||
}
|
||||
|
||||
public ImU8String(int literalLength, int formattedCount)
|
||||
: this(ReadOnlySpan<byte>.Empty)
|
||||
{
|
||||
this.state |= State.Interpolation;
|
||||
literalLength += formattedCount * 4;
|
||||
this.Reserve(literalLength);
|
||||
}
|
||||
|
||||
public ImU8String(int literalLength, int formattedCount, IFormatProvider? formatProvider)
|
||||
: this(literalLength, formattedCount)
|
||||
{
|
||||
this.formatProvider = formatProvider;
|
||||
}
|
||||
|
||||
public ImU8String(ReadOnlySpan<byte> text, bool ensureNullTermination = false)
|
||||
: this()
|
||||
{
|
||||
this.state = State.Initialized;
|
||||
if (text.IsEmpty)
|
||||
{
|
||||
this.state |= State.NullTerminated;
|
||||
}
|
||||
else if (ensureNullTermination)
|
||||
{
|
||||
this.Reserve(text.Length + 1);
|
||||
var buffer = this.Buffer;
|
||||
text.CopyTo(buffer);
|
||||
buffer[^1] = 0;
|
||||
this.Length = text.Length;
|
||||
this.state |= State.NullTerminated;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.externalFirstByte = ref text[0];
|
||||
this.Length = text.Length;
|
||||
if (Unsafe.Add(ref Unsafe.AsRef(in this.externalFirstByte), this.Length) == 0)
|
||||
this.state |= State.NullTerminated;
|
||||
}
|
||||
}
|
||||
|
||||
public ImU8String(ReadOnlyMemory<byte> text, bool ensureNullTermination = false)
|
||||
: this(text.Span, ensureNullTermination)
|
||||
{
|
||||
}
|
||||
|
||||
public ImU8String(ReadOnlySpan<char> text)
|
||||
: this()
|
||||
{
|
||||
this.state = State.Initialized | State.NullTerminated;
|
||||
this.Length = Encoding.UTF8.GetByteCount(text);
|
||||
if (this.Length + 1 < AllocFreeBufferSize)
|
||||
{
|
||||
var newSpan = this.FixedBufferSpan[..this.Length];
|
||||
Encoding.UTF8.GetBytes(text, newSpan);
|
||||
this.FixedBufferSpan[this.Length] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.rentedBuffer = ArrayPool<byte>.Shared.Rent(this.Length + 1);
|
||||
var newSpan = this.rentedBuffer.AsSpan(0, this.Length);
|
||||
Encoding.UTF8.GetBytes(text, newSpan);
|
||||
this.rentedBuffer[this.Length] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public ImU8String(ReadOnlyMemory<char> text)
|
||||
: this(text.Span)
|
||||
{
|
||||
}
|
||||
|
||||
public ImU8String(string? text)
|
||||
: this(text.AsSpan())
|
||||
{
|
||||
}
|
||||
|
||||
public unsafe ImU8String(byte* text)
|
||||
: this(MemoryMarshal.CreateReadOnlySpanFromNullTerminated(text))
|
||||
{
|
||||
this.state |= State.NullTerminated;
|
||||
}
|
||||
|
||||
public unsafe ImU8String(char* text)
|
||||
: this(MemoryMarshal.CreateReadOnlySpanFromNullTerminated(text))
|
||||
{
|
||||
}
|
||||
|
||||
public static ImU8String Empty => default;
|
||||
|
||||
public readonly ReadOnlySpan<byte> Span =>
|
||||
!Unsafe.IsNullRef(in this.externalFirstByte)
|
||||
? MemoryMarshal.CreateReadOnlySpan(in this.externalFirstByte, this.Length)
|
||||
: this.rentedBuffer is { } rented
|
||||
? rented.AsSpan(0, this.Length)
|
||||
: Unsafe.AsRef(in this).FixedBufferSpan[..this.Length];
|
||||
|
||||
public int Length { get; private set; }
|
||||
|
||||
public readonly bool IsNull => (this.state & State.Initialized) == 0;
|
||||
|
||||
public readonly bool IsEmpty => this.Length == 0;
|
||||
|
||||
internal Span<byte> Buffer
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Unsafe.IsNullRef(in this.externalFirstByte))
|
||||
this.ConvertToOwned();
|
||||
|
||||
return this.rentedBuffer is { } buf
|
||||
? buf.AsSpan()
|
||||
: MemoryMarshal.Cast<FixedBufferContainer, byte>(new(ref Unsafe.AsRef(ref this.fixedBuffer)));
|
||||
}
|
||||
}
|
||||
|
||||
private Span<byte> RemainingBuffer => this.Buffer[this.Length..];
|
||||
|
||||
private ref byte FixedBufferByteRef => ref this.FixedBufferSpan[0];
|
||||
|
||||
private Span<byte> FixedBufferSpan =>
|
||||
MemoryMarshal.Cast<FixedBufferContainer, byte>(new(ref Unsafe.AsRef(ref this.fixedBuffer)));
|
||||
|
||||
public static implicit operator ImU8String(ReadOnlySpan<byte> text) => new(text);
|
||||
public static implicit operator ImU8String(ReadOnlyMemory<byte> text) => new(text);
|
||||
public static implicit operator ImU8String(Span<byte> text) => new(text);
|
||||
public static implicit operator ImU8String(Memory<byte> text) => new(text);
|
||||
public static implicit operator ImU8String(byte[]? text) => new(text.AsSpan());
|
||||
public static implicit operator ImU8String(ReadOnlySpan<char> text) => new(text);
|
||||
public static implicit operator ImU8String(ReadOnlyMemory<char> text) => new(text);
|
||||
public static implicit operator ImU8String(Span<char> text) => new(text);
|
||||
public static implicit operator ImU8String(Memory<char> text) => new(text);
|
||||
public static implicit operator ImU8String(char[]? text) => new(text.AsSpan());
|
||||
public static implicit operator ImU8String(string? text) => new(text);
|
||||
public static unsafe implicit operator ImU8String(byte* text) => new(text);
|
||||
public static unsafe implicit operator ImU8String(char* text) => new(text);
|
||||
|
||||
public ref readonly byte GetPinnableNullTerminatedReference(ReadOnlySpan<byte> defaultValue = default)
|
||||
{
|
||||
if (this.IsNull)
|
||||
return ref defaultValue.GetPinnableReference();
|
||||
|
||||
if (this.IsEmpty)
|
||||
{
|
||||
ref var t = ref this.FixedBufferSpan[0];
|
||||
t = 0;
|
||||
return ref t;
|
||||
}
|
||||
|
||||
if ((this.state & State.NullTerminated) == 0)
|
||||
this.ConvertToOwned();
|
||||
|
||||
return ref this.Span[0];
|
||||
}
|
||||
|
||||
private void ConvertToOwned()
|
||||
{
|
||||
if (Unsafe.IsNullRef(in this.externalFirstByte))
|
||||
return;
|
||||
|
||||
Debug.Assert(this.rentedBuffer is null);
|
||||
|
||||
if (this.Length + 1 < AllocFreeBufferSize)
|
||||
{
|
||||
var fixedBufferSpan = this.FixedBufferSpan;
|
||||
this.Span.CopyTo(fixedBufferSpan);
|
||||
fixedBufferSpan[this.Length] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
var newBuffer = ArrayPool<byte>.Shared.Rent(this.Length + 1);
|
||||
this.Span.CopyTo(newBuffer);
|
||||
|
||||
newBuffer[this.Length] = 0;
|
||||
this.rentedBuffer = newBuffer;
|
||||
}
|
||||
|
||||
this.state |= State.NullTerminated;
|
||||
this.externalFirstByte = ref Unsafe.NullRef<byte>();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (this.rentedBuffer is { } buf)
|
||||
{
|
||||
this.rentedBuffer = null;
|
||||
ArrayPool<byte>.Shared.Return(buf);
|
||||
}
|
||||
}
|
||||
|
||||
public ImU8String MoveOrDefault(ImU8String other)
|
||||
{
|
||||
if (!this.IsNull)
|
||||
{
|
||||
other.Dispose();
|
||||
var res = this;
|
||||
this = default;
|
||||
return res;
|
||||
}
|
||||
|
||||
return other;
|
||||
}
|
||||
|
||||
public override readonly string ToString() => Encoding.UTF8.GetString(this.Span);
|
||||
|
||||
public void AppendLiteral(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return;
|
||||
|
||||
var remaining = this.RemainingBuffer;
|
||||
var len = Encoding.UTF8.GetByteCount(value);
|
||||
if (remaining.Length <= len)
|
||||
this.IncreaseBuffer(out remaining, this.Length + len + 1);
|
||||
this.Buffer[this.Length += Encoding.UTF8.GetBytes(value.AsSpan(), remaining)] = 0;
|
||||
}
|
||||
|
||||
public void AppendFormatted(ReadOnlySpan<byte> value) => this.AppendFormatted(value, null);
|
||||
|
||||
public void AppendFormatted(ReadOnlySpan<byte> value, string? format)
|
||||
{
|
||||
var remaining = this.RemainingBuffer;
|
||||
if (remaining.Length < value.Length + 1)
|
||||
this.IncreaseBuffer(out remaining, this.Length + value.Length + 1);
|
||||
value.CopyTo(remaining);
|
||||
this.Buffer[this.Length += value.Length] = 0;
|
||||
}
|
||||
|
||||
public void AppendFormatted(ReadOnlySpan<byte> value, int alignment) =>
|
||||
this.AppendFormatted(value, alignment, null);
|
||||
|
||||
public void AppendFormatted(ReadOnlySpan<byte> value, int alignment, string? format)
|
||||
{
|
||||
var startingPos = this.Length;
|
||||
this.AppendFormatted(value, format);
|
||||
FixAlignment(startingPos, alignment);
|
||||
}
|
||||
|
||||
public void AppendFormatted(ReadOnlySpan<char> value) => this.AppendFormatted(value, null);
|
||||
|
||||
public void AppendFormatted(ReadOnlySpan<char> value, string? format)
|
||||
{
|
||||
var remaining = this.RemainingBuffer;
|
||||
var len = Encoding.UTF8.GetByteCount(value);
|
||||
if (remaining.Length < len + 1)
|
||||
this.IncreaseBuffer(out remaining, this.Length + len + 1);
|
||||
this.Buffer[this.Length += Encoding.UTF8.GetBytes(value, remaining)] = 0;
|
||||
}
|
||||
|
||||
public void AppendFormatted(ReadOnlySpan<char> value, int alignment) =>
|
||||
this.AppendFormatted(value, alignment, null);
|
||||
|
||||
public void AppendFormatted(ReadOnlySpan<char> value, int alignment, string? format)
|
||||
{
|
||||
var startingPos = this.Length;
|
||||
this.AppendFormatted(value, format);
|
||||
FixAlignment(startingPos, alignment);
|
||||
}
|
||||
|
||||
public void AppendFormatted<T>(T value) => this.AppendFormatted(value, null);
|
||||
|
||||
public void AppendFormatted<T>(T value, string? format)
|
||||
{
|
||||
var remaining = this.RemainingBuffer;
|
||||
if (remaining.Length < 1)
|
||||
this.IncreaseBuffer(out remaining);
|
||||
|
||||
int written;
|
||||
while (true)
|
||||
{
|
||||
var handler = new Utf8.TryWriteInterpolatedStringHandler(1, 1, remaining[..^1], this.formatProvider, out _);
|
||||
handler.AppendFormatted(value, format);
|
||||
if (Utf8.TryWrite(remaining, this.formatProvider, ref handler, out written))
|
||||
break;
|
||||
this.IncreaseBuffer(out remaining);
|
||||
}
|
||||
|
||||
this.Buffer[this.Length += written] = 0;
|
||||
}
|
||||
|
||||
public void AppendFormatted<T>(T value, int alignment) => this.AppendFormatted(value, alignment, null);
|
||||
|
||||
public void AppendFormatted<T>(T value, int alignment, string? format)
|
||||
{
|
||||
var startingPos = this.Length;
|
||||
this.AppendFormatted(value, format);
|
||||
FixAlignment(startingPos, alignment);
|
||||
}
|
||||
|
||||
public void Reserve(int length)
|
||||
{
|
||||
if (length >= AllocFreeBufferSize)
|
||||
IncreaseBuffer(out _, length);
|
||||
}
|
||||
|
||||
private void FixAlignment(int startingPos, int alignment)
|
||||
{
|
||||
var appendedLength = this.Length - startingPos;
|
||||
|
||||
var leftAlign = alignment < 0;
|
||||
if (leftAlign)
|
||||
alignment = -alignment;
|
||||
|
||||
var fillLength = alignment - appendedLength;
|
||||
if (fillLength <= 0)
|
||||
return;
|
||||
|
||||
var destination = this.Buffer;
|
||||
if (fillLength > destination.Length - this.Length)
|
||||
{
|
||||
this.IncreaseBuffer(out _, fillLength + 1);
|
||||
destination = this.Buffer;
|
||||
}
|
||||
|
||||
if (leftAlign)
|
||||
{
|
||||
destination.Slice(this.Length, fillLength).Fill((byte)' ');
|
||||
}
|
||||
else
|
||||
{
|
||||
destination.Slice(startingPos, appendedLength).CopyTo(destination[(startingPos + fillLength)..]);
|
||||
destination.Slice(startingPos, fillLength).Fill((byte)' ');
|
||||
}
|
||||
|
||||
this.Buffer[this.Length += fillLength] = 0;
|
||||
}
|
||||
|
||||
private void IncreaseBuffer(out Span<byte> remaining, int minCapacity = 0)
|
||||
{
|
||||
minCapacity = Math.Max(minCapacity, Math.Max(this.Buffer.Length * 2, MinimumRentSize));
|
||||
var newBuffer = ArrayPool<byte>.Shared.Rent(minCapacity);
|
||||
this.Span.CopyTo(newBuffer);
|
||||
newBuffer[this.Length] = 0;
|
||||
if (this.rentedBuffer is not null)
|
||||
ArrayPool<byte>.Shared.Return(this.rentedBuffer);
|
||||
|
||||
this.rentedBuffer = newBuffer;
|
||||
this.externalFirstByte = ref Unsafe.NullRef<byte>();
|
||||
remaining = newBuffer.AsSpan(this.Length);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = AllocFreeBufferSize)]
|
||||
private struct FixedBufferContainer;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue