fix: Adds ISpanFormattable to Geometry structs (#1231)

* Adds ISpanFormattable to geometry structs and makes ToString() near-zero-allocation.
* Adds IEquatable, and Parsable to Rectangle3D to get it in-line with the other structs.

Closes #1067
This commit is contained in:
Kamron Batman 2022-11-06 17:37:30 -08:00 committed by GitHub
parent c75bde55da
commit d7d914df6c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 721 additions and 195 deletions

View file

@ -1,17 +1,68 @@
using System;
using Xunit;
namespace Server.Tests
namespace Server.Tests;
public class Point2DTests
{
public class Point2DTests
[Fact]
public void TestPoint2DToString()
{
[Fact]
public void TestToString()
{
Assert.Equal("(0, 0)", new Point2D(0, 0).ToString());
Assert.Equal("(1, 1)", new Point2D(1, 1).ToString());
Assert.Equal($"({Int32.MaxValue}, {Int32.MaxValue})", new Point2D(Int32.MaxValue, Int32.MaxValue).ToString());
Assert.Equal($"({Int32.MinValue}, {Int32.MinValue})", new Point2D(Int32.MinValue, Int32.MinValue).ToString());
}
Assert.Equal("(0, 0)", new Point2D(0, 0).ToString());
Assert.Equal("(1, 1)", new Point2D(1, 1).ToString());
Assert.Equal($"({int.MaxValue}, {int.MaxValue})", new Point2D(int.MaxValue, int.MaxValue).ToString());
Assert.Equal($"({int.MinValue}, {int.MaxValue})", new Point2D(int.MinValue, int.MaxValue).ToString());
Assert.Equal($"({int.MinValue}, {int.MinValue})", new Point2D(int.MinValue, int.MinValue).ToString());
Assert.Equal($"({int.MaxValue}, {int.MinValue})", new Point2D(int.MaxValue, int.MinValue).ToString());
}
[Fact]
public void TestPoint2DTryFormatSucceeds()
{
char[] array = new char[128];
var p1 = new Point2D(0, 0);
Assert.True(p1.TryFormat(array, out var cp1, null, null));
Assert.Equal(6, cp1);
Array.Clear(array);
var p2 = new Point2D(1, 1);
Assert.True(p2.TryFormat(array, out var cp2, null, null));
Assert.Equal(6, cp2);
Array.Clear(array);
var p3 = new Point2D(int.MaxValue, int.MaxValue);
Assert.True(p3.TryFormat(array, out var cp3, null, null));
Assert.Equal(4 + 20, cp3);
Array.Clear(array);
var p4 = new Point2D(int.MinValue, int.MinValue);
Assert.True(p4.TryFormat(array, out var cp4, null, null));
Assert.Equal(4 + 22, cp4);
}
[Fact]
public void TestPoint2DTryFormatFails()
{
char[] array = new char[1];
var p1 = new Point2D(0, 0);
Assert.False(p1.TryFormat(array, out var cp1, null, null));
Assert.Equal(0, cp1);
Array.Clear(array);
var p2 = new Point2D(1, 1);
Assert.False(p2.TryFormat(array, out var cp2, null, null));
Assert.Equal(0, cp2);
Array.Clear(array);
var p3 = new Point2D(int.MaxValue, int.MaxValue);
Assert.False(p3.TryFormat(array, out var cp3, null, null));
Assert.Equal(0, cp3);
Array.Clear(array);
var p4 = new Point2D(int.MinValue, int.MinValue);
Assert.False(p4.TryFormat(array, out var cp4, null, null));
Assert.Equal(0, cp4);
}
}

View file

@ -0,0 +1,72 @@
using System;
using Xunit;
namespace Server.Tests;
public class Point3DTests
{
[Fact]
public void TestPoint3DToString()
{
const int max = int.MaxValue;
const int min = int.MinValue;
Assert.Equal("(0, 0, 0)", new Point3D(0, 0, 0).ToString());
Assert.Equal("(1, 1, 1)", new Point3D(1, 1, 1).ToString());
Assert.Equal($"({max}, {max}, {max})", new Point3D(max, max, max).ToString());
Assert.Equal($"({min}, {min}, {min})", new Point3D(min, min, min).ToString());
}
[Fact]
public void TestPoint3DTryFormatSucceeds()
{
const int max = int.MaxValue;
const int min = int.MinValue;
char[] array = new char[128];
var p1 = new Point3D(0, 0, 0);
Assert.True(p1.TryFormat(array, out var cp1, null, null));
Assert.Equal(6 + 3, cp1);
Array.Clear(array);
var p2 = new Point3D(1, 1, 1);
Assert.True(p2.TryFormat(array, out var cp2, null, null));
Assert.Equal(6 + 3, cp2);
Array.Clear(array);
var p3 = new Point3D(max, max, max);
Assert.True(p3.TryFormat(array, out var cp3, null, null));
Assert.Equal(6 + 30, cp3);
Array.Clear(array);
var p4 = new Point3D(min, min, min);
Assert.True(p4.TryFormat(array, out var cp4, null, null));
Assert.Equal(6 + 33, cp4);
}
[Fact]
public void TestPoint3DTryFormatFails()
{
const int max = int.MaxValue;
const int min = int.MinValue;
char[] array = new char[1];
var p1 = new Point3D(0, 0, 0);
Assert.False(p1.TryFormat(array, out var cp1, null, null));
Assert.Equal(0, cp1);
Array.Clear(array);
var p2 = new Point3D(1, 1, 1);
Assert.False(p2.TryFormat(array, out var cp2, null, null));
Assert.Equal(0, cp2);
Array.Clear(array);
var p3 = new Point3D(max, max, max);
Assert.False(p3.TryFormat(array, out var cp3, null, null));
Assert.Equal(0, cp3);
Array.Clear(array);
var p4 = new Point3D(min, min, min);
Assert.False(p4.TryFormat(array, out var cp4, null, null));
Assert.Equal(0, cp4);
}
}

View file

@ -0,0 +1,72 @@
using System;
using Xunit;
namespace Server.Tests;
public class Rectangle2DTests
{
[Fact]
public void TestRectangle2DToString()
{
const int max = int.MaxValue;
const int min = int.MinValue;
Assert.Equal("(0, 0)+(0, 0)", new Rectangle2D(0, 0, 0, 0).ToString());
Assert.Equal("(1, 1)+(1, 1)", new Rectangle2D(1, 1, 1, 1).ToString());
Assert.Equal($"({max}, {max})+({max}, {max})", new Rectangle2D(max, max, max, max).ToString());
Assert.Equal($"({min}, {min})+({min}, {min})", new Rectangle2D(min, min, min, min).ToString());
}
[Fact]
public void TestRectangle2DTryFormatSucceeds()
{
const int max = int.MaxValue;
const int min = int.MinValue;
char[] array = new char[128];
var p1 = new Rectangle2D(0, 0, 0, 0);
Assert.True(p1.TryFormat(array, out var cp1, null, null));
Assert.Equal(9 + 4, cp1);
Array.Clear(array);
var p2 = new Rectangle2D(1, 1, 1, 1);
Assert.True(p2.TryFormat(array, out var cp2, null, null));
Assert.Equal(9 + 4, cp2);
Array.Clear(array);
var p3 = new Rectangle2D(max, max, max, max);
Assert.True(p3.TryFormat(array, out var cp3, null, null));
Assert.Equal(9 + 4 * 10, cp3);
Array.Clear(array);
var p4 = new Rectangle2D(min, min, min, min);
Assert.True(p4.TryFormat(array, out var cp4, null, null));
Assert.Equal(9 + 4 * 11, cp4);
}
[Fact]
public void TestRectangle2DTryFormatFails()
{
const int max = int.MaxValue;
const int min = int.MinValue;
char[] array = new char[1];
var p1 = new Rectangle2D(0, 0, 0, 0);
Assert.False(p1.TryFormat(array, out var cp1, null, null));
Assert.Equal(0, cp1);
Array.Clear(array);
var p2 = new Rectangle2D(1, 1, 1, 1);
Assert.False(p2.TryFormat(array, out var cp2, null, null));
Assert.Equal(0, cp2);
Array.Clear(array);
var p3 = new Rectangle2D(max, max, max, max);
Assert.False(p3.TryFormat(array, out var cp3, null, null));
Assert.Equal(0, cp3);
Array.Clear(array);
var p4 = new Rectangle2D(min, min, min, min);
Assert.False(p4.TryFormat(array, out var cp4, null, null));
Assert.Equal(0, cp4);
}
}

View file

@ -0,0 +1,72 @@
using System;
using Xunit;
namespace Server.Tests;
public class Rectangle3DTests
{
[Fact]
public void TestRectangle3DToString()
{
const int max = int.MaxValue;
const int min = int.MinValue;
Assert.Equal("(0, 0, 0)+(0, 0, 0)", new Rectangle3D(0, 0, 0, 0, 0, 0).ToString());
Assert.Equal("(1, 1, 1)+(1, 1, 1)", new Rectangle3D(1, 1, 1, 1, 1, 1).ToString());
Assert.Equal($"({max}, {max}, {max})+({max}, {max}, {max})", new Rectangle3D(max, max, max, max, max, max).ToString());
Assert.Equal($"({min}, {min}, {min})+({min}, {min}, {min})", new Rectangle3D(min, min, min, min, min, min).ToString());
}
[Fact]
public void TestRectangle3DTryFormatSucceeds()
{
const int max = int.MaxValue;
const int min = int.MinValue;
char[] array = new char[128];
var p1 = new Rectangle3D(0, 0, 0, 0, 0, 0);
Assert.True(p1.TryFormat(array, out var cp1, null, null));
Assert.Equal(13 + 6, cp1);
Array.Clear(array);
var p2 = new Rectangle3D(1, 1, 1, 1, 1, 1);
Assert.True(p2.TryFormat(array, out var cp2, null, null));
Assert.Equal(13 + 6, cp2);
Array.Clear(array);
var p3 = new Rectangle3D(max, max, max, max, max, max);
Assert.True(p3.TryFormat(array, out var cp3, null, null));
Assert.Equal(13 + 6 * 10, cp3);
Array.Clear(array);
var p4 = new Rectangle3D(min, min, min, min, min, min);
Assert.True(p4.TryFormat(array, out var cp4, null, null));
Assert.Equal(13 + 6 * 11, cp4);
}
[Fact]
public void TestRectangle3DTryFormatFails()
{
const int max = int.MaxValue;
const int min = int.MinValue;
char[] array = new char[1];
var p1 = new Rectangle3D(0, 0, 0, 0, 0, 0);
Assert.False(p1.TryFormat(array, out var cp1, null, null));
Assert.Equal(0, cp1);
Array.Clear(array);
var p2 = new Rectangle3D(1, 1, 1, 1, 1, 1);
Assert.False(p2.TryFormat(array, out var cp2, null, null));
Assert.Equal(0, cp2);
Array.Clear(array);
var p3 = new Rectangle3D(max, max, max, max, max, max);
Assert.False(p3.TryFormat(array, out var cp3, null, null));
Assert.Equal(0, cp3);
Array.Clear(array);
var p4 = new Rectangle3D(min, min, min, min, min, min);
Assert.False(p4.TryFormat(array, out var cp4, null, null));
Assert.Equal(0, cp4);
}
}

View file

@ -0,0 +1,105 @@
using System;
using Xunit;
namespace Server.Tests;
public sealed class WorldLocationTests
{
private static Map CreateMap(string name) => new(0, 0, 0, 1, 1, 0, name, MapRules.Internal);
private const string _nullMap = "(-null-)";
private const string _superLongMapName =
"Some super long map name that nobody should be using, but probably someone will just because you can!";
[Fact]
public void TestWorldLocationToString()
{
const string mapName = "Internal Map";
var map = CreateMap(mapName);
const int max = int.MaxValue;
const int min = int.MinValue;
Assert.Equal($"(0, 0, 0) [{mapName}]", new WorldLocation(0, 0, 0, map).ToString());
Assert.Equal($"(1, 1, 1) [{mapName}]", new WorldLocation(1, 1, 1, map).ToString());
Assert.Equal($"(0, 0, 0) [{_nullMap}]", new WorldLocation(0, 0, 0, null).ToString());
Assert.Equal($"(1, 1, 1) [{_nullMap}]", new WorldLocation(1, 1, 1, null).ToString());
Assert.Equal($"({max}, {max}, {max}) [{mapName}]", new WorldLocation(max, max, max, map).ToString());
Assert.Equal($"({min}, {min}, {min}) [{mapName}]", new WorldLocation(min, min, min, map).ToString());
Assert.Equal($"({max}, {max}, {max}) [{_nullMap}]", new WorldLocation(max, max, max, null).ToString());
Assert.Equal($"({min}, {min}, {min}) [{_nullMap}]", new WorldLocation(min, min, min, null).ToString());
map = CreateMap(_superLongMapName);
Assert.Equal($"({max}, {max}, {max}) [{_superLongMapName}]", new WorldLocation(max, max, max, map).ToString());
Assert.Equal($"({min}, {min}, {min}) [{_superLongMapName}]", new WorldLocation(min, min, min, map).ToString());
}
[Theory]
[InlineData(null)]
[InlineData("Internal Map")]
[InlineData(_superLongMapName)]
public void TestWorldLocationTryFormatSucceeds(string mapName)
{
var map = mapName != null ? CreateMap(mapName) : null;
var mapNameLength = mapName?.Length ?? _nullMap.Length;
const int max = int.MaxValue;
const int min = int.MinValue;
char[] array = new char[128];
var p1 = new WorldLocation(0, 0, 0, map);
Assert.True(p1.TryFormat(array, out var cp1, null, null));
Assert.Equal(9 + 3 + mapNameLength, cp1);
Array.Clear(array);
var p2 = new WorldLocation(1, 1, 1, map);
Assert.True(p2.TryFormat(array, out var cp2, null, null));
Assert.Equal(9 + 3 + mapNameLength, cp2);
Array.Clear(array);
array = new char[256]; // We need a bigger buffer!
var p3 = new WorldLocation(max, max, max, map);
Assert.True(p3.TryFormat(array, out var cp3, null, null));
Assert.Equal(9 + 30 + mapNameLength, cp3);
Array.Clear(array);
var p4 = new WorldLocation(min, min, min, map);
Assert.True(p4.TryFormat(array, out var cp4, null, null));
Assert.Equal(9 + 33 + mapNameLength, cp4);
}
[Theory]
[InlineData(null)]
[InlineData("Internal Map")]
public void TestWorldLocationTryFormatFails(string mapName)
{
var map = mapName != null ? CreateMap(mapName) : null;
const int max = int.MaxValue;
const int min = int.MinValue;
char[] array = new char[1];
var p1 = new WorldLocation(0, 0, 0, map);
Assert.False(p1.TryFormat(array, out var cp1, null, null));
Assert.Equal(0, cp1);
Array.Clear(array);
var p2 = new WorldLocation(1, 1, 1, map);
Assert.False(p2.TryFormat(array, out var cp2, null, null));
Assert.Equal(0, cp2);
Array.Clear(array);
var p3 = new WorldLocation(max, max, max, map);
Assert.False(p3.TryFormat(array, out var cp3, null, null));
Assert.Equal(0, cp3);
Array.Clear(array);
var p4 = new WorldLocation(min, min, min, map);
Assert.False(p4.TryFormat(array, out var cp4, null, null));
Assert.Equal(0, cp4);
}
}

View file

@ -113,7 +113,8 @@ public struct Point2D
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider provider)
=> destination.TryWrite(provider, $"({m_X}, {m_Y})", out charsWritten);
public override string ToString() {
public override string ToString()
{
// Maximum number of characters that are needed to represent this:
// 4 characters for (, )
// Up to 11 characters to represent each integer

View file

@ -21,7 +21,7 @@ namespace Server;
[Parsable]
public struct Point3D
: IPoint3D, IComparable<Point3D>, IComparable<IPoint3D>, IEquatable<object>, IEquatable<Point3D>,
IEquatable<IPoint3D>
IEquatable<IPoint3D>, ISpanFormattable
{
internal int m_X;
internal int m_Y;
@ -70,8 +70,6 @@ public struct Point3D
m_Z = z;
}
public override string ToString() => $"({m_X}, {m_Y}, {m_Z})";
public bool Equals(Point3D other) => m_X == other.m_X && m_Y == other.m_Y && m_Z == other.m_Z;
public bool Equals(IPoint3D other) =>
@ -164,4 +162,25 @@ public struct Point3D
return m_Z.CompareTo(other.Z);
}
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider provider)
=> destination.TryWrite(provider, $"({m_X}, {m_Y}, {m_Z})", out charsWritten);
public override string ToString()
{
// Maximum number of characters that are needed to represent this:
// 6 characters for (, , )
// Up to 11 characters to represent each integer
const int maxLength = 6 + 11 * 3;
Span<char> span = stackalloc char[maxLength];
TryFormat(span, out var charsWritten, null, null);
return span[..charsWritten].ToString();
}
public string ToString(string format, IFormatProvider formatProvider)
{
// format and formatProvider are not doing anything right now, so use the
// default ToString implementation.
return ToString();
}
}

