feat: Adds optimized dynamic/static layout gumps (#1652)

# New Gump API
We are pleased to release a new API that is faster, allocates nearly zero memory, and still feels very similar to the original API. The API is broken into 3 types of gumps, dynamic, static with placeholders, and static without placeholders. 

### Dynamic Gumps
These gumps will inherit `DynamicGump` and are meant for gumps that have a dynamic layout. This includes specifying dynamic arguments to HtmlLocalized entries.

## Static Gumps 
Static gumps are those where the function to the build the layout is called only once and cached forever. They can optionally have placeholders. These placeholders allow the developer to specify the string values later, dynamically in a `BuildStrings` method on the gump. If a gump does not have any placeholders, the string entries will also be cached forever.

## Benchmarks
To make sure we were going in the right direction and not wasting time, we took copious benchmarks. Here are the final benchmarks for a really simple gump. 

Note:
* The majority of creating a gump is compressing the layout and the strings. Compressing each section takes ~6,000ns (12us total).

```cs
| Method                         | Mean         | Error        | StdDev       | Median       | Ratio | RatioSD | Gen0   | Allocated | Alloc Ratio |
|------------------------------- |-------------:|-------------:|-------------:|-------------:|------:|--------:|-------:|----------:|------------:|
| OldGump                        | 13,308.29 ns | 1,059.695 ns | 1,883.608 ns | 14,330.72 ns | 1.000 |    0.00 | 0.1526 |    2400 B |        1.00 |
| DynamicLayoutGump              | 13,357.86 ns |   129.144 ns |   226.185 ns | 13,323.60 ns | 1.029 |    0.17 |      - |      48 B |        0.02 |
| StaticLayoutDynamicStringsGump |  6,653.10 ns |    81.815 ns |   143.292 ns |  6,617.45 ns | 0.514 |    0.09 |      - |      40 B |        0.02 |
| StaticLayoutGump               |     86.33 ns |     0.760 ns |     1.350 ns |     86.07 ns | 0.007 |    0.00 | 0.0020 |      32 B |        0.01 |
```

# Non-Breaking Changes
* All gump components in the core have been moved to `Gumps/Legacy`.
* All legacy gumps will still inherit `Gump`, which now inherits `BaseGump`

# Special Thanks

Thank you to @stefanomerotta for considerable contributions/benchmarking/testing to make this effort a reality! We collectively went through over 10 iterations, but it is finally ready.
This commit is contained in:
Kamron Batman 2024-04-25 22:40:30 -07:00 committed by GitHub
parent 1c10b6dbed
commit 65532ea887
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
46 changed files with 2527 additions and 273 deletions

View file

@ -8,7 +8,7 @@ using System.Runtime.CompilerServices;
namespace Server.Buffers;
/// <summary>Provides a handler to interpolate strings which UNSAFELY exposes it's internal character span.</summary>
/// <summary>Provides a handler to interpolate strings which UNSAFELY exposes its internal character span.</summary>
[InterpolatedStringHandler]
public ref struct RawInterpolatedStringHandler
{

View file

@ -0,0 +1,73 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: BaseGump.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using Server.Network;
using System;
using System.Runtime.CompilerServices;
namespace Server.Gumps;
public abstract class BaseGump
{
private static Serial nextSerial = (Serial)1;
public int TypeID { get; protected set; }
public Serial Serial { get; protected set; }
public abstract int Switches { get; }
public abstract int TextEntries { get; }
public int X { get; set; }
public int Y { get; set; }
public BaseGump(int x, int y) : this()
{
X = x;
Y = y;
}
public BaseGump()
{
Serial = nextSerial++;
TypeID = GetTypeId(GetType());
}
public abstract void SendTo(NetState ns);
public virtual void OnResponse(NetState sender, in RelayInfo info)
{
}
public virtual void OnServerClose(NetState owner)
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetTypeId(Type type)
{
unchecked
{
// To use the original .NET Framework deterministic hash code (with really terrible performance)
// change the next line to use HashUtility.GetNetFrameworkHashCode
var hash = (int)HashUtility.ComputeHash32(type?.FullName);
const int primeMulti = 0x108B76F1;
// Virtue Gump
return hash == 461 ? hash * primeMulti : hash;
}
}
}

View file

@ -0,0 +1,77 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: DynamicGump.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Buffers;
using System.IO;
using Server.Network;
namespace Server.Gumps;
public abstract class DynamicGump : BaseGump
{
private static readonly byte[] _packetBuffer = GC.AllocateUninitializedArray<byte>(0x10000);
private int _switches;
private int _textEntries;
public override int Switches => _switches;
public override int TextEntries => _textEntries;
public DynamicGump(int x, int y) : base(x, y)
{
}
protected abstract void BuildLayout(ref DynamicGumpBuilder builder);
public void CreatePacket(ref SpanWriter writer)
{
writer.Write((byte)0xDD); // Packet ID
writer.Seek(2, SeekOrigin.Current);
writer.Write(Serial);
writer.Write(TypeID);
writer.Write(X);
writer.Write(Y);
DynamicGumpBuilder gumpBuilder = new DynamicGumpBuilder();
BuildLayout(ref gumpBuilder);
gumpBuilder.FinalizeLayout();
_switches = gumpBuilder.Switches;
_textEntries = gumpBuilder.TextEntries;
OutgoingGumpPackets.WritePacked(gumpBuilder.LayoutData, ref writer);
writer.Write(gumpBuilder._stringsCount);
OutgoingGumpPackets.WritePacked(gumpBuilder.StringsData, ref writer);
gumpBuilder.Dispose();
writer.WritePacketLength();
}
public override void SendTo(NetState ns)
{
ns.AddGump(this);
var writer = new SpanWriter(_packetBuffer);
CreatePacket(ref writer);
ns.Send(writer.Span);
writer.Dispose();
}
}

View file

@ -0,0 +1,392 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: DynamicGumpBuilder.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
using Server.Buffers;
using Server.Text;
namespace Server.Gumps;
public ref struct DynamicGumpBuilder
{
private static readonly byte[] _staticStringsBuffer = GC.AllocateUninitializedArray<byte>(0x80000);
private byte[] _stringsBuffer;
private int _stringBytesWritten;
internal int _stringsCount;
private GumpLayoutBuilder _gumpBuilder;
public ReadOnlySpan<byte> LayoutData => _gumpBuilder.LayoutData;
public ReadOnlySpan<byte> StringsData => _stringsBuffer.AsSpan(0, _stringBytesWritten);
public int Switches => _gumpBuilder._switches;
public int TextEntries => _gumpBuilder._textEntries;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public DynamicGumpBuilder()
{
_stringsBuffer = _staticStringsBuffer;
_gumpBuilder = new GumpLayoutBuilder();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetNoClose() => _gumpBuilder.SetNoClose();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetNoMove() => _gumpBuilder.SetNoMove();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetNoResize() => _gumpBuilder.SetNoResize();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetNoDispose() => _gumpBuilder.SetNoDispose();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddAlphaRegion(int x, int y, int width, int height) =>
_gumpBuilder.AddAlphaRegion(x, y, width, height);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddBackground(int x, int y, int width, int height, int gumpID) =>
_gumpBuilder.AddBackground(x, y, width, height, gumpID);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddButton(
int x, int y, int normalID, int pressedId, int buttonId, GumpButtonType type = GumpButtonType.Reply, int param = 0
) => _gumpBuilder.AddButton(x, y, normalID, pressedId, buttonId, type, param);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddCheckbox(int x, int y, int inactiveID, int activeID, bool selected, int switchId) =>
_gumpBuilder.AddCheckbox(x, y, inactiveID, activeID, selected, switchId);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddGroup(int groupId) => _gumpBuilder.AddGroup(groupId);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHtml(
int x,
int y,
int width,
int height,
ReadOnlySpan<char> text,
bool background = false,
bool scrollbar = false
)
{
WriteInternalizedString(text);
_gumpBuilder.AddHtml(x, y, width, height, _stringsCount++, background, scrollbar);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHtml(
int x, int y, int width, int height, ref RawInterpolatedStringHandler handler,
bool background = false, bool scrollbar = false
)
{
AddHtml(x, y, width, height, handler.Text, background, scrollbar);
handler.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHtml(
int x,
int y,
int width,
int height,
int color,
ReadOnlySpan<char> text,
bool background = false,
bool scrollbar = false
) => AddHtml(x, y, width, height, color, $"<BASEFONT COLOR=#{color:X6}>{text}</BASEFONT>", background, scrollbar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHtml(
int x,
int y,
int width,
int height,
int color,
ref RawInterpolatedStringHandler handler,
bool background = false,
bool scrollbar = false
)
{
AddHtml(x, y, width, height, color, handler.Text, background, scrollbar);
handler.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHtmlCentered(
int x, int y, int width, int height, ReadOnlySpan<char> text, bool background = false, bool scrollbar = false
) => AddHtml(x, y, width, height, $"<CENTER>{text}</CENTER>", background, scrollbar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHtmlCentered(
int x,
int y,
int width,
int height,
ref RawInterpolatedStringHandler handler,
bool background = false,
bool scrollbar = false
)
{
AddHtml(x, y, width, height, $"<CENTER>{handler.Text}</CENTER>", background, scrollbar);
handler.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHtmlCentered(
int x,
int y,
int width,
int height,
int color,
ReadOnlySpan<char> text,
bool background = false,
bool scrollbar = false
) => AddHtml(x, y, width, height, color, $"<CENTER>{text}</CENTER>", background, scrollbar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHtmlCentered(
int x,
int y,
int width,
int height,
int color,
ref RawInterpolatedStringHandler handler,
bool background = false,
bool scrollbar = false
)
{
AddHtmlCentered(x, y, width, height, color, handler.Text, background, scrollbar);
handler.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHtmlLocalized(
int x, int y, int width, int height, int number, bool background = false, bool scrollbar = false
) => _gumpBuilder.AddHtmlLocalized(x, y, width, height, number, background, scrollbar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHtmlLocalized(
int x, int y, int width, int height, int number, short color, bool background = false, bool scrollbar = false
) => _gumpBuilder.AddHtmlLocalized(x, y, width, height, number, color, background, scrollbar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHtmlLocalized(
int x,
int y,
int width,
int height,
int number,
ReadOnlySpan<char> args,
int color,
bool background = false,
bool scrollbar = false
) => _gumpBuilder.AddHtmlLocalized(x, y, width, height, number, args, color, background, scrollbar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHtmlLocalized(
int x, int y, int width, int height, int number, ref RawInterpolatedStringHandler handler, int color,
bool background = false, bool scrollbar = false
) => _gumpBuilder.AddHtmlLocalized(x, y, width, height, number, ref handler, color, background, scrollbar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddImage(int x, int y, int gumpId, int hue = 0, ReadOnlySpan<char> cls = default) =>
_gumpBuilder.AddImage(x, y, gumpId, hue, cls);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddImage(int x, int y, int gumpId, ref RawInterpolatedStringHandler handler) =>
_gumpBuilder.AddImage(x, y, gumpId, ref handler);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddImage(int x, int y, int gumpId, int hue, ref RawInterpolatedStringHandler handler) =>
_gumpBuilder.AddImage(x, y, gumpId, hue, ref handler);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddImageTiledButton(
int x,
int y,
int normalId,
int pressedId,
int buttonId,
GumpButtonType type,
int param,
int itemId,
int hue,
int width,
int height,
int localizedTooltip = -1
) => _gumpBuilder.AddImageTiledButton(
x, y, normalId, pressedId, buttonId, type, param, itemId, hue, width, height, localizedTooltip
);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddImageTiled(int x, int y, int width, int height, int gumpId) =>
_gumpBuilder.AddImageTiled(x, y, width, height, gumpId);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddItem(int x, int y, int itemId, int hue = 0) => _gumpBuilder.AddItem(x, y, itemId, hue);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddItemProperty(Serial serial) => _gumpBuilder.AddItemProperty(serial);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddLabel(int x, int y, int hue, ReadOnlySpan<char> text)
{
WriteInternalizedString(text);
_gumpBuilder.AddLabel(x, y, hue, _stringsCount++);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddLabel(int x, int y, int hue, ref RawInterpolatedStringHandler handler)
{
AddLabel(x, y, hue, handler.Text);
handler.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddLabelCropped(int x, int y, int width, int height, int hue, ReadOnlySpan<char> text)
{
WriteInternalizedString(text);
_gumpBuilder.AddLabelCropped(x, y, width, height, hue, _stringsCount++);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddLabelCropped(int x, int y, int width, int height, int hue, ref RawInterpolatedStringHandler handler)
{
AddLabelCropped(x, y, width, height, hue, handler.Text);
handler.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddGumpIdOverride(int gumpId) => _gumpBuilder.AddGumpIdOverride(gumpId);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddPage(int page = 0) => _gumpBuilder.AddPage(page);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddRadio(int x, int y, int inactiveId, int activeId, bool selected, int switchId) =>
_gumpBuilder.AddRadio(x, y, inactiveId, activeId, selected, switchId);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddSpriteImage(int x, int y, int gumpId, int width, int height, int sx, int sy) =>
_gumpBuilder.AddSpriteImage(x, y, gumpId, width, height, sx, sy);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddTextEntry(
int x, int y, int width, int height, int hue, int entryId, ReadOnlySpan<char> initialText = default
)
{
WriteInternalizedString(initialText);
_gumpBuilder.AddTextEntry(x, y, width, height, hue, entryId, _stringsCount++);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddTextEntry(
int x, int y, int width, int height, int hue, int entryId, ref RawInterpolatedStringHandler handler
)
{
AddTextEntry(x, y, width, height, hue, entryId, handler.Text);
handler.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddTextEntryLimited(
int x, int y, int width, int height, int hue, int entryId, ReadOnlySpan<char> initialText = default, int size = 0
)
{
WriteInternalizedString(initialText);
_gumpBuilder.AddTextEntryLimited(x, y, width, height, hue, entryId, _stringsCount++, size);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddTextEntryLimited(
int x, int y, int width, int height, int hue, int entryId, ref RawInterpolatedStringHandler handler, int size = 0
)
{
AddTextEntryLimited(x, y, width, height, hue, entryId, handler.Text, size);
handler.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddTooltip(int number) => _gumpBuilder.AddTooltip(number);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddTooltip(int number, ReadOnlySpan<char> args) => _gumpBuilder.AddTooltip(number, args);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddTooltip(int number, ref RawInterpolatedStringHandler handler)
{
_gumpBuilder.AddTooltip(number, handler.Text);
handler.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FinalizeLayout() => _gumpBuilder.FinalizeLayout();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WriteInternalizedString(ReadOnlySpan<char> text)
{
if (text.Length > ushort.MaxValue)
{
text = text[..ushort.MaxValue];
}
GrowStringsBufferIfNeeded(2 + text.Length * 2);
BinaryPrimitives.WriteUInt16BigEndian(_stringsBuffer.AsSpan(_stringBytesWritten), (ushort)text.Length);
_stringBytesWritten += 2;
_stringBytesWritten += text.GetBytesBigUni(_stringsBuffer.AsSpan(_stringBytesWritten));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void GrowStringsBufferIfNeeded(int needed)
{
if (needed + _stringBytesWritten <= _stringsBuffer.Length)
{
return;
}
var newSize = Math.Max(_stringBytesWritten + needed, _stringsBuffer.Length * 2);
byte[] poolArray = STArrayPool<byte>.Shared.Rent(newSize);
_stringsBuffer.AsSpan(0, _stringBytesWritten).CopyTo(poolArray);
byte[] toReturn = _stringsBuffer;
_stringsBuffer = poolArray;
if (toReturn != null && toReturn != _staticStringsBuffer)
{
STArrayPool<byte>.Shared.Return(toReturn);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
_gumpBuilder.Dispose();
if (_stringsBuffer != _staticStringsBuffer)
{
STArrayPool<byte>.Shared.Return(_stringsBuffer);
}
this = default;
}
}

View file

@ -0,0 +1,27 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: GumpFlags.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
namespace Server.Gumps;
[Flags]
public enum GumpFlags
{
NoDispose = 0x1,
NoResize = 0x2,
NoMove = 0x4,
NoClose = 0x8
}

View file

@ -0,0 +1,642 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: GumpLayoutBuilder.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Runtime.CompilerServices;
using Server.Buffers;
using Server.Text;
namespace Server.Gumps;
public ref struct GumpLayoutBuilder
{
private static readonly byte[] _staticLayoutBuffer = GC.AllocateUninitializedArray<byte>(0x80000);
private byte[] _layoutBuffer;
private int _bytesWritten;
internal GumpFlags _flags;
internal int _switches;
internal int _textEntries;
internal Span<byte> LayoutData => _layoutBuffer.AsSpan(0, _bytesWritten);
// This struct uses static fields and is therefore not thread safe!
// Do not instantiate/process multiple gumps at the same time!
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public GumpLayoutBuilder() => _layoutBuffer = _staticLayoutBuffer;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetNoClose()
{
if ((_flags & GumpFlags.NoClose) == 0)
{
GrowIfNeeded(11);
Write("{ noclose }"u8);
_flags |= GumpFlags.NoClose;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetNoMove()
{
if ((_flags & GumpFlags.NoMove) == 0)
{
GrowIfNeeded(10);
Write("{ nomove }"u8);
_flags |= GumpFlags.NoMove;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetNoResize()
{
if ((_flags & GumpFlags.NoResize) == 0)
{
GrowIfNeeded(12);
Write("{ noresize }"u8);
_flags |= GumpFlags.NoResize;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetNoDispose()
{
if ((_flags & GumpFlags.NoDispose) == 0)
{
GrowIfNeeded(13);
Write("{ nodispose }"u8);
_flags |= GumpFlags.NoDispose;
}
}
public void AddAlphaRegion(int x, int y, int width, int height)
{
GrowIfNeeded(8 + 12 + 36);
WriteStart("checkertrans"u8);
WriteValue(x);
WriteValue(y);
WriteValue(width);
WriteValue(height);
WriteEnd();
}
public void AddBackground(int x, int y, int width, int height, int gumpId)
{
GrowIfNeeded(9 + 9 + 45);
WriteStart("resizepic"u8);
WriteValue(x);
WriteValue(y);
WriteValue(gumpId);
WriteValue(width);
WriteValue(height);
WriteEnd();
}
public void AddButton(
int x, int y, int normalId, int pressedId, int buttonId, GumpButtonType type = GumpButtonType.Reply, int param = 0
)
{
GrowIfNeeded(11 + 6 + 54 + 2);
WriteStart("button"u8);
WriteValue(x);
WriteValue(y);
WriteValue(normalId);
WriteValue(pressedId);
WriteValue(type == GumpButtonType.Reply);
WriteValue(param);
WriteValue(buttonId);
WriteEnd();
}
public void AddCheckbox(int x, int y, int inactiveId, int activeId, bool selected, int switchId)
{
GrowIfNeeded(10 + 8 + 45 + 2);
WriteStart("checkbox"u8);
WriteValue(x);
WriteValue(y);
WriteValue(inactiveId);
WriteValue(activeId);
WriteValue(selected);
WriteValue(switchId);
WriteEnd();
_switches++;
}
public void AddGroup(int groupId)
{
if (groupId == 1)
{
GrowIfNeeded(11);
Write("{ group 1 }"u8);
return;
}
GrowIfNeeded(5 + 7 + 9);
WriteStart("group"u8);
WriteValue(groupId);
WriteEnd();
}
public int AddHtmlPlaceholder(int x, int y, int width, int height,
bool background = false, bool scrollbar = false)
{
GrowIfNeeded(11 + 8 + 36 + 10);
WriteStart("htmlgump"u8);
WriteValue(x);
WriteValue(y);
WriteValue(width);
WriteValue(height);
var position = _bytesWritten;
Write(" "u8);
WriteValue(background);
WriteValue(scrollbar);
WriteEnd();
return position;
}
public void AddHtml(int x, int y, int width, int height, int text,
bool background = false, bool scrollbar = false)
{
GrowIfNeeded(11 + 8 + 45 + 4);
WriteStart("htmlgump"u8);
WriteValue(x);
WriteValue(y);
WriteValue(width);
WriteValue(height);
WriteValue(text);
WriteValue(background);
WriteValue(scrollbar);
WriteEnd();
}
public void AddHtmlLocalized(
int x, int y, int width, int height, int number, bool background = false, bool scrollbar = false
)
{
GrowIfNeeded(11 + 11 + 45 + 4);
WriteStart("xmfhtmlgump"u8);
WriteValue(x);
WriteValue(y);
WriteValue(width);
WriteValue(height);
WriteValue(number);
WriteValue(background);
WriteValue(scrollbar);
WriteEnd();
}
public void AddHtmlLocalized(
int x, int y, int width, int height, int number, short color, bool background = false, bool scrollbar = false
)
{
GrowIfNeeded(12 + 16 + 45 + 5 + 4);
WriteStart("xmfhtmlgumpcolor"u8);
WriteValue(x);
WriteValue(y);
WriteValue(width);
WriteValue(height);
WriteValue(number);
WriteValue(background);
WriteValue(scrollbar);
WriteValue(color);
WriteEnd();
}
public void AddHtmlLocalized(int x, int y, int width, int height, int number, ReadOnlySpan<char> args, int color,
bool background = false, bool scrollbar = false)
{
GrowIfNeeded(12 + 10 + 45 + 5 + 4 + (args.Length > 0 ? 3 + args.Length : 0));
WriteStart("xmfhtmltok"u8);
WriteValue(x);
WriteValue(y);
WriteValue(width);
WriteValue(height);
WriteValue(background);
WriteValue(scrollbar);
WriteValue(color);
WriteValue(number);
if (args.Length > 0)
{
Write("@"u8);
WriteValue(args);
Write("@ "u8);
}
WriteEnd();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHtmlLocalized(
int x, int y, int width, int height, int number, ref RawInterpolatedStringHandler handler, int color,
bool background = false, bool scrollbar = false
)
{
AddHtmlLocalized(x, y, width, height, number, handler.Text, color, background, scrollbar);
// Dispose of the handler
handler.Clear();
}
public void AddImage(int x, int y, int gumpId, int hue = 0, ReadOnlySpan<char> cls = default)
{
GrowIfNeeded(7 + 7 + 36 + (hue != 0 ? 14 : 0) + (cls.Length > 0 ? 7 + cls.Length : 0));
WriteStart("gumppic"u8);
WriteValue(x);
WriteValue(y);
WriteValue(gumpId);
if (hue != 0)
{
Write("hue="u8);
WriteValue(hue);
Write(" "u8);
}
if (!cls.IsEmpty)
{
Write("class="u8);
WriteValue(cls);
Write(" "u8);
}
WriteEnd();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddImage(int x, int y, int gumpId, ref RawInterpolatedStringHandler handler)
{
AddImage(x, y, gumpId, 0, handler.Text);
handler.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddImage(int x, int y, int gumpId, int hue, ref RawInterpolatedStringHandler handler)
{
AddImage(x, y, gumpId, hue, handler.Text);
handler.Clear();
}
public void AddImageTiledButton(int x, int y, int normalId, int pressedId, int buttonId, GumpButtonType type, int param,
int itemId, int hue, int width, int height, int localizedTooltip = -1)
{
GrowIfNeeded(15 + 13 + 90 + 2);
WriteStart("buttontileart"u8);
WriteValue(x);
WriteValue(y);
WriteValue(normalId);
WriteValue(pressedId);
WriteValue(type == GumpButtonType.Reply);
WriteValue(param);
WriteValue(buttonId);
WriteValue(itemId);
WriteValue(hue);
WriteValue(width);
WriteValue(height);
WriteEnd();
if (localizedTooltip <= 0)
{
return;
}
AddTooltip(localizedTooltip);
}
public void AddImageTiled(int x, int y, int width, int height, int gumpId)
{
GrowIfNeeded(9 + 12 + 45);
WriteStart("gumppictiled"u8);
WriteValue(x);
WriteValue(y);
WriteValue(width);
WriteValue(height);
WriteValue(gumpId);
WriteEnd();
}
public void AddItem(int x, int y, int itemId, int hue = 0)
{
GrowIfNeeded(7 + 36 + (hue != 0 ? 20 : 7));
WriteStart(hue == 0 ? "tilepic"u8 : "tilepichue"u8);
WriteValue(x);
WriteValue(y);
WriteValue(itemId);
if (hue != 0)
{
WriteValue(hue);
}
WriteEnd();
}
public void AddItemProperty(Serial serial)
{
GrowIfNeeded(5 + 12 + 9);
WriteStart("itemproperty"u8);
WriteValue(serial.Value);
WriteEnd();
}
public int AddLabelPlaceholder(int x, int y, int hue)
{
GrowIfNeeded(8 + 4 + 27 + 6);
WriteStart("text"u8);
WriteValue(x);
WriteValue(y);
WriteValue(hue);
var position = _bytesWritten;
Write(" "u8);
WriteEnd();
return position;
}
public void AddLabel(int x, int y, int hue, int text)
{
GrowIfNeeded(8 + 4 + 36 + 6);
WriteStart("text"u8);
WriteValue(x);
WriteValue(y);
WriteValue(hue);
WriteValue(text);
WriteEnd();
}
public int AddLabelCroppedPlaceholder(int x, int y, int width, int height, int hue)
{
GrowIfNeeded(10 + 11 + 45 + 6);
WriteStart("croppedtext"u8);
WriteValue(x);
WriteValue(y);
WriteValue(width);
WriteValue(height);
WriteValue(hue);
var position = _bytesWritten;
Write(" "u8);
WriteEnd();
return position;
}
public void AddLabelCropped(int x, int y, int width, int height, int hue, int text)
{
GrowIfNeeded(10 + 11 + 54 + 6);
WriteStart("croppedtext"u8);
WriteValue(x);
WriteValue(y);
WriteValue(width);
WriteValue(height);
WriteValue(hue);
WriteValue(text);
WriteEnd();
}
public void AddGumpIdOverride(int gumpId)
{
GrowIfNeeded(5 + 10 + 9);
WriteStart("mastergump"u8);
WriteValue(gumpId);
WriteEnd();
}
public void AddPage(int page = 0)
{
if (page == 0)
{
GrowIfNeeded(10);
Write("{ page 0 }"u8);
return;
}
GrowIfNeeded(5 + 4 + 9);
WriteStart("page"u8);
WriteValue(page);
WriteEnd();
}
public void AddRadio(int x, int y, int inactiveId, int activeId, bool selected, int switchId)
{
GrowIfNeeded(10 + 5 + 45 + 2);
WriteStart("radio"u8);
WriteValue(x);
WriteValue(y);
WriteValue(inactiveId);
WriteValue(activeId);
WriteValue(selected);
WriteValue(switchId);
WriteEnd();
_switches++;
}
public void AddSpriteImage(int x, int y, int gumpId, int width, int height, int sx, int sy)
{
GrowIfNeeded(11 + 8 + 63);
WriteStart("picinpic"u8);
WriteValue(x);
WriteValue(y);
WriteValue(gumpId);
WriteValue(width);
WriteValue(height);
WriteValue(sx);
WriteValue(sy);
WriteEnd();
}
public int AddTextEntryPlaceholder(
int x, int y, int width, int height, int hue, int entryId
)
{
GrowIfNeeded(11 + 9 + 54 + 6);
WriteStart("textentry"u8);
WriteValue(x);
WriteValue(y);
WriteValue(width);
WriteValue(height);
WriteValue(hue);
WriteValue(entryId);
var position = _bytesWritten;
Write(" "u8);
WriteEnd();
_textEntries++;
return position;
}
public void AddTextEntry(
int x, int y, int width, int height, int hue, int entryId, int initialText
)
{
GrowIfNeeded(11 + 9 + 63 + 6);
WriteStart("textentry"u8);
WriteValue(x);
WriteValue(y);
WriteValue(width);
WriteValue(height);
WriteValue(hue);
WriteValue(entryId);
WriteValue(initialText);
WriteEnd();
_textEntries++;
}
public int AddTextEntryLimitedPlaceholder(
int x, int y, int width, int height, int hue, int entryId, int size = 0
)
{
GrowIfNeeded(12 + 16 + 63 + 6);
WriteStart("textentrylimited"u8);
WriteValue(x);
WriteValue(y);
WriteValue(width);
WriteValue(height);
WriteValue(hue);
WriteValue(entryId);
var position = _bytesWritten;
Write(" "u8);
WriteValue(size);
WriteEnd();
_textEntries++;
return position;
}
public void AddTextEntryLimited(
int x, int y, int width, int height, int hue, int entryId, int initialText, int size = 0
)
{
GrowIfNeeded(12 + 16 + 72);
WriteStart("textentrylimited"u8);
WriteValue(x);
WriteValue(y);
WriteValue(width);
WriteValue(height);
WriteValue(hue);
WriteValue(entryId);
WriteValue(initialText);
WriteValue(size);
WriteEnd();
_textEntries++;
}
public void AddTooltip(int number)
{
GrowIfNeeded(5 + 7 + 9);
WriteStart("tooltip"u8);
WriteValue(number);
WriteEnd();
}
public void AddTooltip(int number, ReadOnlySpan<char> args)
{
GrowIfNeeded(5 + 7 + 9 + (args.Length > 0 ? 3 + args.Length : 0));
WriteStart("tooltip"u8);
WriteValue(number);
if (args.Length > 0)
{
Write("@"u8);
WriteValue(args);
Write("@ "u8);
}
WriteEnd();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WriteStart(ReadOnlySpan<byte> value)
{
Write("{ "u8);
Write(value);
Write(" "u8);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WriteEnd() => Write("}"u8);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WriteValue(bool value) => Write(value ? "1 "u8 : "0 "u8);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WriteValue(ReadOnlySpan<char> value)
{
if (!value.IsEmpty)
{
_bytesWritten += value.GetBytesAscii(_layoutBuffer.AsSpan(_bytesWritten));
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WriteValue<T>(T value, ReadOnlySpan<char> format = default) where T : IUtf8SpanFormattable
{
if (!value.TryFormat(_layoutBuffer.AsSpan(_bytesWritten), out int bytesWritten, format, null))
{
throw new InvalidOperationException($"Failed to format '{value}' with the given format '{format}'");
}
_bytesWritten += bytesWritten;
Write(" "u8);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Write(ReadOnlySpan<byte> span)
{
span.CopyTo(_layoutBuffer.AsSpan(_bytesWritten));
_bytesWritten += span.Length;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void FinalizeLayout()
{
GrowIfNeeded(1);
_layoutBuffer[_bytesWritten++] = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void GrowIfNeeded(int count)
{
if (_bytesWritten + count <= _layoutBuffer.Length)
{
return;
}
var newSize = Math.Max(_bytesWritten + count, _layoutBuffer.Length * 2);
byte[] poolArray = STArrayPool<byte>.Shared.Rent(newSize);
_layoutBuffer.AsSpan(0, _bytesWritten).CopyTo(poolArray);
byte[] toReturn = _layoutBuffer;
_layoutBuffer = poolArray;
if (toReturn != _staticLayoutBuffer)
{
STArrayPool<byte>.Shared.Return(toReturn);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
if (_layoutBuffer != _staticLayoutBuffer)
{
STArrayPool<byte>.Shared.Return(_layoutBuffer);
}
this = default;
}
}

View file

@ -0,0 +1,127 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: GumpStringsBuilder.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Server.Buffers;
using Server.Text;
namespace Server.Gumps;
public ref struct GumpStringsBuilder
{
private static readonly byte[] _staticStringsBuffer = GC.AllocateUninitializedArray<byte>(0x80000);
private static readonly Dictionary<ulong, int> _hashes = new();
private readonly bool _finalizeLayout;
private byte[] _stringsBuffer;
private int _stringBytesWritten;
internal int _stringsCount;
internal ReadOnlySpan<byte> StringsBuffer => _stringsBuffer.AsSpan(0, _stringBytesWritten);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public GumpStringsBuilder(bool finalizeLayout)
{
_finalizeLayout = finalizeLayout;
_stringsBuffer = _staticStringsBuffer;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void GrowStringsBufferIfNeeded(int needed)
{
var newLength = needed + _stringBytesWritten;
if (newLength <= _stringsBuffer.Length)
{
return;
}
var newSize = Math.Max(newLength, _stringsBuffer.Length * 2);
byte[] poolArray = STArrayPool<byte>.Shared.Rent(newSize);
_stringsBuffer.AsSpan(0, _stringBytesWritten).CopyTo(poolArray);
byte[] toReturn = _stringsBuffer;
_stringsBuffer = poolArray;
if (toReturn != _staticStringsBuffer)
{
STArrayPool<byte>.Shared.Return(toReturn);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetStringSlot(ReadOnlySpan<char> slotKey, ref RawInterpolatedStringHandler handler)
{
SetStringSlot(slotKey, handler.Text);
handler.Clear();
}
public void SetStringSlot(ReadOnlySpan<char> slotKey, ReadOnlySpan<char> text)
{
var hash = HashUtility.ComputeHash64(slotKey);
if (text.Length > ushort.MaxValue)
{
text = text[..ushort.MaxValue];
}
GrowStringsBufferIfNeeded(2 + text.Length * 2);
BinaryPrimitives.WriteUInt16BigEndian(_stringsBuffer.AsSpan(_stringBytesWritten), (ushort)text.Length);
_stringBytesWritten += 2;
if (text.Length > 0)
{
_stringBytesWritten += text.GetBytesBigUni(_stringsBuffer.AsSpan(_stringBytesWritten));
}
if (_finalizeLayout)
{
_hashes[hash] = _stringsCount++;
}
}
public void FinalizeStrings(ref StaticGumpBuilder builder)
{
var startingIndex = builder._stringsCount;
var stringSlotOffsets = builder.StringSlotOffsets;
for (var i = 0; i < stringSlotOffsets.Length; i++)
{
var (hash, offset) = stringSlotOffsets[i];
if (hash != 0 && _hashes.TryGetValue(hash, out var index))
{
builder.WriteSlotIndex(offset, index + startingIndex);
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
if (_finalizeLayout)
{
_hashes.Clear();
}
if (_stringsBuffer != _staticStringsBuffer)
{
STArrayPool<byte>.Shared.Return(_stringsBuffer);
}
}
}

View file

@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: Gump.cs *
* *
@ -13,61 +13,30 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Collections.Generic;
using Server.Network;
using Server.Utilities;
namespace Server.Gumps;
public partial class Gump
public class Gump : BaseGump
{
private static Serial _nextSerial = (Serial)1;
private int _switches;
private int _textEntries;
public int Switches
{
get => _switches;
set => _switches = value;
}
public int TextEntries
{
get => _textEntries;
set => _textEntries = value;
}
public Gump(int x, int y)
{
do
{
Serial = _nextSerial++;
} while (Serial == 0); // standard client apparently doesn't send a gump response packet if serial == 0
X = x;
Y = y;
TypeID = GetTypeID(GetType());
Entries = new List<GumpEntry>();
Strings = new List<string>();
Entries = [];
Strings = [];
}
public List<string> Strings { get; }
public int TypeID { get; }
public List<GumpEntry> Entries { get; }
public Serial Serial { get; set; }
public int X { get; set; }
public int Y { get; set; }
public bool Disposable { get; set; } = true;
public bool Resizable { get; set; } = true;
@ -76,15 +45,9 @@ public partial class Gump
public bool Closable { get; set; } = true;
public static int GetTypeID(Type type)
{
unchecked
{
// To use the original .NET Framework deterministic hash code (with really bad performance)
// change the next line to use HashUtility.GetNetFrameworkHashCode
return (int)HashUtility.ComputeHash32(type?.FullName);
}
}
public override int Switches => _switches;
public override int TextEntries => _textEntries;
public void AddPage(int page)
{
@ -278,17 +241,17 @@ public partial class Gump
return Strings.Count - 1;
}
public void SendTo(NetState state)
public override void SendTo(NetState state)
{
state.AddGump(this);
state.SendDisplayGump(this, out _switches, out _textEntries);
}
public virtual void OnResponse(NetState sender, in RelayInfo info)
{
}
public virtual void OnServerClose(NetState owner)
protected void Reset()
{
_switches = 0;
_textEntries = 0;
Entries.Clear();
Strings.Clear();
}
}

View file

@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: GumpAlphaRegion.cs *
* *

View file

@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: GumpBackground.cs *
* *

View file

@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: GumpButton.cs *
* *

View file

@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: GumpCheck.cs *
* *

View file

@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: GumpECHandleInput.cs *
* *

View file

@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: GumpGroup.cs *
* *

View file

@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: GumpHtml.cs *
* *

View file

@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: GumpHtmlLocalized.cs *
* *

View file

@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: GumpImage.cs *
* *

View file

@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: GumpImageTileButton.cs *
* *

View file

@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: GumpImageTiled.cs *
* *

View file

@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: GumpItem.cs *
* *

View file

@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: GumpItemProperty.cs *
* *

View file

@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: GumpLabel.cs *
* *

View file

@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: GumpLabelCropped.cs *
* *

View file

@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: GumpMasterGump.cs *
* *

View file

@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: GumpPage.cs *
* *

View file

@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: GumpRadio.cs *
* *

View file

@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: GumpSpriteImage.cs *
* *

View file

@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: GumpTextEntry.cs *
* *

View file

@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: GumpTextEntryLimited.cs *
* *

View file

@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: GumpTooltip.cs *
* *

View file

@ -0,0 +1,179 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: StaticGump.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
using Server.Buffers;
using Server.Network;
namespace Server.Gumps;
public abstract class StaticGump<TSelf> : BaseGump where TSelf : StaticGump<TSelf>
{
private static readonly byte[] _packetBuffer = GC.AllocateUninitializedArray<byte>(0x10000);
private static int _switches;
private static int _textEntries;
private static byte[] _compressedLayoutData;
private static byte[] _compressedStringsData;
private static bool _hasDynamicStrings;
private static int _staticStringsCount;
private static byte[] _staticStrings;
public override int Switches => _switches;
public override int TextEntries => _textEntries;
public StaticGump(int x, int y) : base(x, y)
{
}
protected abstract void BuildLayout(ref StaticGumpBuilder builder);
protected virtual void BuildStrings(ref GumpStringsBuilder builder)
{
}
public void CreatePacket(ref SpanWriter writer)
{
writer.Write((byte)0xDD); // Packet ID
writer.Seek(2, SeekOrigin.Current);
writer.Write(Serial);
writer.Write(TypeID);
writer.Write(X);
writer.Write(Y);
if (_compressedLayoutData != null)
{
writer.Write(_compressedLayoutData);
if (_compressedStringsData != null)
{
writer.Write(_staticStringsCount);
writer.Write(_compressedStringsData);
}
else if (_hasDynamicStrings)
{
var stringsBuilder = new GumpStringsBuilder(false);
BuildStrings(ref stringsBuilder);
if (_staticStringsCount == 0)
{
writer.Write(stringsBuilder._stringsCount);
OutgoingGumpPackets.WritePacked(stringsBuilder.StringsBuffer, ref writer);
}
else
{
writer.Write(_staticStringsCount + stringsBuilder._stringsCount);
var stringsData = stringsBuilder.StringsBuffer;
var buffer = STArrayPool<byte>.Shared.Rent(_staticStrings.Length + stringsData.Length);
_staticStrings.CopyTo(buffer.AsSpan());
stringsData.CopyTo(buffer.AsSpan(_staticStrings.Length));
OutgoingGumpPackets.WritePacked(buffer, ref writer);
STArrayPool<byte>.Shared.Return(buffer);
}
}
else if (_staticStrings != null)
{
writer.Write(_staticStringsCount);
OutgoingGumpPackets.WritePacked(_staticStrings, ref writer);
}
}
else
{
StaticGumpBuilder gumpBuilder = new StaticGumpBuilder();
BuildLayout(ref gumpBuilder);
gumpBuilder.FinalizeLayout();
_switches = gumpBuilder.Switches;
_textEntries = gumpBuilder.TextEntries;
_staticStringsCount = gumpBuilder._stringsCount;
var staticStringsData = gumpBuilder.StringsData;
var hasDynamicStrings = _hasDynamicStrings = gumpBuilder.StringSlotOffsets.Length > 0;
if (hasDynamicStrings)
{
_staticStrings = GC.AllocateUninitializedArray<byte>(staticStringsData.Length);
staticStringsData.CopyTo(_staticStrings);
var stringsBuilder = new GumpStringsBuilder(true);
BuildStrings(ref stringsBuilder);
stringsBuilder.FinalizeStrings(ref gumpBuilder); // Modifies the layout
WriteLayout(ref writer, ref gumpBuilder);
writer.Write(_staticStringsCount + stringsBuilder._stringsCount);
var stringsData = stringsBuilder.StringsBuffer;
var stringsLength = staticStringsData.Length + stringsData.Length;
var buffer = STArrayPool<byte>.Shared.Rent(stringsLength);
staticStringsData.CopyTo(buffer.AsSpan());
stringsData.CopyTo(buffer.AsSpan(staticStringsData.Length));
OutgoingGumpPackets.WritePacked(buffer.AsSpan(0, stringsLength), ref writer);
STArrayPool<byte>.Shared.Return(buffer);
stringsBuilder.Dispose();
}
else
{
WriteLayout(ref writer, ref gumpBuilder);
writer.Write(_staticStringsCount);
var stringsPos = writer.Position;
OutgoingGumpPackets.WritePacked(staticStringsData, ref writer);
var stringsLength = writer.Position - stringsPos;
_compressedStringsData = GC.AllocateUninitializedArray<byte>(stringsLength);
writer.Span.Slice(stringsPos, stringsLength).CopyTo(_compressedStringsData);
}
gumpBuilder.Dispose();
}
writer.WritePacketLength();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteLayout(ref SpanWriter writer, ref StaticGumpBuilder gumpBuilder)
{
var layoutPos = writer.Position;
OutgoingGumpPackets.WritePacked(gumpBuilder.LayoutData, ref writer);
var layoutLength = writer.Position - layoutPos;
_compressedLayoutData = GC.AllocateUninitializedArray<byte>(layoutLength);
writer.Span.Slice(layoutPos, layoutLength).CopyTo(_compressedLayoutData);
}
public override void SendTo(NetState ns)
{
ns.AddGump(this);
var writer = new SpanWriter(_packetBuffer);
CreatePacket(ref writer);
ns.Send(writer.Span);
writer.Dispose();
}
}

View file

@ -0,0 +1,511 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: StaticGumpBuilder.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
using Server.Buffers;
using Server.Text;
namespace Server.Gumps;
public ref struct StaticGumpBuilder
{
private static readonly byte[] _staticStringsBuffer = GC.AllocateUninitializedArray<byte>(0x80000);
// Position in the layout and the hash of the keyslot
private static readonly (ulong, int)[] _stringSlotOffsets = GC.AllocateUninitializedArray<(ulong, int)>(0x8000); // Assume max 65535 strings
private byte[] _stringsBuffer;
private int _stringBytesWritten;
internal int _stringsCount;
private GumpLayoutBuilder _gumpBuilder;
private int _stringOffsetCount;
internal ReadOnlySpan<byte> LayoutData => _gumpBuilder.LayoutData;
internal ReadOnlySpan<byte> StringsData => _stringsBuffer.AsSpan(0, _stringBytesWritten);
internal ReadOnlySpan<(ulong, int)> StringSlotOffsets => _stringSlotOffsets.AsSpan(0, _stringOffsetCount);
public int Switches => _gumpBuilder._switches;
public int TextEntries => _gumpBuilder._textEntries;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public StaticGumpBuilder()
{
_stringsBuffer = _staticStringsBuffer;
// For corner cases where slots are not filled, reserve the first string index for empty
_stringsBuffer.AsSpan(0, 2).Clear();
_stringBytesWritten = 2;
_stringsCount = 1;
_gumpBuilder = new GumpLayoutBuilder();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetNoClose() => _gumpBuilder.SetNoClose();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetNoMove() => _gumpBuilder.SetNoMove();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetNoResize() => _gumpBuilder.SetNoResize();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetNoDispose() => _gumpBuilder.SetNoDispose();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddAlphaRegion(int x, int y, int width, int height) =>
_gumpBuilder.AddAlphaRegion(x, y, width, height);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddBackground(int x, int y, int width, int height, int gumpID) =>
_gumpBuilder.AddBackground(x, y, width, height, gumpID);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddButton(
int x, int y, int normalID, int pressedId, int buttonId, GumpButtonType type = GumpButtonType.Reply, int param = 0
) => _gumpBuilder.AddButton(x, y, normalID, pressedId, buttonId, type, param);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddCheckbox(int x, int y, int inactiveID, int activeID, bool selected, int switchId) =>
_gumpBuilder.AddCheckbox(x, y, inactiveID, activeID, selected, switchId);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddGroup(int groupId) => _gumpBuilder.AddGroup(groupId);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHtmlPlaceholder(
int x,
int y,
int width,
int height,
ReadOnlySpan<char> slotKey,
bool background = false,
bool scrollbar = false
)
{
var index = _gumpBuilder.AddHtmlPlaceholder(x, y, width, height, background, scrollbar);
WriteInternalizedStringSlot(slotKey, index);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHtmlPlaceholder(
int x,
int y,
int width,
int height,
ref RawInterpolatedStringHandler handler,
bool background = false,
bool scrollbar = false
)
{
AddHtmlPlaceholder(x, y, width, height, handler.Text, background, scrollbar);
handler.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHtml(
int x,
int y,
int width,
int height,
ReadOnlySpan<char> text,
bool background = false,
bool scrollbar = false
)
{
WriteInternalizedString(text);
_gumpBuilder.AddHtml(x, y, width, height, _stringsCount++, background, scrollbar);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHtml(
int x, int y, int width, int height, ref RawInterpolatedStringHandler handler,
bool background = false, bool scrollbar = false
)
{
AddHtml(x, y, width, height, handler.Text, background, scrollbar);
handler.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHtml(
int x,
int y,
int width,
int height,
int color,
ReadOnlySpan<char> text,
bool background = false,
bool scrollbar = false
) => AddHtml(x, y, width, height, color, $"<BASEFONT COLOR=#{color:X6}>{text}</BASEFONT>", background, scrollbar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHtml(
int x,
int y,
int width,
int height,
int color,
ref RawInterpolatedStringHandler handler,
bool background = false,
bool scrollbar = false
)
{
AddHtml(x, y, width, height, color, handler.Text, background, scrollbar);
handler.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHtmlCentered(
int x, int y, int width, int height, ReadOnlySpan<char> text, bool background = false, bool scrollbar = false
) => AddHtml(x, y, width, height, $"<CENTER>{text}</CENTER>", background, scrollbar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHtmlCentered(
int x,
int y,
int width,
int height,
ref RawInterpolatedStringHandler handler,
bool background = false,
bool scrollbar = false
)
{
AddHtml(x, y, width, height, $"<CENTER>{handler.Text}</CENTER>", background, scrollbar);
handler.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHtmlCentered(
int x,
int y,
int width,
int height,
int color,
ReadOnlySpan<char> text,
bool background = false,
bool scrollbar = false
) => AddHtml(x, y, width, height, color, $"<CENTER>{text}</CENTER>", background, scrollbar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHtmlCentered(
int x,
int y,
int width,
int height,
int color,
ref RawInterpolatedStringHandler handler,
bool background = false,
bool scrollbar = false
)
{
AddHtmlCentered(x, y, width, height, color, handler.Text, background, scrollbar);
handler.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHtmlLocalized(
int x, int y, int width, int height, int number, bool background = false, bool scrollbar = false
) => _gumpBuilder.AddHtmlLocalized(x, y, width, height, number, background, scrollbar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHtmlLocalized(
int x, int y, int width, int height, int number, short color, bool background = false, bool scrollbar = false
) => _gumpBuilder.AddHtmlLocalized(x, y, width, height, number, color, background, scrollbar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHtmlLocalized(
int x,
int y,
int width,
int height,
int number,
ReadOnlySpan<char> args,
int color,
bool background = false,
bool scrollbar = false
) => _gumpBuilder.AddHtmlLocalized(x, y, width, height, number, args, color, background, scrollbar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHtmlLocalized(
int x, int y, int width, int height, int number, ref RawInterpolatedStringHandler handler, int color,
bool background = false, bool scrollbar = false
) => _gumpBuilder.AddHtmlLocalized(x, y, width, height, number, ref handler, color, background, scrollbar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddImage(int x, int y, int gumpId, int hue = 0, ReadOnlySpan<char> cls = default) =>
_gumpBuilder.AddImage(x, y, gumpId, hue, cls);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddImage(int x, int y, int gumpId, ref RawInterpolatedStringHandler handler) =>
_gumpBuilder.AddImage(x, y, gumpId, ref handler);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddImage(int x, int y, int gumpId, int hue, ref RawInterpolatedStringHandler handler) =>
_gumpBuilder.AddImage(x, y, gumpId, hue, ref handler);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddImageTiledButton(
int x,
int y,
int normalId,
int pressedId,
int buttonId,
GumpButtonType type,
int param,
int itemId,
int hue,
int width,
int height,
int localizedTooltip = -1
) => _gumpBuilder.AddImageTiledButton(
x, y, normalId, pressedId, buttonId, type, param, itemId, hue, width, height, localizedTooltip
);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddImageTiled(int x, int y, int width, int height, int gumpId) =>
_gumpBuilder.AddImageTiled(x, y, width, height, gumpId);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddItem(int x, int y, int itemId, int hue = 0) => _gumpBuilder.AddItem(x, y, itemId, hue);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddItemProperty(Serial serial) => _gumpBuilder.AddItemProperty(serial);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddLabelPlaceholder(int x, int y, int hue, ReadOnlySpan<char> slotKey)
{
var index = _gumpBuilder.AddLabelPlaceholder(x, y, hue);
WriteInternalizedStringSlot(slotKey, index);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddLabelPlaceholder(int x, int y, int hue, ref RawInterpolatedStringHandler handler)
{
AddHtmlPlaceholder(x, y, 0, 0, handler.Text);
handler.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddLabel(int x, int y, int hue, ReadOnlySpan<char> text)
{
WriteInternalizedString(text);
_gumpBuilder.AddLabel(x, y, hue, _stringsCount++);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddLabel(int x, int y, int hue, ref RawInterpolatedStringHandler handler)
{
AddLabel(x, y, hue, handler.Text);
handler.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddLabelCroppedPlaceholder(int x, int y, int width, int height, int hue, ref RawInterpolatedStringHandler handler)
{
AddLabelCroppedPlaceholder(x, y, width, height, hue, handler.Text);
handler.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddLabelCroppedPlaceholder(int x, int y, int width, int height, int hue, ReadOnlySpan<char> slotKey)
{
var index = _gumpBuilder.AddLabelCroppedPlaceholder(x, y, width, height, hue);
WriteInternalizedStringSlot(slotKey, index);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddLabelCropped(int x, int y, int width, int height, int hue, ReadOnlySpan<char> text)
{
WriteInternalizedString(text);
_gumpBuilder.AddLabelCropped(x, y, width, height, hue, _stringsCount++);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddLabelCropped(int x, int y, int width, int height, int hue, ref RawInterpolatedStringHandler handler)
{
AddLabelCropped(x, y, width, height, hue, handler.Text);
handler.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddGumpIdOverride(int gumpId) => _gumpBuilder.AddGumpIdOverride(gumpId);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddPage(int page = 0) => _gumpBuilder.AddPage(page);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddRadio(int x, int y, int inactiveId, int activeId, bool selected, int switchId) =>
_gumpBuilder.AddRadio(x, y, inactiveId, activeId, selected, switchId);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddSpriteImage(int x, int y, int gumpId, int width, int height, int sx, int sy) =>
_gumpBuilder.AddSpriteImage(x, y, gumpId, width, height, sx, sy);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddTextEntryPlaceholder(
int x, int y, int width, int height, int hue, int entryId, ref RawInterpolatedStringHandler handler
)
{
AddTextEntryPlaceholder(x, y, width, height, hue, entryId, handler.Text);
handler.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddTextEntryPlaceholder(int x, int y, int width, int height, int hue, int entryId, ReadOnlySpan<char> slotKey)
{
var index = _gumpBuilder.AddTextEntryPlaceholder(x, y, width, height, hue, entryId);
WriteInternalizedStringSlot(slotKey, index);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddTextEntry(
int x, int y, int width, int height, int hue, int entryId, ReadOnlySpan<char> initialText = default
)
{
WriteInternalizedString(initialText);
_gumpBuilder.AddTextEntry(x, y, width, height, hue, entryId, _stringsCount++);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddTextEntry(
int x, int y, int width, int height, int hue, int entryId, ref RawInterpolatedStringHandler handler
)
{
AddTextEntry(x, y, width, height, hue, entryId, handler.Text);
handler.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddTextEntryLimitedPlaceholder(
int x, int y, int width, int height, int hue, int entryId, ReadOnlySpan<char> slotKey, int size = 0
)
{
var index = _gumpBuilder.AddTextEntryLimitedPlaceholder(x, y, width, height, hue, entryId, size);
WriteInternalizedStringSlot(slotKey, index);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddTextEntryLimitedPlaceholder(
int x, int y, int width, int height, int hue, int entryId, ref RawInterpolatedStringHandler handler, int size = 0
)
{
AddTextEntryLimitedPlaceholder(x, y, width, height, hue, entryId, handler.Text, size);
handler.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddTextEntryLimited(
int x, int y, int width, int height, int hue, int entryId, ReadOnlySpan<char> initialText = default, int size = 0
)
{
WriteInternalizedString(initialText);
_gumpBuilder.AddTextEntryLimited(x, y, width, height, hue, entryId, _stringsCount++, size);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddTextEntryLimited(
int x, int y, int width, int height, int hue, int entryId, ref RawInterpolatedStringHandler handler, int size = 0
)
{
AddTextEntryLimited(x, y, width, height, hue, entryId, handler.Text, size);
handler.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddTooltip(int number) => _gumpBuilder.AddTooltip(number);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddTooltip(int number, ReadOnlySpan<char> args) => _gumpBuilder.AddTooltip(number, args);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddTooltip(int number, ref RawInterpolatedStringHandler handler)
{
_gumpBuilder.AddTooltip(number, handler.Text);
handler.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FinalizeLayout() => _gumpBuilder.FinalizeLayout();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void WriteInternalizedString(ReadOnlySpan<char> text)
{
if (text.Length > ushort.MaxValue)
{
text = text[..ushort.MaxValue];
}
GrowStringsBufferIfNeeded(2 + text.Length * 2);
BinaryPrimitives.WriteUInt16BigEndian(_stringsBuffer.AsSpan(_stringBytesWritten), (ushort)text.Length);
_stringBytesWritten += 2;
_stringBytesWritten += text.GetBytesBigUni(_stringsBuffer.AsSpan(_stringBytesWritten));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void GrowStringsBufferIfNeeded(int needed)
{
if (needed + _stringBytesWritten <= _stringsBuffer.Length)
{
return;
}
var newSize = Math.Max(_stringBytesWritten + needed, _stringsBuffer.Length * 2);
byte[] poolArray = STArrayPool<byte>.Shared.Rent(newSize);
_stringsBuffer.AsSpan(0, _stringBytesWritten).CopyTo(poolArray);
byte[] toReturn = _stringsBuffer;
_stringsBuffer = poolArray;
if (toReturn != _staticStringsBuffer)
{
STArrayPool<byte>.Shared.Return(toReturn);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WriteInternalizedStringSlot(ReadOnlySpan<char> slotKey, int index)
{
var hash = HashUtility.ComputeHash64(slotKey);
_stringSlotOffsets[_stringOffsetCount++] = (hash, index);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void WriteSlotIndex(int offset, int index)
{
// Left padded final index for the slot
index.TryFormat(_gumpBuilder.LayoutData[offset..], out _, "00000");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
_gumpBuilder.Dispose();
if (_stringsBuffer != _staticStringsBuffer)
{
STArrayPool<byte>.Shared.Return(_stringsBuffer);
}
this = default;
}
}

View file

@ -8169,9 +8169,9 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
return false;
}
public Gump FindGump<T>() where T : Gump => m_NetState?.Gumps.Find(g => g is T);
public BaseGump FindGump<T>() where T : BaseGump => m_NetState?.Gumps.Find(g => g is T);
public bool CloseGump<T>() where T : Gump
public bool CloseGump<T>() where T : BaseGump
{
if (m_NetState == null)
{
@ -8199,7 +8199,7 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
return false;
}
var gumps = new List<Gump>(ns.Gumps);
var gumps = new List<BaseGump>(ns.Gumps);
ns.ClearGumps();
@ -8213,9 +8213,9 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
return true;
}
public bool HasGump<T>() where T : Gump => FindGump<T>() != null;
public bool HasGump<T>() where T : BaseGump => FindGump<T>() != null;
public bool SendGump(Gump g)
public bool SendGump(BaseGump g)
{
if (m_NetState == null)
{

View file

@ -127,10 +127,10 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
{
Connection = connection;
Seeded = false;
Gumps = new List<Gump>();
HuePickers = new List<HuePicker>();
Menus = new List<IMenu>();
Trades = new List<SecureTrade>();
Gumps = [];
HuePickers = [];
Menus = [];
Trades = [];
RecvPipe = new Pipe(RecvPipeSize);
SendPipe = new Pipe(SendPipeSize);
_nextActivityCheck = Core.TickCount + 30000;
@ -225,7 +225,7 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
public int Sequence { get; set; }
public List<Gump> Gumps { get; private set; }
public List<BaseGump> Gumps { get; private set; }
public List<HuePicker> HuePickers { get; private set; }
@ -381,7 +381,7 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
public void AddMenu(IMenu menu)
{
Menus ??= new List<IMenu>();
Menus ??= [];
if (Menus.Count < MenuCap)
{
@ -411,7 +411,7 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
public void AddHuePicker(HuePicker huePicker)
{
HuePickers ??= new List<HuePicker>();
HuePickers ??= [];
if (HuePickers.Count < HuePickerCap)
{
@ -439,9 +439,9 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
HuePickers?.Clear();
}
public void AddGump(Gump gump)
public void AddGump(BaseGump gump)
{
Gumps ??= new List<Gump>();
Gumps ??= [];
if (Gumps.Count < GumpCap)
{
@ -454,7 +454,7 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
}
}
public void RemoveGump(Gump gump)
public void RemoveGump(BaseGump gump)
{
Gumps?.Remove(gump);
}
@ -1172,7 +1172,7 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
try
{
using StreamWriter op = new StreamWriter("network-disconnects.log", true);
using var op = new StreamWriter("network-disconnects.log", true);
op.WriteLine($"# {Core.Now}");
op.WriteLine($"NetState: {ip}");