using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Text; // Leaving this in this namespace because I only need this class here to work around maths being a separate assembly. // ReSharper disable once CheckNamespace namespace Robust.Shared.Utility; /// /// Helpers for dealing with string formatting and related things. /// public static class FormatHelpers { /// /// Format a string interpolation into a given buffer. If the buffer is not large enough, the result is truncated. /// /// /// Assuming everything you're formatting with implements , this should be zero-alloc. /// /// The buffer to format into. /// String interpolation handler to implement buffer formatting logic. /// The amount of chars written into the buffer. public static int FormatInto( Span buffer, [InterpolatedStringHandlerArgument("buffer")] ref BufferInterpolatedStringHandler handler) { return handler.Length; } /// /// Tries to format a string interpolation into a given buffer. /// If the buffer is not large enough, the result is truncated. /// /// /// /// This is intended to be used as an easy implementation of . /// /// /// Assuming everything you're formatting with implements , /// this should be zero-alloc on release. /// /// /// The buffer to format into. /// The amount of chars written into the buffer. /// String interpolation handler to implement buffer formatting logic. /// False if the formatting failed due to lack of space and was truncated. public static bool TryFormatInto( Span buffer, out int charsWritten, [InterpolatedStringHandlerArgument("buffer")] ref BufferInterpolatedStringHandler handler) { charsWritten = handler.Length; return !handler.Truncated; } /// /// Format a string interpolation into a given memory buffer. /// If the buffer is not large enough, the result is truncated. /// /// The memory buffer to format into. /// String interpolation handler to implement buffer formatting logic. /// The region of memory filled by the formatting operation. public static Memory FormatIntoMem( Memory buffer, [InterpolatedStringHandlerArgument("buffer")] ref MemoryBufferInterpolatedStringHandler handler) { return buffer[..handler.Handler.Length]; } /// /// Copy the contents of a into a memory buffer, giving back the subregion used. /// If the buffer is not enough space, the result is truncated. /// /// The to copy from /// The memory buffer to copy into. /// The memory region actually used by the copied data. public static Memory BuilderToMemory(StringBuilder builder, Memory memory) { var truncLength = Math.Min(builder.Length, memory.Length); builder.CopyTo(0, memory.Span, truncLength); return memory[..truncLength]; } } /// /// Interpolated string handler used by . /// [InterpolatedStringHandler] public ref struct BufferInterpolatedStringHandler { private Span _buffer; internal int Length; internal bool Truncated; [SuppressMessage("ReSharper", "UnusedParameter.Local")] public BufferInterpolatedStringHandler(int literalLength, int formattedCount, Span buffer) { _buffer = buffer; Length = 0; Truncated = false; } public void AppendLiteral(string literal) { AppendString(literal); } public void AppendFormatted(ReadOnlySpan value) { AppendString(value); } [SuppressMessage("ReSharper", "MergeCastWithTypeCheck")] public void AppendFormatted(T value) { if (value is ISpanFormattable) { // JIT is able to avoid boxing due to call structure. ((ISpanFormattable)value).TryFormat(_buffer, out var written, default, null); Advance(written); return; } var str = value?.ToString(); if (str != null) AppendString(str); } [SuppressMessage("ReSharper", "MergeCastWithTypeCheck")] public void AppendFormatted(T value, string format) { string? str; if (value is IFormattable) { if (value is ISpanFormattable) { // JIT is able to avoid boxing due to call structure. Truncated |= !((ISpanFormattable)value).TryFormat(_buffer, out var written, format, null); Advance(written); return; } // JIT is able to avoid boxing due to call structure. str = ((IFormattable)value).ToString(format, null); } else { str = value?.ToString(); } if (str != null) AppendString(str); } private void AppendString(ReadOnlySpan value) { var copyLength = value.Length; if (copyLength > _buffer.Length) { copyLength = _buffer.Length; Truncated = true; } value[..copyLength].CopyTo(_buffer); Advance(copyLength); } private void Advance(int amount) { _buffer = _buffer[amount..]; Length += amount; } } /// /// Interpolated string handler used by . /// /// /// Only exists as workaround for https://youtrack.jetbrains.com/issue/RIDER-78472. /// [InterpolatedStringHandler] public ref struct MemoryBufferInterpolatedStringHandler { public BufferInterpolatedStringHandler Handler; public MemoryBufferInterpolatedStringHandler(int literalLength, int formattedCount, Memory buffer) { Handler = new BufferInterpolatedStringHandler(literalLength, formattedCount, buffer.Span); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AppendLiteral(string literal) => Handler.AppendLiteral(literal); [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AppendFormatted(ReadOnlySpan value) => Handler.AppendFormatted(value); [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AppendFormatted(T value) => Handler.AppendFormatted(value); [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AppendFormatted(T value, string format) => Handler.AppendFormatted(value, format); }