View file

@ -20,37 +20,104 @@ namespace Server;
[NoSort]
[Parsable]
[PropertyObject]
public struct Rectangle2D
public struct Rectangle2D : IEquatable<Rectangle2D>, ISpanFormattable
{
public bool Equals(Rectangle2D other) => m_Start == other.m_Start && m_End == other.m_End;
public static bool operator ==(Rectangle2D l, Rectangle2D r) => l.m_Start == r.m_Start && l.m_End == r.m_End;
public static bool operator !=(Rectangle2D l, Rectangle2D r) => l.m_Start != r.m_Start || l.m_End != r.m_End;
public override int GetHashCode() => HashCode.Combine(m_Start, m_End);
private Point2D m_Start;
private Point2D m_End;
private Point2D _start;
private Point2D _end;
public Rectangle2D(Point2D start, Point2D end)
{
m_Start = start;
m_End = end;
_start = start;
_end = end;
}
public Rectangle2D(int x, int y, int width, int height)
{
m_Start = new Point2D(x, y);
m_End = new Point2D(x + width, y + height);
_start = new Point2D(x, y);
_end = new Point2D(x + width, y + height);
}
public void Set(int x, int y, int width, int height)
{
m_Start = new Point2D(x, y);
m_End = new Point2D(x + width, y + height);
_start = new Point2D(x, y);
_end = new Point2D(x + width, y + height);
}
[CommandProperty(AccessLevel.Counselor)]
public Point2D Start
{
get => _start;
set => _start = value;
}
[CommandProperty(AccessLevel.Counselor)]
public Point2D End
{
get => _end;
set => _end = value;
}
[CommandProperty(AccessLevel.Counselor)]
public int X
{
get => _start.m_X;
set => _start.m_X = value;
}
[CommandProperty(AccessLevel.Counselor)]
public int Y
{
get => _start.m_Y;
set => _start.m_Y = value;
}
[CommandProperty(AccessLevel.Counselor)]
public int Width
{
get => _end.m_X - _start.m_X;
set => _end.m_X = _start.m_X + value;
}
[CommandProperty(AccessLevel.Counselor)]
public int Height
{
get => _end.m_Y - _start.m_Y;
set => _end.m_Y = _start.m_Y + value;
}
public void MakeHold(Rectangle2D r)
{
if (r._start.m_X < _start.m_X)
{
_start.m_X = r._start.m_X;
}
if (r._start.m_Y < _start.m_Y)
{
_start.m_Y = r._start.m_Y;
}
if (r._end.m_X > _end.m_X)
{
_end.m_X = r._end.m_X;
}
if (r._end.m_Y > _end.m_Y)
{
_end.m_Y = r._end.m_Y;
}
}
public bool Equals(Rectangle2D other) => _start == other._start && _end == other._end;
public override bool Equals(object obj) => obj is Rectangle2D other && Equals(other);
public override int GetHashCode() => HashCode.Combine(_start, _end);
public static bool operator ==(Rectangle2D l, Rectangle2D r) => l._start == r._start && l._end == r._end;
public static bool operator !=(Rectangle2D l, Rectangle2D r) => l._start != r._start || l._end != r._end;
public static Rectangle2D Parse(string value)
{
var start = value.IndexOfOrdinal('(');
@ -76,79 +143,33 @@ public struct Rectangle2D
return new Rectangle2D(x, y, w, h);
}
[CommandProperty(AccessLevel.Counselor)]
public Point2D Start
public bool Contains(Point3D p) =>
_start.m_X <= p.m_X && _start.m_Y <= p.m_Y && _end.m_X > p.m_X && _end.m_Y > p.m_Y;
public bool Contains(Point2D p) =>
_start.m_X <= p.m_X && _start.m_Y <= p.m_Y && _end.m_X > p.m_X && _end.m_Y > p.m_Y;
public bool Contains(int x, int y) =>
_start.m_X <= x && _start.m_Y <= y && _end.m_X > x && _end.m_Y > y;
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider provider)
=> destination.TryWrite(provider, $"({X}, {Y})+({Width}, {Height})", out charsWritten);
public override string ToString()
{
get => m_Start;
set => m_Start = value;
// Maximum number of characters that are needed to represent this:
// 9 characters for (, )+(, )
// Up to 11 characters to represent each integer
const int maxLength = 9 + 11 * 4;
Span<char> span = stackalloc char[maxLength];
TryFormat(span, out var charsWritten, null, null);
return span[..charsWritten].ToString();
}
[CommandProperty(AccessLevel.Counselor)]
public Point2D End
public string ToString(string format, IFormatProvider formatProvider)
{
get => m_End;
set => m_End = value;
// format and formatProvider are not doing anything right now, so use the
// default ToString implementation.
return ToString();
}
[CommandProperty(AccessLevel.Counselor)]
public int X
{
get => m_Start.m_X;
set => m_Start.m_X = value;
}
[CommandProperty(AccessLevel.Counselor)]
public int Y
{
get => m_Start.m_Y;
set => m_Start.m_Y = value;
}
[CommandProperty(AccessLevel.Counselor)]
public int Width
{
get => m_End.m_X - m_Start.m_X;
set => m_End.m_X = m_Start.m_X + value;
}
[CommandProperty(AccessLevel.Counselor)]
public int Height
{
get => m_End.m_Y - m_Start.m_Y;
set => m_End.m_Y = m_Start.m_Y + value;
}
public void MakeHold(Rectangle2D r)
{
if (r.m_Start.m_X < m_Start.m_X)
{
m_Start.m_X = r.m_Start.m_X;
}
if (r.m_Start.m_Y < m_Start.m_Y)
{
m_Start.m_Y = r.m_Start.m_Y;
}
if (r.m_End.m_X > m_End.m_X)
{
m_End.m_X = r.m_End.m_X;
}
if (r.m_End.m_Y > m_End.m_Y)
{
m_End.m_Y = r.m_End.m_Y;
}
}
public readonly bool Contains(Point3D p) =>
m_Start.m_X <= p.m_X && m_Start.m_Y <= p.m_Y && m_End.m_X > p.m_X && m_End.m_Y > p.m_Y;
public readonly bool Contains(Point2D p) =>
m_Start.m_X <= p.m_X && m_Start.m_Y <= p.m_Y && m_End.m_X > p.m_X && m_End.m_Y > p.m_Y;
public readonly bool Contains(int x, int y) =>
m_Start.m_X <= x && m_Start.m_Y <= y && m_End.m_X > x && m_End.m_Y > y;
public override string ToString() => $"({X}, {Y})+({Width}, {Height})";
}

