feat(opl): add ObjectPropertyList.Add(ReadOnlySpan<char>) overloads

This commit is contained in:
Kamron Batman 2026-06-22 07:47:38 -07:00
parent a28a32f46d
commit d35a527199
3 changed files with 99 additions and 0 deletions

View file

@ -0,0 +1,61 @@
using System;
using System.Buffers.Binary;
using System.Text;
using Server;
using Xunit;
namespace Server.Tests;
public class ObjectPropertyListSpanAddTests
{
// Decodes a terminated OPL buffer into (cliloc, argument) entries.
// The OPL packet is big-endian (SpanWriter default), so use BinaryPrimitives.
private static (int cliloc, string arg)[] Decode(ObjectPropertyList opl)
{
opl.Terminate();
var buffer = opl.Buffer;
var entries = new System.Collections.Generic.List<(int, string)>();
var pos = 15; // header is 15 bytes
while (true)
{
var cliloc = BinaryPrimitives.ReadInt32BigEndian(buffer.AsSpan(pos));
pos += 4;
if (cliloc == 0)
{
break;
}
var byteLen = BinaryPrimitives.ReadUInt16BigEndian(buffer.AsSpan(pos));
pos += 2;
var arg = Encoding.Unicode.GetString(buffer, pos, byteLen);
pos += byteLen;
entries.Add((cliloc, arg));
}
return entries.ToArray();
}
[Fact]
public void SpanAdd_ProducesSameBytesAsStringAdd()
{
var fromString = new ObjectPropertyList(null);
fromString.Add("Hello World");
var fromSpan = new ObjectPropertyList(null);
fromSpan.Add("Hello World".AsSpan());
Assert.Equal(Decode(fromString), Decode(fromSpan));
Assert.Equal(fromString.Hash, fromSpan.Hash);
}
[Fact]
public void SpanAdd_WithNumber_EmitsClilocAndArgument()
{
var opl = new ObjectPropertyList(null);
opl.Add(1070722, "Custom".AsSpan());
var entries = Decode(opl);
Assert.Single(entries);
Assert.Equal((1070722, "Custom"), entries[0]);
}
}

View file

@ -13,6 +13,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Runtime.CompilerServices;
using Server.Text;
@ -31,6 +32,9 @@ public interface IPropertyList : ISelfInterpolatedStringHandler
/** Convenience method for $"{text}". */
public void Add(string text);
public void Add(ReadOnlySpan<char> argument);
public void Add(int number, ReadOnlySpan<char> argument);
/** Convenience method for $"{value}". */
public void Add(int number, int value);

View file

@ -153,6 +153,40 @@ public sealed class ObjectPropertyList : IPropertyList, IDisposable
public void AddLocalized(int value) => InternalAdd(GetStringNumber(), $"{value:#}");
public void AddLocalized(int number, int value) => InternalAdd(number, $"{value:#}");
public void Add(ReadOnlySpan<char> argument) => InternalAdd(GetStringNumber(), argument);
public void Add(int number, ReadOnlySpan<char> argument) => InternalAdd(number, argument);
private void InternalAdd(int number, ReadOnlySpan<char> chars)
{
if (number == 0)
{
return;
}
if (Header == 0)
{
Header = number;
HeaderArgs = chars.ToString();
}
AddHash(number);
AddHash(string.GetHashCode(chars, StringComparison.Ordinal));
var strLength = chars.Length * 2;
var length = _bufferPos + 6 + strLength;
while (length > _buffer.Length)
{
Flush();
}
var writer = new SpanWriter(_buffer.AsSpan(_bufferPos));
writer.Write(number);
writer.Write((ushort)strLength);
writer.Write(chars, TextEncoding.UnicodeLE);
_bufferPos += writer.BytesWritten;
}
private int GetStringNumber() => _stringNumbers[_stringNumbersIndex++ % _stringNumbers.Length];
// String Interpolation