Remove almost all allocations from F3 menu. (#2923)

This commit is contained in:
Pieter-Jan Briers
2022-06-10 12:59:43 +02:00
committed by GitHub
parent 8d5fa58e1a
commit 776669b789
32 changed files with 784 additions and 125 deletions

View File

@@ -1,9 +1,11 @@
using Robust.Client.Graphics;
using System;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Profiling;
using Robust.Shared.Utility;
namespace Robust.Client.Profiling;
@@ -14,26 +16,33 @@ public sealed class LiveProfileViewControl : Control
public int MaxDepth { get; set; } = 2;
private readonly Font? _font;
private readonly char[] _sampleBuffer = new char[32];
public LiveProfileViewControl()
{
IoCManager.InjectDependencies(this);
if (!_resourceCache.TryGetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf", out var font))
return;
_font = font.MakeDefault();
}
protected internal override void Draw(DrawingHandleScreen handle)
{
base.Draw(handle);
if (!_profManager.IsEnabled)
if (!_profManager.IsEnabled || _font == null)
return;
var font = _resourceCache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf").MakeDefault();
var baseLine = new Vector2(0, font.GetAscent(UIScale));
var baseLine = new Vector2(0, _font.GetAscent(UIScale));
ref readonly var buffer = ref _profManager.Buffer;
ref readonly var index = ref buffer.Index(buffer.IndexWriteOffset - 1);
DrawData drawData = default;
drawData.Font = font;
drawData.Font = _font;
drawData.Buffer = buffer;
drawData.Index = index;
drawData.Handle = handle;
@@ -79,13 +88,13 @@ public sealed class LiveProfileViewControl : Control
var str = value.Type switch
{
ProfValueType.TimeAllocSample =>
$"{value.TimeAllocSample.Time * 1000:N2} ms, {value.TimeAllocSample.Alloc} B",
ProfValueType.Int32 => value.Int32.ToString(),
ProfValueType.Int64 => value.Int64.ToString(),
_ => "???"
FormatHelpers.FormatIntoMem(_sampleBuffer, $"{value.TimeAllocSample.Time * 1000:N2} ms, {value.TimeAllocSample.Alloc} B"),
ProfValueType.Int32 => FormatHelpers.FormatIntoMem(_sampleBuffer, $"{value.Int32}"),
ProfValueType.Int64 => FormatHelpers.FormatIntoMem(_sampleBuffer, $"{value.Int64}"),
_ => "???".AsMemory()
};
data.Handle.DrawString(data.Font, baseline, str, UIScale, Color.White);
data.Handle.DrawString(data.Font, baseline, str.Span, UIScale, Color.White);
}
private void DrawCmd(

View File

@@ -23,6 +23,7 @@ namespace Robust.Client.UserInterface.Controls
private readonly List<int> _cachedTextWidths = new();
private bool _textDimensionCacheValid;
private string? _text;
private ReadOnlyMemory<char> _textMemory;
private bool _clipText;
private AlignMode _align;
@@ -34,13 +35,44 @@ namespace Robust.Client.UserInterface.Controls
/// <summary>
/// The text to display.
/// </summary>
/// <remarks>
/// Replaces <see cref="TextMemory"/> when set.
/// </remarks>
/// <exception cref="InvalidOperationException">
/// Thrown if <see cref="TextMemory"/> was set directly and there is no backing string instance to fetch.
/// </exception>
[ViewVariables]
public string? Text
{
get => _text;
get => _text ?? (_textMemory.Length > 0 ? throw new InvalidOperationException("Label uses TextMemory, cannot fetch string text.") : null);
set
{
_text = value;
_textMemory = value.AsMemory();
_textDimensionCacheValid = false;
InvalidateMeasure();
}
}
/// <summary>
/// The text to display, set as a read-only memory.
/// </summary>
/// <remarks>
/// <para>
/// Note that updating the backing memory while the control is using it can result in incorrect display due to caching of measure information and similar.
/// If you modify the backing storage, re-assign the property to invalidate these.
/// </para>
/// <para>
/// Sets <see cref="Text"/> to throw an exception if read, as there is no backing string to retrieve.
/// </para>
/// </remarks>
public ReadOnlyMemory<char> TextMemory
{
get => _textMemory;
set
{
_text = null;
_textMemory = value;
_textDimensionCacheValid = false;
InvalidateMeasure();
}
@@ -124,7 +156,7 @@ namespace Robust.Client.UserInterface.Controls
protected internal override void Draw(DrawingHandleScreen handle)
{
if (_text == null)
if (_textMemory.Length == 0)
{
return;
}
@@ -182,7 +214,7 @@ namespace Robust.Client.UserInterface.Controls
var baseLine = CalcBaseline();
foreach (var rune in _text.EnumerateRunes())
foreach (var rune in _textMemory.Span.EnumerateRunes())
{
if (rune == new Rune('\n'))
{
@@ -245,7 +277,7 @@ namespace Robust.Client.UserInterface.Controls
_cachedTextWidths.Clear();
_cachedTextWidths.Add(0);
if (_text == null)
if (_textMemory.Length == 0)
{
_cachedTextHeight = 0;
_textDimensionCacheValid = true;
@@ -254,7 +286,7 @@ namespace Robust.Client.UserInterface.Controls
var font = ActualFont;
var height = font.GetHeight(UIScale);
foreach (var rune in _text.EnumerateRunes())
foreach (var rune in _textMemory.Span.EnumerateRunes())
{
if (rune == new Rune('\n'))
{

View File

@@ -1,8 +1,10 @@
using System.Text;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Client.UserInterface.CustomControls
{
@@ -12,6 +14,9 @@ namespace Robust.Client.UserInterface.CustomControls
private readonly Label _label;
private readonly StringBuilder _textBuilder = new();
private readonly char[] _textBuffer = new char[256];
public DebugClydePanel()
{
IoCManager.InjectDependencies(this);
@@ -33,21 +38,23 @@ namespace Robust.Client.UserInterface.CustomControls
return;
}
_textBuilder.Clear();
var info = _clydeInternal.DebugInfo;
var stats = _clydeInternal.DebugStats;
var overridingText = "";
if (info.Overriding)
{
overridingText = $"\nVersion override: {info.OpenGLVersion}";
}
_label.Text = $@"Renderer: {info.Renderer}
_textBuilder.Append($@"Renderer: {info.Renderer}
Vendor: {info.Vendor}
Version: {info.VersionString}{overridingText}
Draw Calls: Cly: {stats.LastClydeDrawCalls} GL: {stats.LastGLDrawCalls}
Batches: {stats.LastBatches} Max size: {stats.LargestBatchSize}
Lights: {stats.TotalLights}";
Version: {info.VersionString}\n");
if (info.Overriding)
_textBuilder.Append($"Version override: {info.OpenGLVersion}\n");
_textBuilder.Append($@"Draw Calls: Cly: {stats.LastClydeDrawCalls} GL: {stats.LastGLDrawCalls}
Batches: {stats.LastBatches} Max size: ({stats.LargestBatchSize.vertices} vtx, {stats.LargestBatchSize.vertices} idx)
Lights: {stats.TotalLights}");
_label.TextMemory = FormatHelpers.BuilderToMemory(_textBuilder, _textBuffer);
}
}
}

View File

@@ -8,6 +8,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Client.UserInterface.CustomControls
{
@@ -20,8 +21,10 @@ namespace Robust.Client.UserInterface.CustomControls
[Dependency] private readonly IClyde _displayManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
private readonly StringBuilder _textBuilder = new();
private readonly char[] _textBuffer = new char[1024];
private readonly Label _contents;
private UIBox2i _uiBox;
public DebugCoordsPanel()
{
@@ -53,7 +56,7 @@ namespace Robust.Client.UserInterface.CustomControls
return;
}
var stringBuilder = new StringBuilder();
_textBuilder.Clear();
var mouseScreenPos = _inputManager.MouseScreenPosition;
var screenSize = _displayManager.ScreenSize;
@@ -79,21 +82,20 @@ namespace Robust.Client.UserInterface.CustomControls
var controlHovered = UserInterfaceManager.CurrentlyHovered;
stringBuilder.AppendFormat(@"Positioning Debug:
Screen Size: {0}
_textBuilder.Append($@"Positioning Debug:
Screen Size: {screenSize}
Mouse Pos:
Screen: {1}
{2}
{3}
{4}
GUI: {5}", screenSize, mouseScreenPos, mouseWorldMap, mouseGridPos,
tile, controlHovered);
Screen: {mouseScreenPos}
{mouseWorldMap}
{mouseGridPos}
{tile}
GUI: {controlHovered}");
stringBuilder.AppendLine("\nAttached Entity:");
_textBuilder.AppendLine("\nAttached Entity:");
var controlledEntity = _playerManager?.LocalPlayer?.ControlledEntity ?? EntityUid.Invalid;
if (controlledEntity == EntityUid.Invalid)
{
stringBuilder.AppendLine("No attached entity.");
_textBuilder.AppendLine("No attached entity.");
}
else
{
@@ -106,42 +108,16 @@ Mouse Pos:
Angle gridRotation = _mapManager.TryGetGrid(entityTransform.GridID, out var grid) ? grid.WorldRotation : Angle.Zero;
stringBuilder.AppendFormat(@" Screen: {0}
{1}
{2}
Rotation: {3:F2}°
EntId: {4}
GridID: {5}
Grid Rotation: {6:F2}°", playerScreen, playerWorldOffset, playerCoordinates, playerRotation.Degrees, entityTransform.Owner,
entityTransform.GridID, gridRotation.Degrees);
_textBuilder.Append($@" Screen: {playerScreen}
{playerWorldOffset}
{playerCoordinates}
Rotation: {playerRotation.Degrees:F2}°
EntId: {entityTransform.Owner}
GridID: {entityTransform.GridID}
Grid Rotation: {gridRotation.Degrees:F2}°");
}
if (controlHovered != null)
{
_uiBox = UIBox2i.FromDimensions(controlHovered.GlobalPixelPosition, controlHovered.PixelSize);
}
_contents.Text = stringBuilder.ToString();
// MinimumSizeChanged();
}
protected internal override void Draw(DrawingHandleScreen handle)
{
base.Draw(handle);
if (!VisibleInTree)
{
return;
}
var (x, y) = GlobalPixelPosition;
var renderBox = new UIBox2(
_uiBox.Left - x,
_uiBox.Top - y,
_uiBox.Right - x,
_uiBox.Bottom - y);
handle.DrawRect(renderBox, Color.Red, false);
_contents.TextMemory = FormatHelpers.BuilderToMemory(_textBuilder, _textBuffer);
}
}
}

View File

@@ -1,9 +1,11 @@
using System.Text;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Client.UserInterface.CustomControls
{
@@ -13,6 +15,10 @@ namespace Robust.Client.UserInterface.CustomControls
private readonly Label _label;
private readonly StringBuilder _textBuilder = new();
private readonly char[] _textBuffer = new char[512];
public DebugInputPanel()
{
IoCManager.InjectDependencies(this);
@@ -36,8 +42,15 @@ namespace Robust.Client.UserInterface.CustomControls
return;
}
var functionsText = string.Join("\n", _inputManager.DownKeyFunctions);
_label.Text = $"Context: {_inputManager.Contexts.ActiveContext.Name}\n{functionsText}";
_textBuilder.Clear();
_textBuilder.Append($"Input context: {_inputManager.Contexts.ActiveContext.Name}");
foreach (var func in _inputManager.DownKeyFunctions)
{
_textBuilder.Append($"\n {func.FunctionName}");
}
_label.TextMemory = FormatHelpers.BuilderToMemory(_textBuilder, _textBuffer);
}
}
}

View File

@@ -1,9 +1,9 @@
using System;
using System.Linq;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Client.UserInterface.CustomControls
{
@@ -11,6 +11,7 @@ namespace Robust.Client.UserInterface.CustomControls
{
private readonly Label _label;
private readonly char[] _textBuffer = new char[512];
private readonly long[] _allocDeltas = new long[60];
private long _lastAllocated;
private int _allocDeltaIndex;
@@ -40,10 +41,10 @@ namespace Robust.Client.UserInterface.CustomControls
return;
}
_label.Text = GetMemoryInfo();
_label.TextMemory = GetMemoryInfo();
}
private string GetMemoryInfo()
private ReadOnlyMemory<char> GetMemoryInfo()
{
var gen0 = GC.CollectionCount(0);
var gen1 = GC.CollectionCount(1);
@@ -53,18 +54,15 @@ namespace Robust.Client.UserInterface.CustomControls
LogAllocSize(allocated);
var info = GC.GetGCMemoryInfo();
return $@"Total Allocated: {FormatBytes(allocated)}
return FormatHelpers.FormatIntoMem(
_textBuffer,
$@"Total Allocated: {allocated / 1024:N0} KiB
Total Collections: {gen0} {gen1} {gen2}
Alloc Rate: {FormatBytes(CalculateAllocRate())} / frame
Alloc Rate: {CalculateAllocRate() / 1024:N0} KiB / frame
Last GC: {info.Index} Gen: {info.Generation} BGC: {info.Concurrent} C: {info.Compacted}
Pause: {info.PauseDurations[0].TotalMilliseconds}ms
Heap: {FormatBytes(info.HeapSizeBytes)}
Fragmented: {FormatBytes(info.FragmentedBytes)}";
}
private static string FormatBytes(long bytes)
{
return $"{bytes / 1024} KiB";
Heap: {info.HeapSizeBytes / 1024:N0} KiB
Fragmented: {info.FragmentedBytes / 1024:N0} KiB");
}
private void LogAllocSize(long allocated)
@@ -79,7 +77,18 @@ Last GC: {info.Index} Gen: {info.Generation} BGC: {info.Concurrent} C: {info.Com
private long CalculateAllocRate()
{
return (long) _allocDeltas.Where(x => x >= 0).Average();
var sum = 0L;
var count = 0;
foreach (var val in _allocDeltas)
{
if (val >= 0)
{
sum += val;
count += 1;
}
}
return count == 0 ? 0 : sum / count;
}
}
}

View File

@@ -4,6 +4,7 @@ using Robust.Client.UserInterface.Controls;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Client.UserInterface.CustomControls
{
@@ -15,6 +16,8 @@ namespace Robust.Client.UserInterface.CustomControls
private readonly IClientNetManager NetManager;
private readonly IGameTiming GameTiming;
private readonly char[] _textBuffer = new char[256];
private TimeSpan LastUpdate;
private Label contents;
@@ -84,11 +87,10 @@ namespace Robust.Client.UserInterface.CustomControls
LastSentPackets = stats.SentPackets;
LastReceivedPackets = stats.ReceivedPackets;
contents.Text = $@"UP: {sentBytes / ONE_KIBIBYTE:N} KiB/s, {sentPackets} pckt/s, {LastSentBytes / ONE_KIBIBYTE:N} KiB, {LastSentPackets} pckt
contents.TextMemory = FormatHelpers.FormatIntoMem(_textBuffer,
$@"UP: {sentBytes / ONE_KIBIBYTE:N} KiB/s, {sentPackets} pckt/s, {LastSentBytes / ONE_KIBIBYTE:N} KiB, {LastSentPackets} pckt
DOWN: {receivedBytes / ONE_KIBIBYTE:N} KiB/s, {receivedPackets} pckt/s, {LastReceivedBytes / ONE_KIBIBYTE:N} KiB, {LastReceivedPackets} pckt
PING: {NetManager.ServerChannel?.Ping ?? -1} ms";
// MinimumSizeChanged();
PING: {NetManager.ServerChannel?.Ping ?? -1} ms");
}
}
}

View File

@@ -1,8 +1,10 @@
using System;
using Robust.Client.GameStates;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Client.UserInterface.CustomControls
{
@@ -11,7 +13,8 @@ namespace Robust.Client.UserInterface.CustomControls
private readonly IGameTiming _gameTiming;
private readonly IClientGameStateManager _gameState;
private Label _contents;
private readonly char[] _textBuffer = new char[256];
private readonly Label _contents;
public DebugTimePanel(IGameTiming gameTiming, IClientGameStateManager gameState)
{
@@ -49,9 +52,10 @@ namespace Robust.Client.UserInterface.CustomControls
// This means that CurTick reports the NEXT tick to be ran, NOT the last tick that was ran.
// This is why there's a -1 on Pred:.
_contents.Text = $@"Paused: {_gameTiming.Paused}, CurTick: {_gameTiming.CurTick}/{_gameTiming.CurTick-1}, CurServerTick: {_gameState.CurServerTick}, Pred: {_gameTiming.CurTick.Value - _gameState.CurServerTick.Value-1}
_contents.TextMemory = FormatHelpers.FormatIntoMem(_textBuffer,
$@"Paused: {_gameTiming.Paused}, CurTick: {_gameTiming.CurTick}/{_gameTiming.CurTick - 1}, CurServerTick: {_gameState.CurServerTick}, Pred: {_gameTiming.CurTick.Value - _gameState.CurServerTick.Value - 1}
CurTime: {_gameTiming.CurTime:hh\:mm\:ss\.ff}, RealTime: {_gameTiming.RealTime:hh\:mm\:ss\.ff}, CurFrame: {_gameTiming.CurFrame}
ServerTime: {_gameTiming.ServerTime}, TickTimingAdjustment: {_gameTiming.TickTimingAdjustment}";
ServerTime: {_gameTiming.ServerTime}, TickTimingAdjustment: {_gameTiming.TickTimingAdjustment}");
}
}
}

View File

@@ -1,6 +1,7 @@
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Client.UserInterface.CustomControls
{
@@ -8,6 +9,8 @@ namespace Robust.Client.UserInterface.CustomControls
{
private readonly IGameTiming _gameTiming;
private readonly char[] _textBuffer = new char[16];
public FpsCounter(IGameTiming gameTiming)
{
_gameTiming = gameTiming;
@@ -25,7 +28,7 @@ namespace Robust.Client.UserInterface.CustomControls
}
var fps = _gameTiming.FramesPerSecondAvg;
Text = $"FPS: {fps:N0}";
TextMemory = FormatHelpers.FormatIntoMem(_textBuffer, $"FPS: {fps:N0}");
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using JetBrains.Annotations;
using Robust.Shared.Utility;
namespace Robust.Shared.Maths
{
@@ -7,7 +8,7 @@ namespace Robust.Shared.Maths
/// A representation of an angle, in radians.
/// </summary>
[Serializable]
public readonly struct Angle : IApproxEquatable<Angle>, IEquatable<Angle>
public readonly struct Angle : IApproxEquatable<Angle>, IEquatable<Angle>, ISpanFormattable
{
public static Angle Zero { get; } = new();
@@ -291,5 +292,22 @@ namespace Robust.Shared.Maths
{
return $"{Theta} rad";
}
public string ToString(string? format, IFormatProvider? formatProvider)
{
return ToString();
}
public bool TryFormat(
Span<char> destination,
out int charsWritten,
ReadOnlySpan<char> format,
IFormatProvider? provider)
{
return FormatHelpers.TryFormatInto(
destination,
out charsWritten,
$"{Theta} rad");
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Robust.Shared.Utility;
namespace Robust.Shared.Maths
{
@@ -10,7 +11,7 @@ namespace Robust.Shared.Maths
/// </summary>
[Serializable]
[StructLayout(LayoutKind.Explicit)]
public struct Box2 : IEquatable<Box2>, IApproxEquatable<Box2>
public struct Box2 : IEquatable<Box2>, IApproxEquatable<Box2>, ISpanFormattable
{
/// <summary>
/// The X coordinate of the left edge of the box.
@@ -310,11 +311,28 @@ namespace Robust.Shared.Maths
return !(a == b);
}
public override string ToString()
public readonly override string ToString()
{
return $"({Left}, {Bottom}, {Right}, {Top})";
}
public readonly string ToString(string? format, IFormatProvider? formatProvider)
{
return ToString();
}
public readonly bool TryFormat(
Span<char> destination,
out int charsWritten,
ReadOnlySpan<char> format,
IFormatProvider? provider)
{
return FormatHelpers.TryFormatInto(
destination,
out charsWritten,
$"({Left}, {Bottom}, {Right}, {Top})");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Area(in Box2 box)
=> box.Width * box.Height;

View File

@@ -2,6 +2,7 @@ using System;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using Robust.Shared.Utility;
namespace Robust.Shared.Maths
{
@@ -9,7 +10,7 @@ namespace Robust.Shared.Maths
/// This type contains a <see cref="Box2"/> and a rotation <see cref="Angle"/> in world space.
/// </summary>
[Serializable]
public struct Box2Rotated : IEquatable<Box2Rotated>
public struct Box2Rotated : IEquatable<Box2Rotated>, ISpanFormattable
{
public Box2 Box;
public Angle Rotation;
@@ -214,7 +215,24 @@ namespace Robust.Shared.Maths
/// </summary>
public override readonly string ToString()
{
return $"{Box.ToString()}, {Rotation.ToString()}";
return $"{Box}, {Rotation}";
}
public readonly string ToString(string? format, IFormatProvider? formatProvider)
{
return ToString();
}
public readonly bool TryFormat(
Span<char> destination,
out int charsWritten,
ReadOnlySpan<char> format,
IFormatProvider? provider)
{
return FormatHelpers.TryFormatInto(
destination,
out charsWritten,
$"{Box}, {Rotation}");
}
}
}

View File

@@ -1,12 +1,13 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Robust.Shared.Utility;
namespace Robust.Shared.Maths
{
[Serializable]
[StructLayout(LayoutKind.Explicit)]
public struct Box2i : IEquatable<Box2i>
public struct Box2i : IEquatable<Box2i>, ISpanFormattable
{
[FieldOffset(sizeof(int) * 0)] public int Left;
[FieldOffset(sizeof(int) * 1)] public int Bottom;
@@ -136,6 +137,23 @@ namespace Robust.Shared.Maths
return $"({Left}, {Bottom}, {Right}, {Top})";
}
public readonly string ToString(string? format, IFormatProvider? formatProvider)
{
return ToString();
}
public readonly bool TryFormat(
Span<char> destination,
out int charsWritten,
ReadOnlySpan<char> format,
IFormatProvider? provider)
{
return FormatHelpers.TryFormatInto(
destination,
out charsWritten,
$"({Left}, {Bottom}, {Right}, {Top})");
}
/// <summary>
/// Multiplies each side of the box by the scalar.
/// </summary>

View File

@@ -1,4 +1,5 @@
using System;
using Robust.Shared.Utility;
namespace Robust.Shared.Maths
{
@@ -6,7 +7,7 @@ namespace Robust.Shared.Maths
/// Represents a circle with a 2D position and a radius.
/// </summary>
[Serializable]
public struct Circle : IEquatable<Circle>
public struct Circle : IEquatable<Circle>, ISpanFormattable
{
/// <summary>
/// Position of the circle in 2D space.
@@ -108,5 +109,22 @@ namespace Robust.Shared.Maths
{
return $"Circle ({Position.X}, {Position.Y}), {Radius} r";
}
public readonly string ToString(string? format, IFormatProvider? formatProvider)
{
return ToString();
}
public readonly bool TryFormat(
Span<char> destination,
out int charsWritten,
ReadOnlySpan<char> format,
IFormatProvider? provider)
{
return FormatHelpers.TryFormatInto(
destination,
out charsWritten,
$"Circle ({Position.X}, {Position.Y}), {Radius} r");
}
}
}

View File

@@ -31,6 +31,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Xml.Linq;
using JetBrains.Annotations;
using Robust.Shared.Utility;
using SysVector3 = System.Numerics.Vector3;
using SysVector4 = System.Numerics.Vector4;
@@ -46,7 +47,7 @@ namespace Robust.Shared.Maths
/// </summary>
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Color : IEquatable<Color>
public struct Color : IEquatable<Color>, ISpanFormattable
{
/// <summary>
/// The red component of this Color4 structure.
@@ -239,6 +240,23 @@ namespace Robust.Shared.Maths
return $"{{(R, G, B, A) = ({R}, {G}, {B}, {A})}}";
}
public readonly string ToString(string? format, IFormatProvider? formatProvider)
{
return ToString();
}
public readonly bool TryFormat(
Span<char> destination,
out int charsWritten,
ReadOnlySpan<char> format,
IFormatProvider? provider)
{
return FormatHelpers.TryFormatInto(
destination,
out charsWritten,
$"{{(R, G, B, A) = ({R}, {G}, {B}, {A})}}");
}
public readonly Color WithRed(float newR)
{
return new(newR, G, B, A);

View File

@@ -0,0 +1,203 @@
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;
/// <summary>
/// Helpers for dealing with string formatting and related things.
/// </summary>
public static class FormatHelpers
{
/// <summary>
/// Format a string interpolation into a given buffer. If the buffer is not large enough, the result is truncated.
/// </summary>
/// <remarks>
/// Assuming everything you're formatting with implements <see cref="ISpanFormattable"/>, this should be zero-alloc.
/// </remarks>
/// <param name="buffer">The buffer to format into.</param>
/// <param name="handler">String interpolation handler to implement buffer formatting logic.</param>
/// <returns>The amount of chars written into the buffer.</returns>
public static int FormatInto(
Span<char> buffer,
[InterpolatedStringHandlerArgument("buffer")] ref BufferInterpolatedStringHandler handler)
{
return handler.Length;
}
/// <summary>
/// Tries to format a string interpolation into a given buffer.
/// If the buffer is not large enough, the result is truncated.
/// </summary>
/// <remarks>
/// <para>
/// This is intended to be used as an easy implementation of <see cref="ISpanFormattable"/>.
/// </para>
/// <para>
/// Assuming everything you're formatting with implements <see cref="ISpanFormattable"/>,
/// this should be zero-alloc on release.
/// </para>
/// </remarks>
/// <param name="buffer">The buffer to format into.</param>
/// <param name="charsWritten">The amount of chars written into the buffer.</param>
/// <param name="handler">String interpolation handler to implement buffer formatting logic.</param>
/// <returns>False if the formatting failed due to lack of space and was truncated.</returns>
public static bool TryFormatInto(
Span<char> buffer,
out int charsWritten,
[InterpolatedStringHandlerArgument("buffer")] ref BufferInterpolatedStringHandler handler)
{
charsWritten = handler.Length;
return !handler.Truncated;
}
/// <summary>
/// Format a string interpolation into a given memory buffer.
/// If the buffer is not large enough, the result is truncated.
/// </summary>
/// <param name="buffer">The memory buffer to format into.</param>
/// <param name="handler">String interpolation handler to implement buffer formatting logic.</param>
/// <returns>The region of memory filled by the formatting operation.</returns>
public static Memory<char> FormatIntoMem(
Memory<char> buffer,
[InterpolatedStringHandlerArgument("buffer")] ref MemoryBufferInterpolatedStringHandler handler)
{
return buffer[..handler.Handler.Length];
}
/// <summary>
/// Copy the contents of a <see cref="StringBuilder"/> into a memory buffer, giving back the subregion used.
/// If the buffer is not enough space, the result is truncated.
/// </summary>
/// <param name="builder">The <see cref="StringBuilder"/> to copy from</param>
/// <param name="memory">The memory buffer to copy into.</param>
/// <returns>The memory region actually used by the copied data.</returns>
public static Memory<char> BuilderToMemory(StringBuilder builder, Memory<char> memory)
{
var truncLength = Math.Min(builder.Length, memory.Length);
builder.CopyTo(0, memory.Span, truncLength);
return memory[..builder.Length];
}
}
/// <summary>
/// Interpolated string handler used by <see cref="FormatHelpers.FormatInto"/>.
/// </summary>
[InterpolatedStringHandler]
public ref struct BufferInterpolatedStringHandler
{
private Span<char> _buffer;
internal int Length;
internal bool Truncated;
[SuppressMessage("ReSharper", "UnusedParameter.Local")]
public BufferInterpolatedStringHandler(int literalLength, int formattedCount, Span<char> buffer)
{
_buffer = buffer;
Length = 0;
Truncated = false;
}
public void AppendLiteral(string literal)
{
AppendString(literal);
}
public void AppendFormatted(ReadOnlySpan<char> value)
{
AppendString(value);
}
[SuppressMessage("ReSharper", "MergeCastWithTypeCheck")]
public void AppendFormatted<T>(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>(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<char> 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;
}
}
/// <summary>
/// Interpolated string handler used by <see cref="FormatHelpers.FormatIntoMem"/>.
/// </summary>
/// <remarks>
/// Only exists as workaround for https://youtrack.jetbrains.com/issue/RIDER-78472.
/// </remarks>
[InterpolatedStringHandler]
public ref struct MemoryBufferInterpolatedStringHandler
{
public BufferInterpolatedStringHandler Handler;
public MemoryBufferInterpolatedStringHandler(int literalLength, int formattedCount, Memory<char> 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<char> value) => Handler.AppendFormatted(value);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AppendFormatted<T>(T value) => Handler.AppendFormatted(value);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AppendFormatted<T>(T value, string format) => Handler.AppendFormatted(value, format);
}

View File

@@ -29,12 +29,13 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using Robust.Shared.Utility;
namespace Robust.Shared.Maths
{
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Matrix3 : IEquatable<Matrix3>, IApproxEquatable<Matrix3>
public struct Matrix3 : IEquatable<Matrix3>, IApproxEquatable<Matrix3>, ISpanFormattable
{
#region Fields & Access
@@ -1225,6 +1226,25 @@ namespace Robust.Shared.Maths
+ $"|{R2C0}, {R2C1}, {R2C2}|\n";
}
public readonly string ToString(string? format, IFormatProvider? formatProvider)
{
return ToString();
}
public readonly bool TryFormat(
Span<char> destination,
out int charsWritten,
ReadOnlySpan<char> format,
IFormatProvider? provider)
{
return FormatHelpers.TryFormatInto(
destination,
out charsWritten,
$"|{R0C0}, {R0C1}, {R0C2}|\n"
+ $"|{R1C0}, {R1C1}, {R1C2}|\n"
+ $"|{R2C0}, {R2C1}, {R2C2}|\n");
}
#endregion String
}
#pragma warning restore 3019

View File

@@ -28,6 +28,7 @@ SOFTWARE.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Robust.Shared.Utility;
namespace Robust.Shared.Maths
{
@@ -36,7 +37,7 @@ namespace Robust.Shared.Maths
/// </summary>
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Matrix4 : IEquatable<Matrix4>
public struct Matrix4 : IEquatable<Matrix4>, ISpanFormattable
{
#region Fields
@@ -1160,11 +1161,28 @@ namespace Robust.Shared.Maths
/// Returns a System.String that represents the current Matrix44.
/// </summary>
/// <returns></returns>
public override string ToString()
public readonly override string ToString()
{
return $"{Row0}\n{Row1}\n{Row2}\n{Row3}";
}
public readonly string ToString(string? format, IFormatProvider? formatProvider)
{
return ToString();
}
public readonly bool TryFormat(
Span<char> destination,
out int charsWritten,
ReadOnlySpan<char> format,
IFormatProvider? provider)
{
return FormatHelpers.TryFormatInto(
destination,
out charsWritten,
$"{Row0}\n{Row1}\n{Row2}\n{Row3}");
}
#endregion public override string ToString()
#region public override int GetHashCode()

View File

@@ -29,6 +29,7 @@ using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Xml.Serialization;
using Robust.Shared.Utility;
namespace Robust.Shared.Maths
{
@@ -37,7 +38,7 @@ namespace Robust.Shared.Maths
/// </summary>
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Quaternion : IEquatable<Quaternion>
public struct Quaternion : IEquatable<Quaternion>, ISpanFormattable
{
#region Fields
@@ -903,9 +904,26 @@ namespace Robust.Shared.Maths
/// Returns a System.String that represents the current Quaternion.
/// </summary>
/// <returns></returns>
public override string ToString()
public readonly override string ToString()
{
return $"V: {Xyz}, W: {W}";
return $"V: {xyz}, W: {w}";
}
public readonly string ToString(string? format, IFormatProvider? formatProvider)
{
return ToString();
}
public readonly bool TryFormat(
Span<char> destination,
out int charsWritten,
ReadOnlySpan<char> format,
IFormatProvider? provider)
{
return FormatHelpers.TryFormatInto(
destination,
out charsWritten,
$"V: {xyz}, W: {w}");
}
#endregion public override string ToString()

View File

@@ -1,11 +1,12 @@
using System;
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
using Robust.Shared.Utility;
namespace Robust.Shared.Maths
{
[PublicAPI]
public struct Thickness : IEquatable<Thickness>
public struct Thickness : IEquatable<Thickness>, ISpanFormattable
{
public float Left;
public float Top;
@@ -108,11 +109,29 @@ namespace Robust.Shared.Maths
return !left.Equals(in right);
}
public override string ToString()
public readonly override string ToString()
{
return $"{Left},{Top},{Right},{Bottom}";
}
public readonly string ToString(string? format, IFormatProvider? formatProvider)
{
return ToString();
}
public readonly bool TryFormat(
Span<char> destination,
out int charsWritten,
ReadOnlySpan<char> format,
IFormatProvider? provider)
{
return FormatHelpers.TryFormatInto(
destination,
out charsWritten,
$"{Left},{Top},{Right},{Bottom}");
}
public readonly void Deconstruct(out float left, out float top, out float right, out float bottom)
{
left = Left;

View File

@@ -1,6 +1,7 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Robust.Shared.Utility;
namespace Robust.Shared.Maths
{
@@ -10,7 +11,7 @@ namespace Robust.Shared.Maths
/// </summary>
[Serializable]
[StructLayout(LayoutKind.Explicit)]
public struct UIBox2 : IEquatable<UIBox2>
public struct UIBox2 : IEquatable<UIBox2>, ISpanFormattable
{
/// <summary>
/// The X coordinate of the left edge of the box.
@@ -181,5 +182,22 @@ namespace Robust.Shared.Maths
{
return $"({Left}, {Top}, {Right}, {Bottom})";
}
public readonly string ToString(string? format, IFormatProvider? formatProvider)
{
return ToString();
}
public readonly bool TryFormat(
Span<char> destination,
out int charsWritten,
ReadOnlySpan<char> format,
IFormatProvider? provider)
{
return FormatHelpers.TryFormatInto(
destination,
out charsWritten,
$"({Left}, {Top}, {Right}, {Bottom})");
}
}
}

View File

@@ -1,12 +1,13 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Robust.Shared.Utility;
namespace Robust.Shared.Maths
{
[Serializable]
[StructLayout(LayoutKind.Explicit)]
public struct UIBox2i : IEquatable<UIBox2i>
public struct UIBox2i : IEquatable<UIBox2i>, ISpanFormattable
{
[FieldOffset(sizeof(int) * 0)] public int Left;
[FieldOffset(sizeof(int) * 1)] public int Top;
@@ -145,5 +146,22 @@ namespace Robust.Shared.Maths
{
return $"({Left}, {Top}, {Right}, {Bottom})";
}
public readonly string ToString(string? format, IFormatProvider? formatProvider)
{
return ToString();
}
public readonly bool TryFormat(
Span<char> destination,
out int charsWritten,
ReadOnlySpan<char> format,
IFormatProvider? provider)
{
return FormatHelpers.TryFormatInto(
destination,
out charsWritten,
$"({Left}, {Top}, {Right}, {Bottom})");
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Robust.Shared.Utility;
namespace Robust.Shared.Maths
{
@@ -9,7 +10,7 @@ namespace Robust.Shared.Maths
/// </summary>
[StructLayout(LayoutKind.Sequential)]
[Serializable]
public struct Vector2 : IEquatable<Vector2>, IApproxEquatable<Vector2>
public struct Vector2 : IEquatable<Vector2>, IApproxEquatable<Vector2>, ISpanFormattable
{
/// <summary>
/// The X component of the vector.
@@ -382,6 +383,23 @@ namespace Robust.Shared.Maths
return $"({X}, {Y})";
}
public readonly string ToString(string? format, IFormatProvider? formatProvider)
{
return ToString();
}
public readonly bool TryFormat(
Span<char> destination,
out int charsWritten,
ReadOnlySpan<char> format,
IFormatProvider? provider)
{
return FormatHelpers.TryFormatInto(
destination,
out charsWritten,
$"({X}, {Y})");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Vector2 a, Vector2 b)
{

View File

@@ -1,13 +1,14 @@
using System;
using System.Runtime.InteropServices;
using System.Text.Json.Serialization;
using Robust.Shared.Utility;
namespace Robust.Shared.Maths
{
[Serializable]
[StructLayout(LayoutKind.Sequential)]
// ReSharper disable once InconsistentNaming
public struct Vector2i : IEquatable<Vector2i>
public struct Vector2i : IEquatable<Vector2i>, ISpanFormattable
{
public static readonly Vector2i Zero = (0, 0);
public static readonly Vector2i One = (1, 1);
@@ -170,5 +171,22 @@ namespace Robust.Shared.Maths
{
return $"({X}, {Y})";
}
public string ToString(string? format, IFormatProvider? formatProvider)
{
return ToString();
}
public bool TryFormat(
Span<char> destination,
out int charsWritten,
ReadOnlySpan<char> format,
IFormatProvider? provider)
{
return FormatHelpers.TryFormatInto(
destination,
out charsWritten,
$"({X}, {Y})");
}
}
}

View File

@@ -29,6 +29,7 @@ using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Xml.Serialization;
using Robust.Shared.Utility;
namespace Robust.Shared.Maths
{
@@ -40,7 +41,7 @@ namespace Robust.Shared.Maths
/// </remarks>
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Vector3 : IEquatable<Vector3>
public struct Vector3 : IEquatable<Vector3>, ISpanFormattable
{
#region Fields
@@ -1130,11 +1131,28 @@ namespace Robust.Shared.Maths
/// Returns a System.String that represents the current Vector3.
/// </summary>
/// <returns></returns>
public override string ToString()
public readonly override string ToString()
{
return $"({X}, {Y}, {Z})";
}
public readonly string ToString(string? format, IFormatProvider? formatProvider)
{
return ToString();
}
public readonly bool TryFormat(
Span<char> destination,
out int charsWritten,
ReadOnlySpan<char> format,
IFormatProvider? provider)
{
return FormatHelpers.TryFormatInto(
destination,
out charsWritten,
$"({X}, {Y}, {Z})");
}
#endregion
#region public override int GetHashCode()

View File

@@ -28,6 +28,7 @@ using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Xml.Serialization;
using Robust.Shared.Utility;
namespace Robust.Shared.Maths
{
@@ -37,7 +38,7 @@ namespace Robust.Shared.Maths
/// </remarks>
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Vector4 : IEquatable<Vector4>
public struct Vector4 : IEquatable<Vector4>, ISpanFormattable
{
#region Fields
@@ -911,11 +912,28 @@ namespace Robust.Shared.Maths
/// Returns a System.String that represents the current Vector4.
/// </summary>
/// <returns></returns>
public override string ToString()
public readonly override string ToString()
{
return $"({X}, {Y}, {Z}, {W})";
}
public readonly string ToString(string? format, IFormatProvider? formatProvider)
{
return ToString();
}
public readonly bool TryFormat(
Span<char> destination,
out int charsWritten,
ReadOnlySpan<char> format,
IFormatProvider? provider)
{
return FormatHelpers.TryFormatInto(
destination,
out charsWritten,
$"({X}, {Y}, {Z}, {W})");
}
#endregion public override string ToString()
#region public override int GetHashCode()

View File

@@ -4,6 +4,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
@@ -13,7 +14,7 @@ namespace Robust.Shared.GameObjects
/// This can be used by the EntityManager to access an entity
/// </summary>
[Serializable, NetSerializable]
public readonly struct EntityUid : IEquatable<EntityUid>, IComparable<EntityUid>
public readonly struct EntityUid : IEquatable<EntityUid>, IComparable<EntityUid>, ISpanFormattable
{
/// <summary>
/// If this bit is set on a UID, it's client sided.
@@ -141,6 +142,28 @@ namespace Robust.Shared.GameObjects
return _uid.ToString();
}
public string ToString(string? format, IFormatProvider? formatProvider)
{
return ToString();
}
public bool TryFormat(
Span<char> destination,
out int charsWritten,
ReadOnlySpan<char> format,
IFormatProvider? provider)
{
if (IsClientSide())
{
return FormatHelpers.TryFormatInto(
destination,
out charsWritten,
$"c{_uid & ~ClientUid}");
}
return _uid.TryFormat(destination, out charsWritten);
}
/// <inheritdoc />
public int CompareTo(EntityUid other)
{

View File

@@ -4,6 +4,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Robust.Shared.Map
{
@@ -12,7 +13,7 @@ namespace Robust.Shared.Map
/// </summary>
[PublicAPI]
[Serializable, NetSerializable]
public readonly struct EntityCoordinates : IEquatable<EntityCoordinates>
public readonly struct EntityCoordinates : IEquatable<EntityCoordinates>, ISpanFormattable
{
public static readonly EntityCoordinates Invalid = new(EntityUid.Invalid, Vector2.Zero);
@@ -431,5 +432,19 @@ namespace Robust.Shared.Map
{
return $"EntId={EntityId}, X={Position.X:N2}, Y={Position.Y:N2}";
}
public string ToString(string? format, IFormatProvider? formatProvider) => ToString();
public bool TryFormat(
Span<char> destination,
out int charsWritten,
ReadOnlySpan<char> format,
IFormatProvider? provider)
{
return FormatHelpers.TryFormatInto(
destination,
out charsWritten,
$"EntId={EntityId}, X={Position.X:N2}, Y={Position.Y:N2}");
}
}
}

View File

@@ -2,6 +2,7 @@ using System;
using JetBrains.Annotations;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Robust.Shared.Map
{
@@ -10,7 +11,7 @@ namespace Robust.Shared.Map
/// </summary>
[PublicAPI]
[Serializable, NetSerializable]
public readonly struct MapCoordinates : IEquatable<MapCoordinates>
public readonly struct MapCoordinates : IEquatable<MapCoordinates>, ISpanFormattable
{
public static readonly MapCoordinates Nullspace = new(Vector2.Zero, MapId.Nullspace);
@@ -60,6 +61,23 @@ namespace Robust.Shared.Map
return $"Map={MapId}, X={Position.X:N2}, Y={Position.Y:N2}";
}
public string ToString(string? format, IFormatProvider? formatProvider)
{
return ToString();
}
public bool TryFormat(
Span<char> destination,
out int charsWritten,
ReadOnlySpan<char> format,
IFormatProvider? provider)
{
return FormatHelpers.TryFormatInto(
destination,
out charsWritten,
$"Map={MapId}, X={Position.X:N2}, Y={Position.Y:N2}");
}
/// <summary>
/// Checks that these coordinates are within a certain distance of another set.
/// </summary>

View File

@@ -2,6 +2,7 @@
using Robust.Shared.Serialization;
using System;
using JetBrains.Annotations;
using Robust.Shared.Utility;
namespace Robust.Shared.Map
{
@@ -10,7 +11,7 @@ namespace Robust.Shared.Map
/// </summary>
[PublicAPI]
[Serializable, NetSerializable]
public readonly struct ScreenCoordinates : IEquatable<ScreenCoordinates>
public readonly struct ScreenCoordinates : IEquatable<ScreenCoordinates>, ISpanFormattable
{
/// <summary>
/// Position on the rendering screen.
@@ -63,6 +64,23 @@ namespace Robust.Shared.Map
return $"({Position.X}, {Position.Y}, W{Window.Value})";
}
public string ToString(string? format, IFormatProvider? formatProvider)
{
return ToString();
}
public bool TryFormat(
Span<char> destination,
out int charsWritten,
ReadOnlySpan<char> format,
IFormatProvider? provider)
{
return FormatHelpers.TryFormatInto(
destination,
out charsWritten,
$"({Position.X}, {Position.Y}, W{Window.Value})");
}
/// <inheritdoc />
public bool Equals(ScreenCoordinates other)
{

View File

@@ -1,5 +1,6 @@
using System;
using JetBrains.Annotations;
using Robust.Shared.Utility;
namespace Robust.Shared.Map;
@@ -7,7 +8,7 @@ namespace Robust.Shared.Map;
/// This structure contains the data for an individual Tile in a <c>MapGrid</c>.
/// </summary>
[PublicAPI, Serializable]
public readonly struct Tile : IEquatable<Tile>
public readonly struct Tile : IEquatable<Tile>, ISpanFormattable
{
/// <summary>
/// Internal type ID of this tile.
@@ -96,6 +97,23 @@ public readonly struct Tile : IEquatable<Tile>
return $"Tile {TypeId}, {Flags}, {Variant}";
}
public string ToString(string? format, IFormatProvider? formatProvider)
{
return ToString();
}
public bool TryFormat(
Span<char> destination,
out int charsWritten,
ReadOnlySpan<char> format,
IFormatProvider? provider)
{
return FormatHelpers.TryFormatInto(
destination,
out charsWritten,
$"Tile {TypeId}, {Flags}, {Variant}");
}
/// <inheritdoc />
public bool Equals(Tile other)
{

View File

@@ -2,6 +2,7 @@
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
namespace Robust.Shared.Map
{
@@ -9,7 +10,7 @@ namespace Robust.Shared.Map
/// All of the information needed to reference a tile in the game.
/// </summary>
[PublicAPI]
public readonly struct TileRef : IEquatable<TileRef>
public readonly struct TileRef : IEquatable<TileRef>, ISpanFormattable
{
public static TileRef Zero => new(GridId.Invalid, EntityUid.Invalid, Vector2i.Zero, Tile.Empty);
@@ -75,6 +76,23 @@ namespace Robust.Shared.Map
return $"TileRef: {X},{Y} ({Tile})";
}
public string ToString(string? format, IFormatProvider? formatProvider)
{
return ToString();
}
public bool TryFormat(
Span<char> destination,
out int charsWritten,
ReadOnlySpan<char> format,
IFormatProvider? provider)
{
return FormatHelpers.TryFormatInto(
destination,
out charsWritten,
$"TileRef: {X},{Y} ({Tile})");
}
/// <inheritdoc />
public bool Equals(TileRef other)
{