View file

@ -13,129 +13,164 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Buffers;
namespace Server;
[NoSort]
[Parsable]
[PropertyObject]
public struct Rectangle3D
public struct Rectangle3D : IEquatable<Rectangle3D>, ISpanFormattable
{
private Point3D m_Start;
private Point3D m_End;
private Point3D _start;
private Point3D _end;
public Rectangle3D(Point3D start, Point3D end)
{
m_Start = start;
m_End = end;
_start = start;
_end = end;
}
public Rectangle3D(int x, int y, int z, int width, int height, int depth)
{
m_Start = new Point3D(x, y, z);
m_End = new Point3D(x + width, y + height, z + depth);
_start = new Point3D(x, y, z);
_end = new Point3D(x + width, y + height, z + depth);
}
[CommandProperty(AccessLevel.Counselor)]
public Point3D Start
{
get => m_Start;
set => m_Start = value;
get => _start;
set => _start = value;
}
[CommandProperty(AccessLevel.Counselor)]
public Point3D End
{
get => m_End;
set => m_End = value;
get => _end;
set => _end = value;
}
[CommandProperty(AccessLevel.Counselor)]
public int X
{
get => m_Start.m_X;
set => m_Start.m_X = value;
get => _start.m_X;
set => _start.m_X = value;
}
[CommandProperty(AccessLevel.Counselor)]
public int Y
{
get => m_Start.m_Y;
set => m_Start.m_Y = value;
get => _start.m_Y;
set => _start.m_Y = value;
}
[CommandProperty(AccessLevel.Counselor)]
public int Z
{
get => m_Start.m_Z;
set => m_Start.m_Z = value;
get => _start.m_Z;
set => _start.m_Z = value;
}
[CommandProperty(AccessLevel.Counselor)]
public int Width => m_End.X - m_Start.X;
public int Width => _end.X - _start.X;
[CommandProperty(AccessLevel.Counselor)]
public int Height => m_End.Y - m_Start.Y;
public int Height => _end.Y - _start.Y;
[CommandProperty(AccessLevel.Counselor)]
public int Depth => m_End.Z - m_Start.Z;
public int Depth => _end.Z - _start.Z;
public bool Equals(Rectangle3D other) => _start == other._start && _end == other._end;
public override bool Equals(object obj) => obj is Rectangle3D other && Equals(other);
public static bool operator ==(Rectangle3D l, Rectangle3D r) => l._start == r._start && l._end == r._end;
public static bool operator !=(Rectangle3D l, Rectangle3D r) => l._start != r._start || l._end != r._end;
public override int GetHashCode() => HashCode.Combine(_start, _end);
public void MakeHold(Rectangle3D r)
{
if (r.m_Start.m_X < m_Start.m_X)
if (r._start.m_X < _start.m_X)
{
m_Start.m_X = r.m_Start.m_X;
_start.m_X = r._start.m_X;
}
if (r.m_Start.m_Y < m_Start.m_Y)
if (r._start.m_Y < _start.m_Y)
{
m_Start.m_Y = r.m_Start.m_Y;
_start.m_Y = r._start.m_Y;
}
if (r.m_Start.m_Z < m_Start.m_Z)
if (r._start.m_Z < _start.m_Z)
{
m_Start.m_Z = r.m_Start.m_Z;
_start.m_Z = r._start.m_Z;
}
if (r.m_End.m_X > m_End.m_X)
if (r._end.m_X > _end.m_X)
{
m_End.m_X = r.m_End.m_X;
_end.m_X = r._end.m_X;
}
if (r.m_End.m_Y > m_End.m_Y)
if (r._end.m_Y > _end.m_Y)
{
m_End.m_Y = r.m_End.m_Y;
_end.m_Y = r._end.m_Y;
}
if (r.m_End.m_Z < m_End.m_Z)
if (r._end.m_Z < _end.m_Z)
{
m_End.m_Z = r.m_End.m_Z;
_end.m_Z = r._end.m_Z;
}
}
public bool Contains(Point3D p) =>
p.m_X >= m_Start.m_X
&& p.m_X < m_End.m_X
&& p.m_Y >= m_Start.m_Y
&& p.m_Y < m_End.m_Y
&& p.m_Z >= m_Start.m_Z
&& p.m_Z < m_End.m_Z;
p.m_X >= _start.m_X
&& p.m_X < _end.m_X
&& p.m_Y >= _start.m_Y
&& p.m_Y < _end.m_Y
&& p.m_Z >= _start.m_Z
&& p.m_Z < _end.m_Z;
public bool Contains(Point2D p) =>
p.m_X >= m_Start.m_X
&& p.m_X < m_End.m_X
&& p.m_Y >= m_Start.m_Y
&& p.m_Y < m_End.m_Y;
p.m_X >= _start.m_X
&& p.m_X < _end.m_X
&& p.m_Y >= _start.m_Y
&& p.m_Y < _end.m_Y;
public bool Contains(IPoint2D p) =>
p.X >= m_Start.m_X
&& p.X < m_End.m_X
&& p.Y >= m_Start.m_Y
&& p.Y < m_End.m_Y;
p.X >= _start.m_X
&& p.X < _end.m_X
&& p.Y >= _start.m_Y
&& p.Y < _end.m_Y;
public bool Contains(IPoint3D p) =>
p.X >= m_Start.m_X
&& p.X < m_End.m_X
&& p.Y >= m_Start.m_Y
&& p.Y < m_End.m_Y
&& p.Z >= m_Start.m_Z
&& p.Z < m_End.m_Z;
p.X >= _start.m_X
&& p.X < _end.m_X
&& p.Y >= _start.m_Y
&& p.Y < _end.m_Y
&& p.Z >= _start.m_Z
&& p.Z < _end.m_Z;
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider provider)
=> destination.TryWrite(provider, $"({X}, {Y}, {Z})+({Width}, {Height}, {Depth})", out charsWritten);
public override string ToString()
{
// Maximum number of characters that are needed to represent this:
// 13 characters for (, , )+(, , )
// Up to 11 characters to represent each integer
const int maxLength = 13 + 11 * 6;
Span<char> span = stackalloc char[maxLength];
TryFormat(span, out var charsWritten, null, null);
return span[..charsWritten].ToString();
}
public string ToString(string format, IFormatProvider formatProvider)
{
// format and formatProvider are not doing anything right now, so use the
// default ToString implementation.
return ToString();
}
}

View file

@ -14,52 +14,53 @@
*************************************************************************/
using System;
using System.Buffers;
using System.Collections.Generic;
namespace Server;
[Parsable]
public struct WorldLocation
: IPoint3D, IComparable<WorldLocation>, IEquatable<object>, IEquatable<WorldLocation>, IEquatable<IEntity>
public struct WorldLocation : IPoint3D, IComparable<WorldLocation>, IEquatable<WorldLocation>, IEquatable<IEntity>,
ISpanFormattable
{
internal Point3D m_Loc;
internal Map m_Map;
internal Point3D _loc;
internal Map _map;
public static readonly WorldLocation Zero = new(0, 0, 0, Map.Internal);
[CommandProperty(AccessLevel.Counselor)]
public Point3D Location
{
get => m_Loc;
set => m_Loc = value;
get => _loc;
set => _loc = value;
}
[CommandProperty(AccessLevel.Counselor)]
public int X
{
get => m_Loc.m_X;
set => m_Loc.m_X = value;
get => _loc.m_X;
set => _loc.m_X = value;
}
[CommandProperty(AccessLevel.Counselor)]
public int Y
{
get => m_Loc.m_Y;
set => m_Loc.m_Y = value;
get => _loc.m_Y;
set => _loc.m_Y = value;
}
[CommandProperty(AccessLevel.Counselor)]
public int Z
{
get => m_Loc.m_Z;
set => m_Loc.m_Z = value;
get => _loc.m_Z;
set => _loc.m_Z = value;
}
[CommandProperty(AccessLevel.Counselor)]
public Map Map
{
get => m_Map;
set => m_Map = value;
get => _map;
set => _map = value;
}
public WorldLocation(IEntity e) : this(e.Location.X, e.Location.Y, e.Location.Z, e.Map)
@ -80,65 +81,62 @@ public struct WorldLocation
public WorldLocation(int x, int y, int z, Map map)
{
m_Loc.m_X = x;
m_Loc.m_Y = y;
m_Loc.m_Z = z;
m_Map = map;
_loc.m_X = x;
_loc.m_Y = y;
_loc.m_Z = z;
_map = map;
}
public override string ToString() =>
$"({m_Loc.m_X}, {m_Loc.m_Y}, {m_Loc.m_Z}, {m_Map?.ToString() ?? "(-null-)"})";
public bool Equals(WorldLocation other) =>
m_Loc.Equals(other.m_Loc) && m_Map.MapID == other.m_Map.MapID;
_loc.Equals(other._loc) && _map.MapID == other._map.MapID;
public bool Equals(IEntity other) =>
!ReferenceEquals(other, null) && m_Loc == other.Location &&
m_Map.MapID == other.Map.MapID;
!ReferenceEquals(other, null) && _loc == other.Location &&
_map.MapID == other.Map.MapID;
public override bool Equals(object obj) =>
obj is WorldLocation other && Equals(other);
public override int GetHashCode() => HashCode.Combine(m_Loc, m_Map);
public override int GetHashCode() => HashCode.Combine(_loc, _map);
public int CompareTo(WorldLocation other)
{
var locComparison = m_Loc.CompareTo(other.m_Loc);
return locComparison != 0 ? locComparison : Comparer<Map>.Default.Compare(m_Map, other.m_Map);
var locComparison = _loc.CompareTo(other._loc);
return locComparison != 0 ? locComparison : Comparer<Map>.Default.Compare(_map, other._map);
}
public static implicit operator Point3D(WorldLocation worldLocation) => worldLocation.Location;
public static bool operator ==(WorldLocation l, WorldLocation r) =>
l.m_Loc == r.m_Loc && l.m_Map == r.m_Map;
l._loc == r._loc && l._map == r._map;
public static bool operator ==(WorldLocation l, IEntity r) =>
!ReferenceEquals(r, null) && l.m_Loc == r.Location && l.m_Map == r.Map;
!ReferenceEquals(r, null) && l._loc == r.Location && l._map == r.Map;
public static bool operator !=(WorldLocation l, WorldLocation r) => l.m_Loc != r.m_Loc && l.m_Map != r.m_Map;
public static bool operator !=(WorldLocation l, WorldLocation r) => l._loc != r._loc && l._map != r._map;
public static bool operator !=(WorldLocation l, IEntity r) =>
!ReferenceEquals(r, null) && l.m_Loc != r.Location && l.m_Map != r.Map;
!ReferenceEquals(r, null) && l._loc != r.Location && l._map != r.Map;
public static bool operator >(WorldLocation l, WorldLocation r) => l.m_Loc > r.m_Loc && l.m_Map == r.m_Map;
public static bool operator >(WorldLocation l, WorldLocation r) => l._loc > r._loc && l._map == r._map;
public static bool operator >(WorldLocation l, IEntity r) =>
!ReferenceEquals(r, null) && l.m_Loc > r.Location && l.m_Map == r.Map;
!ReferenceEquals(r, null) && l._loc > r.Location && l._map == r.Map;
public static bool operator <(WorldLocation l, WorldLocation r) => l.m_Loc < r.m_Loc && l.m_Map == r.m_Map;
public static bool operator <(WorldLocation l, WorldLocation r) => l._loc < r._loc && l._map == r._map;
public static bool operator <(WorldLocation l, IEntity r) =>
!ReferenceEquals(r, null) && l.m_Loc < r.Location && l.m_Map == r.Map;
!ReferenceEquals(r, null) && l._loc < r.Location && l._map == r.Map;
public static bool operator >=(WorldLocation l, WorldLocation r) => l.m_Loc >= r.m_Loc && l.m_Map == r.m_Map;
public static bool operator >=(WorldLocation l, WorldLocation r) => l._loc >= r._loc && l._map == r._map;
public static bool operator >=(WorldLocation l, IEntity r) =>
!ReferenceEquals(r, null) && l.m_Loc >= r.Location && l.m_Map == r.Map;
!ReferenceEquals(r, null) && l._loc >= r.Location && l._map == r.Map;
public static bool operator <=(WorldLocation l, WorldLocation r) => l.m_Loc <= r.m_Loc && l.m_Map == r.m_Map;
public static bool operator <=(WorldLocation l, WorldLocation r) => l._loc <= r._loc && l._map == r._map;
public static bool operator <=(WorldLocation l, IEntity r) =>
!ReferenceEquals(r, null) && l.m_Loc <= r.Location && l.m_Map == r.Map;
!ReferenceEquals(r, null) && l._loc <= r.Location && l._map == r.Map;
public static WorldLocation Parse(string value)
{
@ -164,4 +162,50 @@ public struct WorldLocation
return new WorldLocation(x, y, z, map);
}
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider provider)
{
if (_map == null)
{
return destination.TryWrite(provider, $"({_loc.m_X}, {_loc.m_Y}, {_loc.m_Z}) [(-null-)]", out charsWritten);
}
return destination.TryWrite(provider, $"({_loc.m_X}, {_loc.m_Y}, {_loc.m_Z}) [{_map}]", out charsWritten);
}
public override string ToString()
{
if (_map == null)
{
// Maximum number of characters that are needed to represent this:
// 9 characters for (, , ) [(-null-)]
const int staticLength = 17;
// Up to 11 characters to represent each integer
const int maxLength = staticLength + 11 * 3;
Span<char> span = stackalloc char[maxLength];
TryFormat(span, out var charsWritten, null, null);
return span[..charsWritten].ToString();
}
else
{
int charsWritten;
char[] array = ArrayPool<char>.Shared.Rent(128);
Span<char> span = array.AsSpan();
while (!TryFormat(span, out charsWritten, null, null))
{
array = ArrayPool<char>.Shared.Rent(array.Length * 2);
span = array.AsSpan();
}
return span[..charsWritten].ToString();
}
}
public string ToString(string format, IFormatProvider formatProvider)
{
// format and formatProvider are not doing anything right now, so use the
// default ToString implementation.
return ToString();
}
}

View file

@ -314,7 +314,7 @@ public static class PooledEnumeration
}
[Parsable]
public sealed class Map : IComparable<Map>
public sealed class Map : IComparable<Map>, ISpanFormattable
{
public const int SectorSize = 16;
public const int SectorShift = 4;
@ -525,8 +525,29 @@ public sealed class Map : IComparable<Map>
return null;
}
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider provider)
{
if (destination.Length >= Name.Length)
{
Name.CopyTo(destination);
charsWritten = Name.Length;
return true;
}
charsWritten = 0;
return false;
}
public override string ToString() => Name;
public string ToString(string format, IFormatProvider formatProvider)
{
// format and formatProvider are not doing anything right now, so use the
// default ToString implementation.
return ToString();
}
public int GetAverageZ(int x, int y)
{
GetAverageZ(x, y, out _, out var avg, out _);

View file

@ -118,9 +118,22 @@ public readonly struct Serial : IComparable<Serial>, IComparable<uint>, IEquatab
public static Serial operator --(Serial l) => (Serial)(l.Value - 1);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override string ToString() => $"{this}";
public override string ToString()
{
// Maximum number of characters that are needed to represent this:
// 2 characters for 0x
// Up to 8 characters to represent the value in hex
Span<char> span = stackalloc char[10];
TryFormat(span, out var charsWritten, null, null);
return span[..charsWritten].ToString();
}
public string ToString(string format, IFormatProvider formatProvider) => ToString();
public string ToString(string format, IFormatProvider formatProvider)
{
// format and formatProvider are not doing anything right now, so use the
// default ToString implementation.
return ToString();
}
public bool TryFormat(
Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider provider