Merge branch 'main' into behavior-tree

This commit is contained in:
Kamron Batman 2022-06-19 10:32:42 -07:00
commit 62c7acf5d3
No known key found for this signature in database
GPG key ID: 5C9DFD15804B6BB8
2484 changed files with 47079 additions and 54459 deletions

View file

@ -1,16 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<Platforms>x64</Platforms>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>9</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<SkipLocalsInitiAttribute>true</SkipLocalsInitiAttribute>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
<ProjectReference Include="..\Server\Server.csproj" />
<ProjectReference Include="..\UOContent\UOContent.csproj" />
</ItemGroup>
</Project>

View file

@ -1,130 +0,0 @@
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using Server.Collections;
namespace Benchmarks
{
[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
public class BenchmarkOrderedHashSet
{
private readonly string[] _iterations = new string[16];
[IterationSetup]
public void IterationSetup()
{
for (var i = 0; i < _iterations.Length; i++)
{
_iterations[i] = i.ToString();
}
}
[Benchmark]
public int UsingList()
{
var list = new List<string>();
for (int i = 0; i < _iterations.Length / 2; i++)
{
AddIfNotPresent(list, _iterations[i]);
}
for (int i = 0; i < _iterations.Length; i++)
{
AddIfNotPresent(list, _iterations[i]);
}
for (int i = 0; i < list.Count; i++)
{
list[i].ToString();
}
return list.Count;
}
private static int AddIfNotPresent<T>(List<T> list, T item)
{
var index = list.IndexOf(item);
if (index > -1)
{
return index;
}
list.Add(item);
return list.Count - 1;
}
[Benchmark]
public int UsingOrderedHashSet()
{
var ordered = new OrderedHashSet<string>();
for (int i = 0; i < _iterations.Length / 2; i++)
{
ordered.GetOrAdd(_iterations[i]).ToString();
}
for (int i = 0; i < _iterations.Length; i++)
{
ordered.GetOrAdd(_iterations[i]).ToString();
}
foreach (var str in ordered)
{
str.ToString();
}
return ordered.Count;
}
[Benchmark]
public int UsingPooledOrderedHashSet()
{
var ordered = new PooledOrderedHashSet<string>();
for (int i = 0; i < _iterations.Length / 2; i++)
{
ordered.GetOrAdd(_iterations[i]).ToString();
}
for (int i = 0; i < _iterations.Length; i++)
{
ordered.GetOrAdd(_iterations[i]).ToString();
}
foreach (var str in ordered)
{
str.ToString();
}
return ordered.Count;
}
[Benchmark]
public int UsingHashSet()
{
var hashSet = new HashSet<(string, int)>(new OrderedStringComparer());
for (int i = 0; i < _iterations.Length / 2; i++)
{
hashSet.Add((_iterations[i], i));
}
for (int i = 0; i < _iterations.Length; i++)
{
hashSet.Add((_iterations[i], i));
}
foreach (var str in hashSet)
{
str.ToString();
}
return hashSet.Count;
}
private class OrderedStringComparer : EqualityComparer<(string, int)>
{
public override bool Equals((string, int) x, (string, int) y) => x.Item1.Equals(y.Item1, System.StringComparison.Ordinal);
public override int GetHashCode((string, int) obj) => obj.Item1.GetHashCode();
}
}
}

View file

@ -1,90 +0,0 @@
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Security.Cryptography;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using Server;
using Server.Items;
namespace Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
public class BenchmarkFeatureFlags
{
public Dictionary<Type, FeatureFlag<Item>> m_Dictionary;
public ILookup<Type, FeatureFlag<Item>> m_Lookup;
public Type[] m_TypesToLookUp;
[GlobalSetup]
public void Setup()
{
RNGCryptoServiceProvider csp = new RNGCryptoServiceProvider();
string file = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "UOContent.dll");
Assembly assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(file);
m_Dictionary = new Dictionary<Type, FeatureFlag<Item>>();
List<FeatureFlag<Item>> m_Types = new List<FeatureFlag<Item>>();
m_TypesToLookUp = new Type[100];
foreach (var type in assembly.GetTypes())
{
if (typeof(Item).IsAssignableFrom(type))
{
m_Dictionary.Add(type, new FeatureFlag<Item>());
m_Types.Add(new FeatureFlag<Item>{Type = type});
}
}
Console.WriteLine("Dictionary Size: {0}", m_Dictionary.Count);
Console.WriteLine("Lookup Size: {0}", m_Types.Count);
m_Dictionary.TrimExcess();
m_Lookup = m_Types.ToLookup(f => f.Type);
Span<byte> bytes = stackalloc byte[4];
for (int i = 0; i < 100; i++)
{
csp.GetBytes(bytes);
m_TypesToLookUp[i] = m_Types[(int)(BinaryPrimitives.ReadUInt32BigEndian(bytes) % m_Types.Count)].Type;
}
}
[Benchmark]
public FeatureFlag<Item> TestDictionary()
{
for (int i = 0; i < 100; i++)
{
m_Dictionary.TryGetValue(typeof(ExplosionPotion), out var ff);
if (i == 99)
{
return ff;
}
}
return null;
}
[Benchmark]
public FeatureFlag<Item> TestLookup()
{
FeatureFlag<Item> ff;
for (int i = 0; i < 100; i++)
{
ff = m_Lookup[typeof(ExplosionPotion)].GetEnumerator().Current;
if (i == 99)
{
return ff;
}
}
return null;
}
}
}

View file

@ -1,9 +0,0 @@
using System;
namespace Server
{
public class FeatureFlag<T> where T : Item
{
public Type Type { get; set; }
}
}

View file

@ -1,63 +0,0 @@
using System;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using Serilog;
using Serilog.Core;
namespace Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
public class BenchmarkConsoleLogging
{
private const string text = "Sample message";
private Logger logger;
private Logger asyncLogger;
[GlobalSetup]
public void GlobalSetup()
{
logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger();
asyncLogger = new LoggerConfiguration()
.WriteTo.Async(a => a.Console())
.CreateLogger();
}
[GlobalCleanup]
public void GlobalCleanup()
{
logger = null;
asyncLogger = null;
}
[Benchmark]
public void TestConsoleWriteLine()
{
for (int i = 0; i < 10000; i++)
{
Console.WriteLine(text);
}
}
[Benchmark]
public void TestSerilogConsoleSink()
{
for (int i = 0; i < 10000; i++)
{
logger.Information(text);
}
}
[Benchmark]
public void TestSerilogAsyncConsoleSink()
{
for (int i = 0; i < 10000; i++)
{
asyncLogger.Information(text);
}
}
}
}

View file

@ -1,173 +0,0 @@
using System;
using System.Buffers;
using System.IO;
using System.IO.Compression;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using Server.Collections;
using Server.Gumps;
using Server.Network;
using Server.Tests.Network;
namespace Benchmarks
{
[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
public class OutgoingGumpPacketBenchmarks
{
private static readonly byte[] _layoutBuffer = GC.AllocateUninitializedArray<byte>(0x20000);
private static readonly byte[] _stringsBuffer = GC.AllocateUninitializedArray<byte>(0x20000);
public static void CreateDisplayGump(Gump gump, out int switches, out int entries)
{
switches = 0;
entries = 0;
const bool packed = false;
var layoutWriter = new SpanWriter(_layoutBuffer);
if (!gump.Draggable)
{
layoutWriter.Write(Gump.NoMove);
}
if (!gump.Closable)
{
layoutWriter.Write(Gump.NoClose);
}
if (!gump.Disposable)
{
layoutWriter.Write(Gump.NoDispose);
}
if (!gump.Resizable)
{
layoutWriter.Write(Gump.NoResize);
}
var stringsList = new OrderedHashSet<string>(32);
foreach (var entry in gump.Entries)
{
entry.AppendTo(ref layoutWriter, stringsList, ref entries, ref switches);
}
var stringsWriter = new SpanWriter(_stringsBuffer);
foreach (var str in stringsList)
{
var s = str ?? "";
stringsWriter.Write((ushort)s.Length);
stringsWriter.WriteBigUni(s);
}
int maxLength;
if (packed)
{
var worstLayoutLength = Zlib.MaxPackSize(layoutWriter.BytesWritten);
var worstStringsLength = Zlib.MaxPackSize(stringsWriter.BytesWritten);
maxLength = 40 + worstLayoutLength + worstStringsLength;
}
else
{
maxLength = 23 + layoutWriter.BytesWritten + stringsWriter.BytesWritten;
}
var writer = new SpanWriter(maxLength);
writer.Write((byte)(packed ? 0xDD : 0xB0)); // Packet ID
writer.Seek(2, SeekOrigin.Current);
writer.Write(gump.Serial);
writer.Write(gump.TypeID);
writer.Write(gump.X);
writer.Write(gump.Y);
if (packed)
{
layoutWriter.Write((byte)0); // Layout text terminator
OutgoingGumpPackets.WritePacked(layoutWriter.Span, ref writer);
writer.Write(stringsList.Count);
OutgoingGumpPackets.WritePacked(stringsWriter.Span, ref writer);
}
else
{
writer.Write((ushort)layoutWriter.BytesWritten);
writer.Write(layoutWriter.Span);
writer.Write((ushort)stringsList.Count);
writer.Write(stringsWriter.Span);
}
writer.WritePacketLength();
layoutWriter.Dispose(); // Just in case
stringsWriter.Dispose(); // Just in case
}
public class NameChangeDeedGump : Gump
{
public NameChangeDeedGump() : base(50, 50)
{
Closable = false;
Draggable = false;
Resizable = false;
AddPage(0);
AddBlackAlpha(10, 120, 250, 85);
AddHtml(10, 125, 250, 20, Color(Center("Name Change Deed"), 0xFFFFFF));
AddLabel(73, 15, 1152, "");
AddLabel(20, 150, 0x480, "New Name:");
AddTextField(100, 150, 150, 20, 0);
AddButtonLabeled(75, 180, 1, "Submit");
}
public void AddBlackAlpha(int x, int y, int width, int height)
{
AddImageTiled(x, y, width, height, 2624);
AddAlphaRegion(x, y, width, height);
}
public void AddTextField(int x, int y, int width, int height, int index)
{
AddBackground(x - 2, y - 2, width + 4, height + 4, 0x2486);
AddTextEntry(x + 2, y + 2, width - 4, height - 4, 0, index, "");
}
public static string Center(string text) => $"<CENTER>{text}</CENTER>";
public static string Color(string text, int color) => $"<BASEFONT COLOR=#{color:X6}>{text}</BASEFONT>";
public void AddButtonLabeled(int x, int y, int buttonID, string text)
{
AddButton(x, y - 1, 4005, 4007, buttonID);
AddHtml(x + 35, y, 240, 20, Color(text, 0xFFFFFF));
}
}
private static Gump _gump;
[GlobalSetup]
public void Setup()
{
_gump = new NameChangeDeedGump();
}
[Benchmark]
public void TestNewStack()
{
CreateDisplayGump(_gump, out var _, out var _);
}
[Benchmark]
public void TestOldStack()
{
_gump.Compile().Compile(false, out var _);
}
}
}

View file

@ -1,174 +0,0 @@
using System;
using System.Buffers;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using Server;
using Server.Network;
namespace Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
public class BenchmarkPacketBroadcast
{
public static int SendUnicodeMessage(
ArraySegment<byte>[] buffer,
Serial serial, int graphic, MessageType type, int hue, int font, string lang, string name, string text
)
{
name = name?.Trim() ?? "";
text = text?.Trim() ?? "";
lang = lang?.Trim() ?? "ENU";
if (hue == 0)
{
hue = 0x3B2;
}
var writer = new CircularBufferWriter(buffer);
writer.Write((byte)0xAE);
writer.Write((ushort)(50 + text.Length * 2));
writer.Write(serial.Value);
writer.Write((short)graphic);
writer.Write((byte)type);
writer.Write((short)hue);
writer.Write((short)font);
writer.WriteAscii(lang, 4);
writer.WriteAscii(name, 30);
writer.WriteBigUniNull(text);
return writer.Position;
}
public static int CreateUnicodeMessage(
Span<byte> buffer,
Serial serial, int graphic, MessageType type, int hue, int font, string lang, string name, string text
)
{
name = name?.Trim() ?? "";
text = text?.Trim() ?? "";
lang = lang?.Trim() ?? "ENU";
if (hue == 0)
{
hue = 0x3B2;
}
var writer = new SpanWriter(buffer);
writer.Write((byte)0xAE);
writer.Write((ushort)(50 + text.Length * 2));
writer.Write(serial);
writer.Write((short)graphic);
writer.Write((byte)type);
writer.Write((short)hue);
writer.Write((short)font);
writer.WriteAscii(lang, 4);
writer.WriteAscii(name, 30);
writer.WriteBigUniNull(text);
return writer.Position;
}
private Pipe<byte>[] _pipes = new Pipe<byte>[25000];
[IterationSetup]
public void SetUp()
{
for (var i = 0; i < _pipes.Length; i++)
{
_pipes[i] = new Pipe<byte>(new byte[4096]);
}
}
[IterationCleanup]
public void CleanUp()
{
for (var i = 0; i < _pipes.Length; i++)
{
_pipes[i] = null;
}
}
[Benchmark]
public int TestCircularBuffer()
{
var text = "This is some really long text that we want to handle. It should take a little bit to encode this.";
foreach (var pipe in _pipes)
{
var result = pipe.Writer.TryGetMemory();
var length = SendUnicodeMessage(
result.Buffer,
Serial.MinusOne, -1, MessageType.Regular, 0x3B2, 3, "ENU", "System", text
);
pipe.Writer.Advance((uint)length);
}
return _pipes.Length;
}
[Benchmark]
public int TestSpanWriterFromBuffer()
{
var text = "This is some really long text that we want to handle. It should take a little bit to encode this.";
foreach (var pipe in _pipes)
{
var result = pipe.Writer.TryGetMemory();
Span<byte> buffer = result.Buffer[0];
var length = CreateUnicodeMessage(
buffer, Serial.MinusOne, -1, MessageType.Regular, 0x3B2, 3, "ENU", "System", text
);
pipe.Writer.Advance((uint)length);
}
return _pipes.Length;
}
[Benchmark]
public int TestSpanWriter()
{
var text = "This is some really long text that we want to handle. It should take a little bit to encode this.";
Span<byte> buffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLength(text)];
var length = CreateUnicodeMessage(
buffer, Serial.MinusOne, -1, MessageType.Regular, 0x3B2, 3, "ENU", "System", text
);
buffer = buffer[..length];
foreach (var pipe in _pipes)
{
var result = pipe.Writer.TryGetMemory();
result.CopyFrom(buffer);
pipe.Writer.Advance((uint)buffer.Length);
}
return _pipes.Length;
}
private static void SendUnicodeMessageWithSpan(Pipe<byte> pipe, string text)
{
Span<byte> buffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLength(text)];
var length = CreateUnicodeMessage(
buffer, Serial.MinusOne, -1, MessageType.Regular, 0x3B2, 3, "ENU", "System", text
);
buffer = buffer[..length];
var result = pipe.Writer.TryGetMemory();
result.CopyFrom(buffer);
pipe.Writer.Advance((uint)buffer.Length);
}
[Benchmark]
public int TestSpanWriterLooped()
{
var text = "This is some really long text that we want to handle. It should take a little bit to encode this.";
foreach (var pipe in _pipes)
{
SendUnicodeMessageWithSpan(pipe, text);
}
return _pipes.Length;
}
}
}

View file

@ -1,315 +0,0 @@
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Text;
using Server.Gumps;
using Server.Network;
namespace Server.Tests
{
public interface IGumpWriter
{
int TextEntries { get; set; }
int Switches { get; set; }
void AppendLayout(bool val);
void AppendLayout(int val);
void AppendLayout(uint val);
void AppendLayout(Serial serial);
void AppendLayoutNS(int val);
void AppendLayout(string text);
void AppendLayoutNS(string text);
void AppendLayout(byte[] buffer);
void WriteStrings(List<string> strings);
void Flush();
}
public sealed class CloseGump : Packet
{
public CloseGump(int typeID, int buttonID) : base(0xBF)
{
EnsureCapacity(13);
Stream.Write((short)0x04);
Stream.Write(typeID);
Stream.Write(buttonID);
}
}
public sealed class DisplayGumpPacked : Packet, IGumpWriter
{
private static readonly byte[] m_True = Gump.StringToBuffer(" 1");
private static readonly byte[] m_False = Gump.StringToBuffer(" 0");
private static readonly byte[] m_BeginTextSeparator = Gump.StringToBuffer(" @");
private static readonly byte[] m_EndTextSeparator = Gump.StringToBuffer("@");
private static readonly byte[] m_Buffer = new byte[48];
private readonly Gump m_Gump;
private readonly PacketWriter m_Layout;
private readonly PacketWriter m_Strings;
private int m_StringCount;
static DisplayGumpPacked() => m_Buffer[0] = (byte)' ';
public DisplayGumpPacked(Gump gump)
: base(0xDD)
{
m_Gump = gump;
m_Layout = PacketWriter.CreateInstance(8192);
m_Strings = PacketWriter.CreateInstance(8192);
}
public int TextEntries { get; set; }
public int Switches { get; set; }
public void AppendLayout(bool val)
{
AppendLayout(val ? m_True : m_False);
}
public void AppendLayout(int val)
{
var toString = val.ToString();
var bytes = Encoding.ASCII.GetBytes(toString, 0, toString.Length, m_Buffer, 1) + 1;
m_Layout.Write(m_Buffer, 0, bytes);
}
public void AppendLayout(uint val)
{
var toString = val.ToString();
var bytes = Encoding.ASCII.GetBytes(toString, 0, toString.Length, m_Buffer, 1) + 1;
m_Layout.Write(m_Buffer, 0, bytes);
}
public void AppendLayout(Serial serial) => AppendLayout(serial.Value);
public void AppendLayoutNS(int val)
{
var toString = val.ToString();
var bytes = Encoding.ASCII.GetBytes(toString, 0, toString.Length, m_Buffer, 1);
m_Layout.Write(m_Buffer, 1, bytes);
}
public void AppendLayoutNS(string text)
{
m_Layout.WriteAsciiFixed(text, text.Length);
}
public void AppendLayout(string text)
{
AppendLayout(m_BeginTextSeparator);
m_Layout.WriteAsciiFixed(text, text.Length);
AppendLayout(m_EndTextSeparator);
}
public void AppendLayout(byte[] buffer)
{
m_Layout.Write(buffer, 0, buffer.Length);
}
public void WriteStrings(List<string> strings)
{
m_StringCount = strings.Count;
for (var i = 0; i < strings.Count; ++i)
{
var v = strings[i] ?? "";
m_Strings.Write((ushort)v.Length);
m_Strings.WriteBigUniFixed(v, v.Length);
}
}
public void Flush()
{
EnsureCapacity(28 + (int)m_Layout.Length + (int)m_Strings.Length);
Stream.Write(m_Gump.Serial);
Stream.Write(m_Gump.TypeID);
Stream.Write(m_Gump.X);
Stream.Write(m_Gump.Y);
// Note: layout MUST be null terminated (don't listen to krrios)
m_Layout.Write((byte)0);
WritePacked(m_Layout);
Stream.Write(m_StringCount);
WritePacked(m_Strings);
PacketWriter.ReleaseInstance(m_Layout);
PacketWriter.ReleaseInstance(m_Strings);
}
private void WritePacked(PacketWriter src)
{
var buffer = src.UnderlyingStream.GetBuffer();
var length = (int)src.Length;
if (length == 0)
{
Stream.Write(0);
return;
}
var wantLength = 1 + length * 1024 / 1000;
wantLength += 4095;
wantLength &= ~4095;
var packBuffer = ArrayPool<byte>.Shared.Rent(wantLength);
var packLength = wantLength;
Zlib.Pack(packBuffer, ref packLength, buffer, length, ZlibQuality.Default);
Stream.Write(4 + packLength);
Stream.Write(length);
Stream.Write(packBuffer, 0, packLength);
ArrayPool<byte>.Shared.Return(packBuffer);
}
}
public sealed class DisplayGumpFast : Packet, IGumpWriter
{
private static readonly byte[] m_True = Gump.StringToBuffer(" 1");
private static readonly byte[] m_False = Gump.StringToBuffer(" 0");
private static readonly byte[] m_BeginTextSeparator = Gump.StringToBuffer(" @");
private static readonly byte[] m_EndTextSeparator = Gump.StringToBuffer("@");
private readonly byte[] m_Buffer = new byte[48];
private int m_LayoutLength;
public DisplayGumpFast(Gump g) : base(0xB0)
{
m_Buffer[0] = (byte)' ';
EnsureCapacity(4096);
Stream.Write(g.Serial);
Stream.Write(g.TypeID);
Stream.Write(g.X);
Stream.Write(g.Y);
Stream.Write((ushort)0xFFFF);
}
public int TextEntries { get; set; }
public int Switches { get; set; }
public void AppendLayout(bool val)
{
AppendLayout(val ? m_True : m_False);
}
public void AppendLayout(int val)
{
var toString = val.ToString();
var bytes = Encoding.ASCII.GetBytes(toString, 0, toString.Length, m_Buffer, 1) + 1;
Stream.Write(m_Buffer, 0, bytes);
m_LayoutLength += bytes;
}
public void AppendLayout(uint val)
{
var toString = val.ToString();
var bytes = Encoding.ASCII.GetBytes(toString, 0, toString.Length, m_Buffer, 1) + 1;
Stream.Write(m_Buffer, 0, bytes);
m_LayoutLength += bytes;
}
public void AppendLayout(Serial serial) => AppendLayout(serial.Value);
public void AppendLayoutNS(int val)
{
var toString = val.ToString();
var bytes = Encoding.ASCII.GetBytes(toString, 0, toString.Length, m_Buffer, 1);
Stream.Write(m_Buffer, 1, bytes);
m_LayoutLength += bytes;
}
public void AppendLayoutNS(string text)
{
var length = text.Length;
Stream.WriteAsciiFixed(text, length);
m_LayoutLength += length;
}
public void AppendLayout(string text)
{
AppendLayout(m_BeginTextSeparator);
var length = text.Length;
Stream.WriteAsciiFixed(text, length);
m_LayoutLength += length;
AppendLayout(m_EndTextSeparator);
}
public void AppendLayout(byte[] buffer)
{
var length = buffer.Length;
Stream.Write(buffer, 0, length);
m_LayoutLength += length;
}
public void WriteStrings(List<string> text)
{
Stream.Seek(19, SeekOrigin.Begin);
Stream.Write((ushort)m_LayoutLength);
Stream.Seek(0, SeekOrigin.End);
Stream.Write((ushort)text.Count);
for (var i = 0; i < text.Count; ++i)
{
var v = text[i] ?? "";
int length = (ushort)v.Length;
Stream.Write((ushort)length);
Stream.WriteBigUniFixed(v, length);
}
}
public void Flush()
{
}
}
public sealed class DisplaySignGump : Packet
{
public DisplaySignGump(Serial serial, int gumpID, string unknown, string caption) : base(0x8B)
{
unknown ??= "";
caption ??= "";
EnsureCapacity(15 + unknown.Length + caption.Length);
Stream.Write(serial);
Stream.Write((short)gumpID);
Stream.Write((short)(unknown.Length + 1));
Stream.WriteAsciiNull(unknown);
Stream.Write((short)(caption.Length + 1));
Stream.WriteAsciiNull(caption);
}
}
}

View file

@ -1,476 +0,0 @@
using System.Collections.Generic;
using Server.Gumps;
using Server.Network;
namespace Server.Tests.Network
{
public static class GumpUtilities
{
private static readonly byte[] m_BeginLayout = Gump.StringToBuffer("{ ");
private static readonly byte[] m_EndLayout = Gump.StringToBuffer(" }");
public static Packet Compile(this Gump g, NetState ns = null)
{
IGumpWriter disp = new DisplayGumpFast(g);
// IGumpWriter disp = new DisplayGumpPacked(g);
if (!g.Draggable)
{
disp.AppendLayout(Gump.NoMove);
}
if (!g.Closable)
{
disp.AppendLayout(Gump.NoClose);
}
if (!g.Disposable)
{
disp.AppendLayout(Gump.NoDispose);
}
if (!g.Resizable)
{
disp.AppendLayout(Gump.NoResize);
}
var count = g.Entries.Count;
var strings = new List<string>();
for (var i = 0; i < count; ++i)
{
var e = g.Entries[i];
disp.AppendLayout(m_BeginLayout);
e.AppendToByType(disp, strings);
disp.AppendLayout(m_EndLayout);
}
disp.WriteStrings(strings);
disp.Flush();
return (Packet)disp;
}
public static int Intern(this List<string> strings, string value)
{
var indexOf = strings.IndexOf(value);
if (indexOf >= 0)
{
return indexOf;
}
strings.Add(value);
return strings.Count - 1;
}
public static void AppendToByType(this GumpEntry e, IGumpWriter disp, List<string> strings)
{
switch (e)
{
case GumpAlphaRegion g:
{
g.AppendTo(disp, strings);
break;
}
case GumpBackground g:
{
g.AppendTo(disp, strings);
break;
}
case GumpButton g:
{
g.AppendTo(disp, strings);
break;
}
case GumpCheck g:
{
g.AppendTo(disp, strings);
break;
}
case GumpGroup g:
{
g.AppendTo(disp, strings);
break;
}
case GumpECHandleInput g:
{
g.AppendTo(disp, strings);
break;
}
case GumpHtml g:
{
g.AppendTo(disp, strings);
break;
}
case GumpHtmlLocalized g:
{
g.AppendTo(disp, strings);
break;
}
case GumpImage g:
{
g.AppendTo(disp, strings);
break;
}
case GumpImageTileButton g:
{
g.AppendTo(disp, strings);
break;
}
case GumpImageTiled g:
{
g.AppendTo(disp, strings);
break;
}
case GumpItem g:
{
g.AppendTo(disp, strings);
break;
}
case GumpItemProperty g:
{
g.AppendTo(disp, strings);
break;
}
case GumpLabel g:
{
g.AppendTo(disp, strings);
break;
}
case GumpLabelCropped g:
{
g.AppendTo(disp, strings);
break;
}
case GumpMasterGump g:
{
g.AppendTo(disp, strings);
break;
}
case GumpPage g:
{
g.AppendTo(disp, strings);
break;
}
case GumpRadio g:
{
g.AppendTo(disp, strings);
break;
}
case GumpSpriteImage g:
{
g.AppendTo(disp, strings);
break;
}
case GumpTextEntry g:
{
g.AppendTo(disp, strings);
break;
}
case GumpTextEntryLimited g:
{
g.AppendTo(disp, strings);
break;
}
case GumpTooltip g:
{
g.AppendTo(disp, strings);
break;
}
}
}
public static void AppendTo(this GumpAlphaRegion g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpAlphaRegion.LayoutName);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
disp.AppendLayout(g.Height);
}
public static void AppendTo(this GumpBackground g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpBackground.LayoutName);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.GumpID);
disp.AppendLayout(g.Width);
disp.AppendLayout(g.Height);
}
public static void AppendTo(this GumpButton g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpButton.LayoutName);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.NormalID);
disp.AppendLayout(g.PressedID);
disp.AppendLayout((int)g.Type);
disp.AppendLayout(g.Param);
disp.AppendLayout(g.ButtonID);
}
public static void AppendTo(this GumpCheck g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpButton.LayoutName);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.InactiveID);
disp.AppendLayout(g.ActiveID);
disp.AppendLayout(g.InitialState);
disp.AppendLayout(g.SwitchID);
disp.Switches++;
}
public static void AppendTo(this GumpGroup g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpGroup.LayoutName);
disp.AppendLayout(g.Group);
}
public static void AppendTo(this GumpECHandleInput g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpECHandleInput.LayoutName);
}
public static void AppendTo(this GumpHtml g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpHtml.LayoutName);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
disp.AppendLayout(g.Height);
disp.AppendLayout(strings.Intern(g.Text));
disp.AppendLayout(g.Background);
disp.AppendLayout(g.Scrollbar);
}
public static void AppendTo(this GumpHtmlLocalized g, IGumpWriter disp, List<string> strings)
{
switch (g.Type)
{
case GumpHtmlLocalizedType.Plain:
{
disp.AppendLayout(GumpHtmlLocalized.LayoutNamePlain);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
disp.AppendLayout(g.Height);
disp.AppendLayout(g.Number);
disp.AppendLayout(g.Background);
disp.AppendLayout(g.Scrollbar);
break;
}
case GumpHtmlLocalizedType.Color:
{
disp.AppendLayout(GumpHtmlLocalized.LayoutNameColor);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
disp.AppendLayout(g.Height);
disp.AppendLayout(g.Number);
disp.AppendLayout(g.Background);
disp.AppendLayout(g.Scrollbar);
disp.AppendLayout(g.Color);
break;
}
case GumpHtmlLocalizedType.Args:
{
disp.AppendLayout(GumpHtmlLocalized.LayoutNameArgs);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
disp.AppendLayout(g.Height);
disp.AppendLayout(g.Background);
disp.AppendLayout(g.Scrollbar);
disp.AppendLayout(g.Color);
disp.AppendLayout(g.Number);
disp.AppendLayout(g.Args);
break;
}
}
}
public static void AppendTo(this GumpImage g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpImage.LayoutName);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.GumpID);
if (g.Hue != 0)
{
disp.AppendLayout(GumpImage.HueEquals);
disp.AppendLayoutNS(g.Hue);
}
if (!string.IsNullOrEmpty(g.Class))
{
disp.AppendLayout(GumpImage.ClassEquals);
disp.AppendLayoutNS(g.Class);
}
}
public static void AppendTo(this GumpImageTileButton g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpImageTileButton.LayoutName);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.NormalID);
disp.AppendLayout(g.PressedID);
disp.AppendLayout((int)g.Type);
disp.AppendLayout(g.Param);
disp.AppendLayout(g.ButtonID);
disp.AppendLayout(g.ItemID);
disp.AppendLayout(g.Hue);
disp.AppendLayout(g.Width);
disp.AppendLayout(g.Height);
if (g.LocalizedTooltip > 0)
{
disp.AppendLayout(GumpImageTileButton.LayoutTooltip);
disp.AppendLayout(g.LocalizedTooltip);
}
}
public static void AppendTo(this GumpImageTiled g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpImageTiled.LayoutName);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
disp.AppendLayout(g.Height);
disp.AppendLayout(g.GumpID);
}
public static void AppendTo(this GumpItem g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(g.Hue == 0 ? GumpItem.LayoutName : GumpItem.LayoutNameHue);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.ItemID);
if (g.Hue != 0)
{
disp.AppendLayout(g.Hue);
}
}
public static void AppendTo(this GumpItemProperty g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpItemProperty.LayoutName);
disp.AppendLayout(g.Serial);
}
public static void AppendTo(this GumpLabel g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpLabel.LayoutName);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Hue);
disp.AppendLayout(strings.Intern(g.Text));
}
public static void AppendTo(this GumpLabelCropped g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpLabelCropped.LayoutName);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
disp.AppendLayout(g.Height);
disp.AppendLayout(g.Hue);
disp.AppendLayout(strings.Intern(g.Text));
}
public static void AppendTo(this GumpMasterGump g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpMasterGump.LayoutName);
disp.AppendLayout(g.GumpID);
}
public static void AppendTo(this GumpPage g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpPage.LayoutName);
disp.AppendLayout(g.Page);
}
public static void AppendTo(this GumpRadio g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpRadio.LayoutName);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.InactiveID);
disp.AppendLayout(g.ActiveID);
disp.AppendLayout(g.InitialState);
disp.AppendLayout(g.SwitchID);
disp.Switches++;
}
public static void AppendTo(this GumpSpriteImage g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpSpriteImage.LayoutName);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.GumpID);
disp.AppendLayout(g.Width);
disp.AppendLayout(g.Height);
disp.AppendLayout(g.SX);
disp.AppendLayout(g.SY);
}
public static void AppendTo(this GumpTextEntry g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpTextEntry.LayoutName);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
disp.AppendLayout(g.Height);
disp.AppendLayout(g.Hue);
disp.AppendLayout(g.EntryID);
disp.AppendLayout(strings.Intern(g.InitialText));
disp.TextEntries++;
}
public static void AppendTo(this GumpTextEntryLimited g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpTextEntryLimited.LayoutName);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
disp.AppendLayout(g.Height);
disp.AppendLayout(g.Hue);
disp.AppendLayout(g.EntryID);
disp.AppendLayout(strings.Intern(g.InitialText));
disp.AppendLayout(g.Size);
disp.TextEntries++;
}
public static void AppendTo(this GumpTooltip g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpTooltip.LayoutName);
disp.AppendLayout(g.Number);
if (!string.IsNullOrEmpty(g.Args))
{
disp.AppendLayout(g.Args);
}
}
}
}

View file

@ -1,265 +0,0 @@
using System;
using System.Buffers;
using System.Diagnostics;
using System.IO;
using Server.Diagnostics;
namespace Server.Network
{
public abstract class Packet
{
private const int CompressorBufferSize = 0x10000;
private readonly int m_Length;
private byte[] m_CompiledBuffer;
private int m_CompiledLength;
private State m_State;
protected Packet(int packetID)
{
PacketID = packetID;
if (Core.Profiling)
{
var prof = PacketSendProfile.Acquire(PacketID);
prof.Increment();
}
}
protected Packet(int packetID, int length)
{
PacketID = packetID;
m_Length = length;
Stream = PacketWriter.CreateInstance(length); // new PacketWriter( length );
Stream.Write((byte)packetID);
if (Core.Profiling)
{
var prof = PacketSendProfile.Acquire(PacketID);
prof.Increment();
}
}
public int PacketID { get; }
public PacketWriter Stream { get; protected set; }
public void EnsureCapacity(int length)
{
Stream = PacketWriter.CreateInstance(length); // new PacketWriter( length );
Stream.Write((byte)PacketID);
Stream.Write((short)0);
}
public static Packet SetStatic(Packet p)
{
p.SetStatic();
return p;
}
public static Packet Acquire(Packet p)
{
p.Acquire();
return p;
}
public static void Release(ref Packet p)
{
p?.Release();
p = null;
}
public static void Release(Packet p)
{
p?.Release();
}
public void SetStatic()
{
m_State |= State.Static | State.Acquired;
}
public void Acquire()
{
m_State |= State.Acquired;
}
public void OnSend()
{
if ((m_State & (State.Acquired | State.Static)) == 0)
{
Free();
}
}
private void Free()
{
if (m_CompiledBuffer == null)
{
return;
}
if ((m_State & State.Buffered) != 0)
{
ArrayPool<byte>.Shared.Return(m_CompiledBuffer);
}
m_State &= ~(State.Static | State.Acquired | State.Buffered);
m_CompiledBuffer = null;
}
public void Release()
{
if ((m_State & State.Acquired) != 0)
{
Free();
}
}
private readonly object _object = new();
public byte[] Compile(bool compress, out int length)
{
lock (_object)
{
if (m_CompiledBuffer == null)
{
if ((m_State & State.Accessed) == 0)
{
m_State |= State.Accessed;
}
else
{
if ((m_State & State.Warned) == 0)
{
m_State |= State.Warned;
try
{
using var op = new StreamWriter("net_opt.log", true);
op.WriteLine("Redundant compile for packet {0}, use Acquire() and Release()", GetType());
op.WriteLine(new StackTrace());
}
catch
{
// ignored
}
}
m_CompiledBuffer = Array.Empty<byte>();
m_CompiledLength = 0;
length = m_CompiledLength;
return m_CompiledBuffer;
}
InternalCompile(compress);
}
length = m_CompiledLength;
return m_CompiledBuffer;
}
}
private void InternalCompile(bool compress)
{
if (m_Length == 0)
{
var streamLen = Stream.Length;
Stream.Seek(1, SeekOrigin.Begin);
Stream.Write((ushort)streamLen);
}
else if (Stream.Length != m_Length)
{
var diff = (int)Stream.Length - m_Length;
Console.WriteLine(
"Packet: 0x{0:X2}: Bad packet length! ({1}{2} bytes)",
PacketID,
diff >= 0 ? "+" : "",
diff
);
}
var ms = Stream.UnderlyingStream;
m_CompiledBuffer = ms.GetBuffer();
var length = (int)ms.Length;
if (compress)
{
var compressorBuffer = new byte[CompressorBufferSize];
var compressedLength = NetworkCompression.Compress(m_CompiledBuffer.AsSpan(0, length), compressorBuffer);
if (length <= 0)
{
Console.WriteLine(
"Warning: Compression buffer overflowed on packet 0x{0:X2} ('{1}') (length={2})",
PacketID,
GetType().Name,
length
);
using var op = new StreamWriter("compression_overflow.log", true);
op.WriteLine(
"{0} Warning: Compression buffer overflowed on packet 0x{1:X2} ('{2}') (length={3})",
Core.Now,
PacketID,
GetType().Name,
length
);
op.WriteLine(new StackTrace());
}
else
{
m_CompiledBuffer = compressorBuffer;
m_CompiledLength = compressedLength;
}
}
else
{
m_CompiledLength = length;
}
if (m_CompiledLength > 0)
{
var old = m_CompiledBuffer;
if ((m_State & State.Static) != 0)
{
m_CompiledBuffer = new byte[m_CompiledLength];
}
else
{
// Release it later using Release()
m_CompiledBuffer = ArrayPool<byte>.Shared.Rent(m_CompiledLength);
m_State |= State.Buffered;
}
Buffer.BlockCopy(old, 0, m_CompiledBuffer, 0, m_CompiledLength);
if (compress)
{
ArrayPool<byte>.Shared.Return(old);
}
}
PacketWriter.ReleaseInstance(Stream);
Stream = null;
}
[Flags]
private enum State
{
Inactive = 0x00,
Static = 0x01,
Acquired = 0x02,
Accessed = 0x04,
Buffered = 0x08,
Warned = 0x10
}
}
}

View file

@ -1,31 +0,0 @@
using System;
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
using Server;
using Server.Network;
namespace Benchmarks
{
public static class PacketTestUtilities
{
public static Span<byte> Compile(this Packet p) =>
p.Compile(false, out var length).AsSpan(0, length);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Write(this Span<byte> data, ref int pos, Serial serial)
{
BinaryPrimitives.WriteUInt32BigEndian(data.Slice(pos, 4), serial.Value);
pos += 4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Write(this Span<byte> data, ref int pos, ushort value)
{
BinaryPrimitives.WriteUInt16BigEndian(data.Slice(pos, 2), value);
pos += 2;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Write(this Span<byte> data, ref int pos, byte value) => data[pos++] = value;
}
}

View file

@ -1,354 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Text;
namespace Server.Network
{
/// <summary>
/// Provides functionality for writing primitive binary data.
/// </summary>
public class PacketWriter
{
private static readonly ConcurrentQueue<PacketWriter> m_Pool = new();
/// <summary>
/// Internal format buffer.
/// </summary>
private readonly byte[] m_Buffer = new byte[4];
private int m_Capacity;
/// <summary>
/// Instantiates a new PacketWriter instance with a given capacity.
/// </summary>
/// <param name="capacity">Initial capacity for the internal stream.</param>
public PacketWriter(int capacity = 32)
{
UnderlyingStream = new MemoryStream(capacity);
m_Capacity = capacity;
}
/// <summary>
/// Gets the total stream length.
/// </summary>
public long Length => UnderlyingStream.Length;
/// <summary>
/// Gets or sets the current stream position.
/// </summary>
public long Position
{
get => UnderlyingStream.Position;
set => UnderlyingStream.Position = value;
}
/// <summary>
/// The internal stream used by this PacketWriter instance.
/// </summary>
public MemoryStream UnderlyingStream { get; }
public static PacketWriter CreateInstance(int capacity = 32)
{
if (m_Pool.TryDequeue(out var pw))
{
pw.m_Capacity = capacity;
pw.UnderlyingStream.SetLength(0);
return pw;
}
return new PacketWriter(capacity);
}
public static void ReleaseInstance(PacketWriter pw)
{
m_Pool.Enqueue(pw);
}
/// <summary>
/// Writes a 1-byte boolean value to the underlying stream. False is represented by 0, true by 1.
/// </summary>
public void Write(bool value)
{
UnderlyingStream.WriteByte((byte)(value ? 1 : 0));
}
/// <summary>
/// Writes a 1-byte unsigned integer value to the underlying stream.
/// </summary>
public void Write(byte value)
{
UnderlyingStream.WriteByte(value);
}
/// <summary>
/// Writes a 1-byte signed integer value to the underlying stream.
/// </summary>
public void Write(sbyte value)
{
UnderlyingStream.WriteByte((byte)value);
}
/// <summary>
/// Writes a 2-byte signed integer value to the underlying stream.
/// </summary>
public void Write(short value)
{
m_Buffer[0] = (byte)(value >> 8);
m_Buffer[1] = (byte)value;
UnderlyingStream.Write(m_Buffer, 0, 2);
}
/// <summary>
/// Writes a 2-byte unsigned integer value to the underlying stream.
/// </summary>
public void Write(ushort value)
{
m_Buffer[0] = (byte)(value >> 8);
m_Buffer[1] = (byte)value;
UnderlyingStream.Write(m_Buffer, 0, 2);
}
public void Write(Serial serial) => Write(serial.Value);
/// <summary>
/// Writes a 4-byte signed integer value to the underlying stream.
/// </summary>
public void Write(int value)
{
m_Buffer[0] = (byte)(value >> 24);
m_Buffer[1] = (byte)(value >> 16);
m_Buffer[2] = (byte)(value >> 8);
m_Buffer[3] = (byte)value;
UnderlyingStream.Write(m_Buffer, 0, 4);
}
/// <summary>
/// Writes a 4-byte unsigned integer value to the underlying stream.
/// </summary>
public void Write(uint value)
{
m_Buffer[0] = (byte)(value >> 24);
m_Buffer[1] = (byte)(value >> 16);
m_Buffer[2] = (byte)(value >> 8);
m_Buffer[3] = (byte)value;
UnderlyingStream.Write(m_Buffer, 0, 4);
}
/// <summary>
/// Writes a sequence of bytes to the underlying stream
/// </summary>
public void Write(byte[] buffer, int offset, int size)
{
UnderlyingStream.Write(buffer, offset, size);
}
/// <summary>
/// Writes a fixed-length ASCII-encoded string value to the underlying stream. To fit (size), the string content is either
/// truncated or padded with null characters.
/// </summary>
public void WriteAsciiFixed(string value, int size)
{
if (value == null)
{
Console.WriteLine("Network: Attempted to WriteAsciiFixed() with null value");
value = string.Empty;
}
var length = value.Length;
UnderlyingStream.SetLength(UnderlyingStream.Length + size);
if (length >= size)
{
UnderlyingStream.Position +=
Encoding.ASCII.GetBytes(value, 0, size, UnderlyingStream.GetBuffer(), (int)UnderlyingStream.Position);
}
else
{
Encoding.ASCII.GetBytes(value, 0, length, UnderlyingStream.GetBuffer(), (int)UnderlyingStream.Position);
UnderlyingStream.Position += size;
}
}
/// <summary>
/// Writes a dynamic-length ASCII-encoded string value to the underlying stream, followed by a 1-byte null character.
/// </summary>
public void WriteAsciiNull(string value)
{
if (value == null)
{
Console.WriteLine("Network: Attempted to WriteAsciiNull() with null value");
value = string.Empty;
}
var length = value.Length;
UnderlyingStream.SetLength(UnderlyingStream.Length + length + 1);
Encoding.ASCII.GetBytes(value, 0, length, UnderlyingStream.GetBuffer(), (int)UnderlyingStream.Position);
UnderlyingStream.Position += length + 1;
}
/// <summary>
/// Writes a dynamic-length little-endian unicode string value to the underlying stream, followed by a 2-byte null
/// character.
/// </summary>
public void WriteLittleUniNull(string value)
{
if (value == null)
{
Console.WriteLine("Network: Attempted to WriteLittleUniNull() with null value");
value = string.Empty;
}
var length = value.Length;
UnderlyingStream.SetLength(UnderlyingStream.Length + (length + 1) * 2);
UnderlyingStream.Position +=
Encoding.Unicode.GetBytes(value, 0, length, UnderlyingStream.GetBuffer(), (int)UnderlyingStream.Position);
UnderlyingStream.Position += 2;
}
/// <summary>
/// Writes a fixed-length little-endian unicode string value to the underlying stream. To fit (size), the string content is
/// either truncated or padded with null characters.
/// </summary>
public void WriteLittleUniFixed(string value, int size)
{
if (value == null)
{
Console.WriteLine("Network: Attempted to WriteLittleUniFixed() with null value");
value = string.Empty;
}
var length = value.Length;
size *= 2;
UnderlyingStream.SetLength(UnderlyingStream.Length + size);
if (length * 2 >= size)
{
UnderlyingStream.Position +=
Encoding.Unicode.GetBytes(
value,
0,
size / 2,
UnderlyingStream.GetBuffer(),
(int)UnderlyingStream.Position
);
}
else
{
Encoding.Unicode.GetBytes(value, 0, length, UnderlyingStream.GetBuffer(), (int)UnderlyingStream.Position);
UnderlyingStream.Position += size;
}
}
/// <summary>
/// Writes a dynamic-length big-endian unicode string value to the underlying stream, followed by a 2-byte null character.
/// </summary>
public void WriteBigUniNull(string value)
{
if (value == null)
{
Console.WriteLine("Network: Attempted to WriteBigUniNull() with null value");
value = string.Empty;
}
var length = value.Length;
UnderlyingStream.SetLength(UnderlyingStream.Length + (length + 1) * 2);
UnderlyingStream.Position +=
Encoding.BigEndianUnicode.GetBytes(
value,
0,
length,
UnderlyingStream.GetBuffer(),
(int)UnderlyingStream.Position
);
UnderlyingStream.Position += 2;
}
/// <summary>
/// Writes a fixed-length big-endian unicode string value to the underlying stream. To fit (size), the string content is
/// either truncated or padded with null characters.
/// </summary>
public void WriteBigUniFixed(string value, int size)
{
if (value == null)
{
Console.WriteLine("Network: Attempted to WriteBigUniFixed() with null value");
value = string.Empty;
}
var length = value.Length;
size *= 2;
UnderlyingStream.SetLength(UnderlyingStream.Length + size);
if (length * 2 >= size)
{
UnderlyingStream.Position +=
Encoding.BigEndianUnicode.GetBytes(
value,
0,
size / 2,
UnderlyingStream.GetBuffer(),
(int)UnderlyingStream.Position
);
}
else
{
Encoding.BigEndianUnicode.GetBytes(
value,
0,
length,
UnderlyingStream.GetBuffer(),
(int)UnderlyingStream.Position
);
UnderlyingStream.Position += size;
}
}
/// <summary>
/// Fills the stream from the current position up to (capacity) with 0x00's
/// </summary>
public void Fill()
{
Fill(m_Capacity - UnderlyingStream.Length);
}
/// <summary>
/// Writes a number of 0x00 byte values to the underlying stream.
/// </summary>
public void Fill(long length)
{
if (UnderlyingStream.Position == UnderlyingStream.Length)
{
UnderlyingStream.SetLength(UnderlyingStream.Length + length);
UnderlyingStream.Seek(0, SeekOrigin.End);
}
else
{
UnderlyingStream.Write(new byte[length], 0, (int)length);
}
}
/// <summary>
/// Offsets the current position from an origin.
/// </summary>
public long Seek(long offset, SeekOrigin origin) => UnderlyingStream.Seek(offset, origin);
/// <summary>
/// Gets the entire stream content as a byte array.
/// </summary>
public byte[] ToArray() => UnderlyingStream.ToArray();
}
}

View file

@ -1,32 +0,0 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using Server.Text;
namespace Benchmarks.BenchmarkText
{
[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
public class BenchmarkTextEncoding
{
private const string text =
"This is supposed to be a really long text Ƞă¼ÖƄŭȘú😅¥♓ǵDƤĂ😋ġǁ⚕😐'ƿī😪_l" +
"This is supposed to be a really long text Ƞă¼ÖƄŭȘú😅¥♓ǵDƤĂ😋ġǁ⚕😐'ƿī😪_l" +
"This is supposed to be a really long text Ƞă¼ÖƄŭȘú😅¥♓ǵDƤĂ😋ġǁ⚕😐'ƿī😪_l" +
"This is supposed to be a really long text Ƞă¼ÖƄŭȘú😅¥♓ǵDƤĂ😋ġǁ⚕😐'ƿī😪_l" +
"This is supposed to be a really long text Ƞă¼ÖƄŭȘú😅¥♓ǵDƤĂ😋ġǁ⚕😐'ƿī😪_l";
[Benchmark]
public byte[] TestEncodingOldReturnBytes()
{
var bytes = TextEncoding.UTF8.GetBytes(text);
return bytes;
}
[Benchmark]
public byte[] TestEncodingNewReturnBytes()
{
var bytes = text.GetBytesUtf8();
return bytes;
}
}
}

View file

@ -1,87 +0,0 @@
using System.Buffers;
using System.Text;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using Server.Buffers;
namespace Benchmarks.BenchmarkUtilities
{
[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
public class BenchmarkStringHelpers
{
private readonly string[] names =
{
"Kamron", "Owyn", "Luthius", "Jaedan", "Vorspire", "other people",
"Kamron-2", "Owyn-2", "Luthius-2", "Jaedan-2", "Vorspire-2", "other people too"
};
private int length;
[GlobalSetup]
public void Setup()
{
var chrs = ArrayPool<char>.Shared.Rent(65535);
ArrayPool<char>.Shared.Return(chrs);
length = 0;
for (int i = 0; i < names.Length; i++)
{
length += names.Length;
}
length += 2 * (names.Length - 1) + 3;
}
[Benchmark]
public string BenchmarkStringBuilder()
{
var sb = new StringBuilder();
for (var i = 0; i < names.Length; i++)
{
if (i > 0)
{
sb.Append(i == names.Length - 1 ? ", and" : ", ");
}
sb.Append(names[i]);
}
return sb.ToString();
}
[Benchmark]
public string BenchmarkValueStringBuilderWithStack()
{
using var sb = new ValueStringBuilder(stackalloc char[length]);
for (var i = 0; i < names.Length; i++)
{
if (i > 0)
{
sb.Append(i == names.Length - 1 ? ", and" : ", ");
}
sb.Append(names[i]);
}
return sb.ToString();
}
[Benchmark]
public string BenchmarkValueStringBuilderWithRentedBuffer()
{
using var sb = new ValueStringBuilder(stackalloc char[32]);
for (var i = 0; i < names.Length; i++)
{
if (i > 0)
{
sb.Append(i == names.Length - 1 ? ", and" : ", ");
}
sb.Append(names[i]);
}
return sb.ToString();
}
}
}

View file

@ -1,3 +0,0 @@
<Project>
<!-- Only here so that the default Directory.Build.props will not be used. -->
</Project>

View file

@ -1,19 +0,0 @@
using BenchmarkDotNet.Running;
namespace Benchmarks
{
public static class Program
{
private static void Main(string[] args)
{
// var featureFlags = BenchmarkRunner.Run<BenchmarkFeatureFlags>();
// var packetConstruction = BenchmarkRunner.Run<BenchmarkPacketConstruction>();
// var broadcast = BenchmarkRunner.Run<BenchmarkPacketBroadcast>();
// var stringHelpers = BenchmarkRunner.Run<BenchmarkStringHelpers>();
var indexList = BenchmarkRunner.Run<BenchmarkOrderedHashSet>();
// var textEncoding = BenchmarkRunner.Run<BenchmarkTextEncoding>();
// var logging = BenchmarkRunner.Run<BenchmarkConsoleLogging>();
// var gumpPacket = BenchmarkRunner.Run<OutgoingGumpPacketBenchmarks>();
}
}
}

View file

@ -1,91 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: EntityJsonGenerator.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.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using SerializableMigration;
namespace SerializationGenerator
{
[Generator]
public class EntitySerializationGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{
context.RegisterForSyntaxNotifications(() => new SerializerSyntaxReceiver());
}
public void Execute(GeneratorExecutionContext context)
{
if (context.SyntaxContextReceiver is not SerializerSyntaxReceiver receiver)
{
return;
}
var jsonOptions = SerializableMigrationSchema.GetJsonSerializerOptions();
// List of types that _will_ become ISerializable
var serializableList = receiver.SerializableList;
var embeddedSerializableList = receiver.EmbeddedSerializableList;
foreach (var (classSymbol, (serializableAttr, fieldsList)) in receiver.ClassAndFields)
{
if (serializableAttr == null)
{
continue;
}
string classSource = context.GenerateSerializationPartialClass(
classSymbol,
serializableAttr,
false,
fieldsList.ToImmutableArray(),
jsonOptions,
serializableList,
embeddedSerializableList
);
if (classSource != null)
{
context.AddSource($"{classSymbol.ToDisplayString()}.Serialization.cs", SourceText.From(classSource, Encoding.UTF8));
}
}
foreach (var (classSymbol, (embeddedSerializableAttr, fieldsList)) in receiver.EmbeddedClassAndFields)
{
if (embeddedSerializableAttr == null)
{
continue;
}
string classSource = context.GenerateSerializationPartialClass(
classSymbol,
embeddedSerializableAttr,
true,
fieldsList.ToImmutableArray(),
jsonOptions,
serializableList,
embeddedSerializableList
);
if (classSource != null)
{
context.AddSource($"{classSymbol.ToDisplayString()}.Serialization.cs", SourceText.From(classSource, Encoding.UTF8));
}
}
}
}
}

View file

@ -1,4 +0,0 @@
namespace System.Runtime.CompilerServices
{
internal static class IsExternalInit {}
}

View file

@ -1,436 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializableEntityGeneration.Class.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.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using Microsoft.CodeAnalysis;
using SerializableMigration;
namespace SerializationGenerator
{
public static partial class SerializableEntityGeneration
{
public static string GenerateSerializationPartialClass(
this GeneratorExecutionContext context,
INamedTypeSymbol classSymbol,
AttributeData serializableAttr,
bool embedded,
ImmutableArray<ISymbol> fieldsAndProperties,
JsonSerializerOptions jsonSerializerOptions,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes
)
{
var version = (int)serializableAttr.ConstructorArguments[0].Value!;
var migrations = context.GetMigrationsByAnalyzerConfig(
classSymbol,
version,
jsonSerializerOptions
);
return context.Compilation.GenerateSerializationPartialClass(
classSymbol,
serializableAttr,
null, // Do not generate schema
embedded,
null,
migrations.ToImmutableArray(),
fieldsAndProperties,
serializableTypes,
embeddedSerializableTypes
);
}
public static string GenerateSerializationPartialClass(
this Compilation compilation,
INamedTypeSymbol classSymbol,
AttributeData serializableAttr,
string? migrationPath,
bool embedded,
JsonSerializerOptions? jsonSerializerOptions,
ImmutableArray<ISymbol> fieldsAndProperties,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes
)
{
var version = (int)serializableAttr.ConstructorArguments[0].Value!;
var migrations = SerializableMigrationSchema.GetMigrations(
classSymbol,
version,
migrationPath,
jsonSerializerOptions
);
return compilation.GenerateSerializationPartialClass(
classSymbol,
serializableAttr,
migrationPath,
embedded,
jsonSerializerOptions,
migrations.ToImmutableArray(),
fieldsAndProperties,
serializableTypes,
embeddedSerializableTypes
);
}
public static string GenerateSerializationPartialClass(
this Compilation compilation,
INamedTypeSymbol classSymbol,
AttributeData serializableAttr,
string? migrationPath,
bool embedded,
JsonSerializerOptions? jsonSerializerOptions,
ImmutableArray<SerializableMetadata> migrations,
ImmutableArray<ISymbol> fieldsAndProperties,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes
)
{
var serializableFieldAttribute =
compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_FIELD_ATTRIBUTE);
var serializableFieldAttrAttribute =
compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_FIELD_ATTR_ATTRIBUTE);
var serializableInterface =
compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_INTERFACE);
var parentSerializableAttribute =
compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_PARENT_ATTRIBUTE);
var serializableFieldSaveFlagAttribute =
compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_FIELD_SAVE_FLAG_ATTRIBUTE);
var serializableFieldDefaultAttribute =
compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_FIELD_DEFAULT_ATTRIBUTE);
// If we have a parent that is or derives from ISerializable, then we are in override
var isOverride = classSymbol.BaseType.ContainsInterface(serializableInterface);
if (!(embedded || isOverride || classSymbol.ContainsInterface(serializableInterface)))
{
return null;
}
var isRawSerializable = classSymbol.HasRawSerializableInterface(compilation, ImmutableArray<INamedTypeSymbol>.Empty);
var version = (int)serializableAttr.ConstructorArguments[0].Value!;
var encodedVersion = (bool)serializableAttr.ConstructorArguments[1].Value!;
// Let's find out if we need to do serialization flags
var serializableFieldSaveFlags = new SortedDictionary<int, SerializableFieldSaveFlagMethods>();
foreach (var m in classSymbol.GetMembers().OfType<IMethodSymbol>())
{
var getSaveFlagAttribute = m.GetAttribute(serializableFieldSaveFlagAttribute);
var getDefaultValueAttribute = m.GetAttribute(serializableFieldDefaultAttribute);
if (getSaveFlagAttribute == null && getDefaultValueAttribute == null)
{
continue;
}
var attrCtorArgs = getSaveFlagAttribute?.ConstructorArguments ?? getDefaultValueAttribute.ConstructorArguments;
var order = (int)attrCtorArgs[0].Value!;
serializableFieldSaveFlags.TryGetValue(order, out var saveFlagMethods);
serializableFieldSaveFlags[order] = new SerializableFieldSaveFlagMethods
{
DetermineFieldShouldSerialize = getSaveFlagAttribute != null ? m : saveFlagMethods?.DetermineFieldShouldSerialize,
GetFieldDefaultValue = getDefaultValueAttribute != null ? m : saveFlagMethods?.GetFieldDefaultValue
};
}
var namespaceName = classSymbol.ContainingNamespace.ToDisplayString();
var className = classSymbol.Name;
StringBuilder source = new StringBuilder();
source.AppendLine("#pragma warning disable\n");
source.GenerateNamespaceStart(namespaceName);
var interfaces = !embedded || isRawSerializable
? Array.Empty<ITypeSymbol>()
: new ITypeSymbol[] { compilation.GetTypeByMetadataName(SymbolMetadata.RAW_SERIALIZABLE_INTERFACE) };
var indent = " ";
source.RecursiveGenerateClassStart(classSymbol, interfaces.ToImmutableArray(), ref indent);
source.GenerateClassField(
indent,
Accessibility.Private,
InstanceModifier.Const,
"int",
"_version",
version.ToString()
);
source.AppendLine();
var parentFieldOrProperty = embedded ? fieldsAndProperties.FirstOrDefault(
fieldOrPropertySymbol => fieldOrPropertySymbol.GetAttributes()
.FirstOrDefault(
attr =>
SymbolEqualityComparer.Default.Equals(attr.AttributeClass, parentSerializableAttribute)
) != null
) : null;
var serializablePropertySet = new SortedDictionary<SerializableProperty, ISymbol>(new SerializablePropertyComparer());
foreach (var fieldOrPropertySymbol in fieldsAndProperties)
{
var allAttributes = fieldOrPropertySymbol.GetAttributes();
var serializableFieldAttr = allAttributes
.FirstOrDefault(
attr =>
SymbolEqualityComparer.Default.Equals(attr.AttributeClass, serializableFieldAttribute)
);
if (serializableFieldAttr == null)
{
continue;
}
foreach (var attr in allAttributes)
{
if (!SymbolEqualityComparer.Default.Equals(attr.AttributeClass, serializableFieldAttrAttribute))
{
continue;
}
if (attr.AttributeClass == null)
{
continue;
}
var ctorArgs = attr.ConstructorArguments;
var attrTypeArg = ctorArgs[0];
if (attrTypeArg.Kind == TypedConstantKind.Primitive && attrTypeArg.Value is string attrStr)
{
source.AppendLine($"{indent}{attrStr}");
}
else
{
var attrType = (ITypeSymbol)attrTypeArg.Value;
source.GenerateAttribute(indent, attrType?.Name, ctorArgs[1].Values);
}
}
var attrCtorArgs = serializableFieldAttr.ConstructorArguments;
var order = (int)attrCtorArgs[0].Value!;
var getterAccessor = Helpers.GetAccessibility(attrCtorArgs[1].Value?.ToString());
var setterAccessor = Helpers.GetAccessibility(attrCtorArgs[2].Value?.ToString());
var virtualProperty = (bool)attrCtorArgs[3].Value!;
if (fieldOrPropertySymbol is IFieldSymbol fieldSymbol)
{
source.GenerateSerializableProperty(
compilation,
indent,
fieldSymbol,
getterAccessor,
setterAccessor,
virtualProperty,
parentFieldOrProperty
);
source.AppendLine();
}
serializableFieldSaveFlags.TryGetValue(order, out var serializableFieldSaveFlagMethods);
var serializableProperty = SerializableMigrationRulesEngine.GenerateSerializableProperty(
compilation,
fieldOrPropertySymbol,
order,
allAttributes,
serializableTypes,
embeddedSerializableTypes,
classSymbol,
serializableFieldSaveFlagMethods
);
serializablePropertySet.Add(serializableProperty, fieldOrPropertySymbol);
}
var serializableFields = serializablePropertySet.Keys.ToImmutableArray();
var serializableProperties = serializablePropertySet.Select(
kvp => kvp.Key with
{
Name = (kvp.Value as IFieldSymbol)?.GetPropertyName() ?? ((IPropertySymbol)kvp.Value).Name
}
).ToImmutableArray();
// If we are not inheriting ISerializable, then we need to define some stuff
if (!(isOverride || embedded))
{
// long ISerializable.SavePosition { get; set; } = -1;
source.GenerateAutoProperty(
Accessibility.NotApplicable,
"long",
"ISerializable.SavePosition",
Accessibility.NotApplicable,
Accessibility.NotApplicable,
indent,
defaultValue: "-1"
);
// BufferWriter ISerializable.SaveBuffer { get; set; }
source.GenerateAutoProperty(
Accessibility.NotApplicable,
"BufferWriter",
"ISerializable.SaveBuffer",
Accessibility.NotApplicable,
Accessibility.NotApplicable,
indent
);
}
if (!embedded)
{
// Serial constructor
source.GenerateSerialCtor(compilation, className, indent, isOverride);
source.AppendLine();
}
if (version > 0)
{
for (var i = 0; i < migrations.Length; i++)
{
var migration = migrations[i];
if (migration.Version < version)
{
source.GenerateMigrationContentStruct(compilation, indent, migration, classSymbol);
source.AppendLine();
}
}
}
// Serialize Method
source.GenerateSerializeMethod(
compilation,
indent,
isOverride,
encodedVersion,
serializableFields,
serializableProperties,
serializableFieldSaveFlags
);
source.AppendLine();
// Deserialize Method
source.GenerateDeserializeMethod(
compilation,
classSymbol,
indent,
isOverride,
version,
encodedVersion,
migrations,
serializableFields,
serializableProperties,
parentFieldOrProperty,
serializableFieldSaveFlags
);
// Serialize SaveFlag enum class
if (serializableFieldSaveFlags.Count > 0)
{
source.AppendLine();
source.GenerateEnumStart(
"SaveFlag",
$"{indent} ",
true,
Accessibility.Private
);
source.GenerateEnumValue($"{indent} ", true, "None", -1);
int index = 0;
foreach (var (order, _) in serializableFieldSaveFlags)
{
source.GenerateEnumValue($"{indent} ", true, serializableProperties[order].Name, index++);
}
source.GenerateEnumEnd($"{indent} ");
}
source.RecursiveGenerateClassEnd(classSymbol, ref indent);
source.GenerateNamespaceEnd();
if (migrationPath != null)
{
// Write the migration file
var newMigration = new SerializableMetadata
{
Version = version,
Type = classSymbol.ToDisplayString(),
Properties = serializableProperties.Length > 0 ? serializableProperties : null
};
WriteMigration(migrationPath, newMigration, jsonSerializerOptions);
}
return source.ToString();
}
private static void WriteMigration(string migrationPath, SerializableMetadata metadata, JsonSerializerOptions options)
{
Directory.CreateDirectory(migrationPath);
var filePath = Path.Combine(migrationPath, $"{metadata.Type}.v{metadata.Version}.json");
File.WriteAllText(filePath, JsonSerializer.Serialize(metadata, options));
}
private static void RecursiveGenerateClassStart(
this StringBuilder source,
INamedTypeSymbol classSymbol,
ImmutableArray<ITypeSymbol> interfaces,
ref string indent
)
{
var containingSymbolList = new List<INamedTypeSymbol>();
do
{
containingSymbolList.Add(classSymbol);
classSymbol = classSymbol.ContainingSymbol as INamedTypeSymbol;
} while (classSymbol != null);
containingSymbolList.Reverse();
for (var i = 0; i < containingSymbolList.Count; i++)
{
var symbol = containingSymbolList[i];
source.GenerateClassStart(symbol, indent, i == containingSymbolList.Count - 1 ? interfaces : ImmutableArray<ITypeSymbol>.Empty);
indent += " ";
}
}
private static void RecursiveGenerateClassEnd(this StringBuilder source, INamedTypeSymbol classSymbol, ref string indent)
{
do
{
indent = indent.Substring(0, indent.Length - 4);
source.GenerateClassEnd(indent);
classSymbol = classSymbol.ContainingSymbol as INamedTypeSymbol;
} while (classSymbol != null);
}
}
}

View file

@ -1,213 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializableEntityGeneration.DeserializeMethod.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.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializableMigration;
namespace SerializationGenerator
{
public static partial class SerializableEntityGeneration
{
public static void GenerateDeserializeMethod(
this StringBuilder source,
Compilation compilation,
INamedTypeSymbol classSymbol,
string indent,
bool isOverride,
int version,
bool encodedVersion,
ImmutableArray<SerializableMetadata> migrations,
ImmutableArray<SerializableProperty> fields,
ImmutableArray<SerializableProperty> properties,
ISymbol parentFieldOrProperty,
SortedDictionary<int, SerializableFieldSaveFlagMethods> serializableFieldSaveFlagMethodsDictionary
)
{
var genericReaderInterface = compilation.GetTypeByMetadataName(SymbolMetadata.GENERIC_READER_INTERFACE);
source.GenerateMethodStart(
indent,
"Deserialize",
Accessibility.Public,
isOverride,
"void",
ImmutableArray.Create<(ITypeSymbol, string)>((genericReaderInterface, "reader"))
);
var bodyIndent = $"{indent} ";
var innerIndent = $"{bodyIndent} ";
if (isOverride)
{
source.AppendLine($"{bodyIndent}base.Deserialize(reader);");
source.AppendLine();
}
var afterDeserialization = classSymbol
.GetMembers()
.OfType<IMethodSymbol>()
.Select(
m =>
{
if (!m.ReturnsVoid || m.Parameters.Length != 0)
{
return (m, null);
}
return (m, m.GetAttributes()
.FirstOrDefault(
attr => SymbolEqualityComparer.Default.Equals(
attr.AttributeClass,
compilation.GetTypeByMetadataName(SymbolMetadata.AFTERDESERIALIZATION_ATTRIBUTE)
)
));
}
).Where(m => m.Item2 != null).ToList();
// Version
source.AppendLine($"{bodyIndent}var version = reader.{(encodedVersion ? "ReadEncodedInt" : "ReadInt")}();");
if (version > 0)
{
var parent = parentFieldOrProperty?.Name ?? "this";
var nextVersion = 0;
for (var i = 0; i < migrations.Length; i++)
{
var migrationVersion = migrations[i].Version;
if (migrationVersion == nextVersion)
{
nextVersion++;
}
source.AppendLine();
source.AppendLine($"{bodyIndent}if (version == {migrationVersion})");
source.AppendLine($"{bodyIndent}{{");
source.AppendLine($"{bodyIndent} MigrateFrom(new V{migrationVersion}Content(reader, this));");
source.AppendLine($"{bodyIndent} {parent}.MarkDirty();");
source.GenerateAfterDeserialization($"{bodyIndent} ", afterDeserialization);
source.AppendLine($"{bodyIndent} return;");
source.AppendLine($"{bodyIndent}}}");
}
if (nextVersion < version)
{
source.AppendLine();
source.AppendLine($"{bodyIndent}if (version < _version)");
source.AppendLine($"{bodyIndent}{{");
source.AppendLine($"{bodyIndent} Deserialize(reader, version);");
source.AppendLine($"{bodyIndent} {parent}.MarkDirty();");
source.GenerateAfterDeserialization($"{bodyIndent} ", afterDeserialization);
source.AppendLine($"{bodyIndent} return;");
source.AppendLine($"{bodyIndent}}}");
}
}
if (serializableFieldSaveFlagMethodsDictionary.Count > 0)
{
source.AppendLine();
source.AppendLine($"{bodyIndent}var saveFlags = reader.ReadEnum<SaveFlag>();");
}
for (var i = 0; i < properties.Length; i++)
{
var field = fields[i];
var property = properties[i];
var rule = SerializableMigrationRulesEngine.Rules[property.Rule];
if (serializableFieldSaveFlagMethodsDictionary.TryGetValue(
property.Order,
out var serializableFieldSaveFlagMethods
))
{
source.AppendLine();
// Special case
if (property.Type == "bool")
{
source.AppendLine($"{bodyIndent}{field.Name} = (saveFlags & SaveFlag.{property.Name}) != 0;");
}
else
{
source.AppendLine($"{bodyIndent}if ((saveFlags & SaveFlag.{property.Name}) != 0)\n{bodyIndent}{{");
rule.GenerateDeserializationMethod(
source,
innerIndent,
field,
parentFieldOrProperty?.Name ?? "this"
);
(rule as IPostDeserializeMethod)?.PostDeserializeMethod(
source,
innerIndent,
field,
compilation,
classSymbol
);
if (serializableFieldSaveFlagMethods.GetFieldDefaultValue != null)
{
source.AppendLine($"{bodyIndent}}}\n{bodyIndent}else\n{bodyIndent}{{");
source.AppendLine(
$"{bodyIndent} {field.Name} = {serializableFieldSaveFlagMethods.GetFieldDefaultValue.Name}();"
);
}
source.AppendLine($"{bodyIndent}}}");
}
}
else
{
source.AppendLine();
rule.GenerateDeserializationMethod(
source,
bodyIndent,
field,
parentFieldOrProperty?.Name ?? "this"
);
(rule as IPostDeserializeMethod)?.PostDeserializeMethod(
source,
bodyIndent,
field,
compilation,
classSymbol
);
}
}
source.GenerateAfterDeserialization($"{bodyIndent}", afterDeserialization);
source.GenerateMethodEnd(indent);
}
private static void GenerateAfterDeserialization(
this StringBuilder source, string indent, IList<(IMethodSymbol, AttributeData?)> afterDeserialization
)
{
foreach (var (method, attr) in afterDeserialization)
{
if ((bool)attr.ConstructorArguments[0].Value!)
{
source.AppendLine($"{indent}{method.Name}();");
}
else
{
source.AppendLine($"{indent}Timer.DelayCall({method.Name});");
}
}
}
}
}

View file

@ -1,82 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializableEntityGeneration.Property.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.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
namespace SerializationGenerator
{
public static partial class SerializableEntityGeneration
{
public static void GenerateSerializableProperty(
this StringBuilder source,
Compilation compilation,
string indent,
IFieldSymbol fieldSymbol,
Accessibility getter,
Accessibility? setter,
bool isVirtual,
ISymbol? parentFieldOrProperty
)
{
var fieldName = fieldSymbol.Name;
var invalidatePropertiesAttribute = fieldSymbol
.GetAttributes()
.OfType<AttributeData>()
.FirstOrDefault(
attr => attr.AttributeClass?.Equals(
compilation.GetTypeByMetadataName(SymbolMetadata.INVALIDATEPROPERTIES_ATTRIBUTE),
SymbolEqualityComparer.Default
) ?? false
);
var propertyIndent = $"{indent} ";
var innerIndent = $"{propertyIndent} ";
var propertyAccessor = setter > getter ? setter : getter;
var getterAccessor = getter == propertyAccessor ? Accessibility.NotApplicable : getter;
source.GeneratePropertyStart(indent, propertyAccessor.Value, isVirtual, fieldSymbol);
// Getter
source.GeneratePropertyGetterReturnsField(propertyIndent, fieldSymbol, getterAccessor);
if (setter != null && setter != Accessibility.NotApplicable)
{
var setterAccessor = setter == propertyAccessor ? Accessibility.NotApplicable : setter;
var parentSymbol = parentFieldOrProperty?.Name ?? "this";
// Setter
source.GeneratePropertySetterStart(propertyIndent, false, setterAccessor.Value);
source.AppendLine($"{innerIndent}if (value != {fieldName})");
source.AppendLine($"{innerIndent}{{");
source.AppendLine($"{innerIndent} {fieldName} = value;");
source.AppendLine($"{innerIndent} {parentSymbol}.MarkDirty();");
if (invalidatePropertiesAttribute != null)
{
source.AppendLine($"{innerIndent} InvalidateProperties();");
}
source.AppendLine($"{innerIndent}}}");
source.GeneratePropertyGetSetEnd(propertyIndent, false);
}
source.GeneratePropertyEnd(indent);
}
}
}

View file

@ -1,52 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializableEntityGeneration.SerialCtor.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.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
namespace SerializationGenerator
{
public static partial class SerializableEntityGeneration
{
private static readonly ImmutableArray<string> _baseParameters = new[] { "serial" }.ToImmutableArray();
public static void GenerateSerialCtor(
this StringBuilder source,
Compilation compilation,
string className,
string indent,
bool isOverride
)
{
var serialType = (ITypeSymbol)compilation.GetTypeByMetadataName("Server.Serial");
source.GenerateConstructorStart(
indent,
className,
Accessibility.Public,
new []{ (serialType, "serial") }.ToImmutableArray(),
isOverride ? _baseParameters : ImmutableArray<string>.Empty
);
if (!isOverride)
{
source.AppendLine($"{indent} Serial = serial;");
source.AppendLine($"{indent} SetTypeRef(typeof({className}));");
}
source.GenerateMethodEnd(indent);
}
}
}

View file

@ -1,112 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializableEntityGeneration.SerializeMethod.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.Collections.Generic;
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializableMigration;
namespace SerializationGenerator
{
public static partial class SerializableEntityGeneration
{
public static void GenerateSerializeMethod(
this StringBuilder source,
Compilation compilation,
string indent,
bool isOverride,
bool encodedVersion,
ImmutableArray<SerializableProperty> fields,
ImmutableArray<SerializableProperty> properties,
SortedDictionary<int, SerializableFieldSaveFlagMethods> serializableFieldSaveFlagMethodsDictionary
)
{
var genericWriterInterface = compilation.GetTypeByMetadataName(SymbolMetadata.GENERIC_WRITER_INTERFACE);
source.GenerateMethodStart(
indent,
"Serialize",
Accessibility.Public,
isOverride,
"void",
ImmutableArray.Create<(ITypeSymbol, string)>((genericWriterInterface, "writer"))
);
var bodyIndent = $"{indent} ";
var innerIndent = $"{bodyIndent} ";
if (isOverride)
{
source.AppendLine($"{bodyIndent}base.Serialize(writer);");
source.AppendLine();
}
// Version
source.AppendLine($"{bodyIndent}writer.{(encodedVersion ? "WriteEncodedInt" : "Write")}(_version);");
// Let's collect the flags
if (serializableFieldSaveFlagMethodsDictionary.Count > 0)
{
source.AppendLine($"\n{bodyIndent}var saveFlags = SaveFlag.None;");
foreach (var (order, saveFlagMethods) in serializableFieldSaveFlagMethodsDictionary)
{
source.AppendLine($"{bodyIndent}if ({saveFlagMethods.DetermineFieldShouldSerialize!.Name}())\n{bodyIndent}{{");
var propertyName = properties[order].Name;
source.AppendLine($"{innerIndent}saveFlags |= SaveFlag.{propertyName};");
source.AppendLine($"{bodyIndent}}}");
}
source.AppendLine($"{bodyIndent}writer.WriteEnum(saveFlags);");
}
for (var i = 0; i < properties.Length; i++)
{
var field = fields[i];
var property = properties[i];
if (serializableFieldSaveFlagMethodsDictionary.ContainsKey(property.Order))
{
// Special case
if (property.Type != "bool")
{
source.AppendLine($"\n{bodyIndent}if ((saveFlags & SaveFlag.{property.Name}) != 0)\n{bodyIndent}{{");
SerializableMigrationRulesEngine.Rules[property.Rule]
.GenerateSerializationMethod(
source,
innerIndent,
field
);
source.AppendLine($"{bodyIndent}}}");
}
}
else
{
source.AppendLine();
SerializableMigrationRulesEngine.Rules[property.Rule]
.GenerateSerializationMethod(
source,
bodyIndent,
field
);
}
}
source.GenerateMethodEnd(indent);
}
}
}

View file

@ -1,11 +0,0 @@
using Microsoft.CodeAnalysis;
namespace SerializationGenerator
{
public record SerializableFieldSaveFlagMethods
{
public IMethodSymbol? DetermineFieldShouldSerialize { get; init; }
public IMethodSymbol? GetFieldDefaultValue { get; init; }
}
}

View file

@ -1,128 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializationEntityGeneration.ContentStruct.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.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializableMigration;
namespace SerializationGenerator
{
public static partial class SerializableEntityGeneration
{
public static void GenerateMigrationContentStruct(
this StringBuilder source,
Compilation compilation,
string indent,
SerializableMetadata migration,
INamedTypeSymbol classSymbol
)
{
source.AppendLine($"{indent}ref struct V{migration.Version}Content");
source.AppendLine($"{indent}{{");
var properties = migration.Properties ?? ImmutableArray<SerializableProperty>.Empty;
foreach (var serializableProperty in properties)
{
var propertyType = serializableProperty.Type;
var type = compilation.GetTypeByMetadataName(propertyType)?.IsValueType == true
|| SymbolMetadata.IsPrimitiveFromTypeDisplayString(propertyType) && propertyType != "bool"
? $"{propertyType}{(serializableProperty.UsesSaveFlag == true ? "?" : "")}" : propertyType;
source.AppendLine($"{indent} internal readonly {type} {serializableProperty.Name};");
}
var innerIndent = $"{indent} ";
var usesSaveFlags = properties.Any(p => p.UsesSaveFlag == true);
if (usesSaveFlags)
{
source.AppendLine();
source.GenerateEnumStart(
$"V{migration.Version}SaveFlag",
$"{indent} ",
true,
Accessibility.Private
);
source.GenerateEnumValue(innerIndent, true, "None", -1);
int index = 0;
foreach (var property in properties)
{
if (property.UsesSaveFlag == true)
{
source.GenerateEnumValue(innerIndent, true, property.Name, index++);
}
}
source.GenerateEnumEnd($"{indent} ");
}
source.AppendLine($"{indent} internal V{migration.Version}Content(IGenericReader reader, {classSymbol.ToDisplayString()} entity)");
source.AppendLine($"{indent} {{");
if (usesSaveFlags)
{
source.AppendLine($"{innerIndent}var saveFlags = reader.ReadEnum<V{migration.Version}SaveFlag>();");
}
if (properties.Length > 0)
{
foreach (var property in properties)
{
if (property.UsesSaveFlag == true)
{
source.AppendLine();
// Special case
if (property.Type == "bool")
{
source.AppendLine($"{innerIndent}{property.Name} = (saveFlags & V{migration.Version}SaveFlag.{property.Name}) != 0;");
}
else
{
source.AppendLine($"{innerIndent}if ((saveFlags & V{migration.Version}SaveFlag.{property.Name}) != 0)\n{innerIndent}{{");
SerializableMigrationRulesEngine.Rules[property.Rule].GenerateDeserializationMethod(
source,
$"{innerIndent} ",
property,
"entity"
);
source.AppendLine($"{innerIndent}}}\n{innerIndent}else\n{innerIndent}{{");
source.AppendLine($"{innerIndent} {property.Name} = default;");
source.AppendLine($"{innerIndent}}}");
}
}
else
{
SerializableMigrationRulesEngine.Rules[property.Rule].GenerateDeserializationMethod(
source,
innerIndent,
property,
"entity"
);
}
}
}
source.AppendLine($"{indent} }}");
source.AppendLine($"{indent}}}");
}
}
}

View file

@ -1,31 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: IPostDeserializeMethod.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.Text;
using Microsoft.CodeAnalysis;
namespace SerializableMigration
{
public interface IPostDeserializeMethod
{
public void PostDeserializeMethod(
StringBuilder source,
string indent,
SerializableProperty property,
Compilation compilation,
INamedTypeSymbol classSymbol
);
}
}

View file

@ -1,49 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializableMigrationRule.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.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
namespace SerializableMigration
{
public interface ISerializableMigrationRule
{
string RuleName { get; }
bool GenerateRuleState(
Compilation compilation,
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
);
void GenerateDeserializationMethod(
StringBuilder source,
string indent,
SerializableProperty property,
string? parentReference
);
void GenerateSerializationMethod(
StringBuilder source,
string indent,
SerializableProperty property
);
}
}

View file

@ -1,134 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: ArrayMigrationRule.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.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
namespace SerializableMigration
{
public class ArrayMigrationRule : ISerializableMigrationRule
{
public string RuleName => nameof(ArrayMigrationRule);
public bool GenerateRuleState(
Compilation compilation,
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
{
if (symbol is not IArrayTypeSymbol arrayTypeSymbol)
{
ruleArguments = null;
return false;
}
var serializableArrayType = SerializableMigrationRulesEngine.GenerateSerializableProperty(
compilation,
"ArrayEntry",
arrayTypeSymbol.ElementType,
0,
attributes,
serializableTypes,
embeddedSerializableTypes,
parentSymbol,
null
);
var length = serializableArrayType.RuleArguments?.Length?? 0;
ruleArguments = new string[length + 2];
ruleArguments[0] = arrayTypeSymbol.ElementType.ToDisplayString();
ruleArguments[1] = serializableArrayType.Rule;
if (length > 0)
{
Array.Copy(serializableArrayType.RuleArguments!, 0, ruleArguments, 2, length);
}
return true;
}
public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property, string? parentReference)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var ruleArguments = property.RuleArguments;
var arrayElementRule = SerializableMigrationRulesEngine.Rules[ruleArguments![1]];
var arrayElementRuleArguments = new string[ruleArguments.Length - 2];
Array.Copy(ruleArguments, 2, arrayElementRuleArguments, 0, ruleArguments.Length - 2);
var propertyIndex = $"{property.Name}Index";
source.AppendLine($"{indent}{property.Name} = new {ruleArguments[0]}[reader.ReadEncodedInt()];");
source.AppendLine($"{indent}for (var {propertyIndex} = 0; {propertyIndex} < {property.Name}.Length; {propertyIndex}++)");
source.AppendLine($"{indent}{{");
var serializableArrayElement = new SerializableProperty
{
Name = $"{property.Name}[{propertyIndex}]",
Type = ruleArguments[0],
Rule = arrayElementRule.RuleName,
RuleArguments = arrayElementRuleArguments
};
arrayElementRule.GenerateDeserializationMethod(source, $"{indent} ", serializableArrayElement, parentReference);
source.AppendLine($"{indent}}}");
}
public void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var ruleArguments = property.RuleArguments;
var arrayElementRule = SerializableMigrationRulesEngine.Rules[ruleArguments![1]];
var arrayElementRuleArguments = new string[ruleArguments.Length - 2];
Array.Copy(ruleArguments, 2, arrayElementRuleArguments, 0, ruleArguments.Length - 2);
var propertyName = property.Name;
var propertyVarPrefix = $"{char.ToLower(propertyName[0])}{propertyName.Substring(1, propertyName.Length - 1)}";
var propertyIndex = $"{propertyVarPrefix}Index";
var propertyLength = $"{propertyVarPrefix}Length";
source.AppendLine($"{indent}var {propertyLength} = {property.Name}?.Length ?? 0;");
source.AppendLine($"{indent}writer.WriteEncodedInt({propertyLength});");
source.AppendLine($"{indent}for (var {propertyIndex} = 0; {propertyIndex} < {propertyLength}; {propertyIndex}++)");
source.AppendLine($"{indent}{{");
var serializableArrayElement = new SerializableProperty
{
Name = $"{property.Name}![{propertyIndex}]",
Type = ruleArguments[0],
Rule = arrayElementRule.RuleName,
RuleArguments = arrayElementRuleArguments
};
arrayElementRule.GenerateSerializationMethod(source, $"{indent} ", serializableArrayElement);
source.AppendLine($"{indent}}}");
}
}
}

View file

@ -1,72 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: EnumMigrationRule.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.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializationGenerator;
namespace SerializableMigration
{
public class EnumMigrationRule : ISerializableMigrationRule
{
public string RuleName => nameof(EnumMigrationRule);
public bool GenerateRuleState(
Compilation compilation,
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
{
if (symbol is not ITypeSymbol typeSymbol || !typeSymbol.IsEnum())
{
ruleArguments = null;
return false;
}
ruleArguments = Array.Empty<string>();
return true;
}
public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property, string? parentReference)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
source.AppendLine($"{indent}{property.Name} = reader.ReadEnum<{property.Type}>();");
}
public void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
source.AppendLine($"{indent}writer.WriteEnum<{property.Type}>({property.Name});");
}
}
}

View file

@ -1,166 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: HashSetMigrationRule.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.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializationGenerator;
namespace SerializableMigration
{
public class HashSetMigrationRule : ISerializableMigrationRule
{
public string RuleName => nameof(HashSetMigrationRule);
public bool GenerateRuleState(
Compilation compilation,
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
{
if (symbol is not INamedTypeSymbol namedTypeSymbol || !symbol.IsHashSet(compilation))
{
ruleArguments = null;
return false;
}
var setTypeSymbol = namedTypeSymbol.TypeArguments[0];
var serializableSetType = SerializableMigrationRulesEngine.GenerateSerializableProperty(
compilation,
"SetEntry",
setTypeSymbol,
0,
attributes,
serializableTypes,
embeddedSerializableTypes,
parentSymbol,
null
);
var extraOptions = "";
if (attributes.Any(a => a.IsTidy(compilation)))
{
extraOptions += "@Tidy";
}
var length = serializableSetType.RuleArguments?.Length ?? 0;
ruleArguments = new string[length + 3];
ruleArguments[0] = extraOptions;
ruleArguments[1] = setTypeSymbol.ToDisplayString();
ruleArguments[2] = serializableSetType.Rule;
Array.Copy(serializableSetType.RuleArguments, 0, ruleArguments, 3, length);
return true;
}
public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property, string? parentReference)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var ruleArguments = property.RuleArguments;
var hasExtraOptions = ruleArguments![0] == "" || ruleArguments[0].StartsWith("@", StringComparison.Ordinal);
var argumentsOffset = hasExtraOptions ? 1 : 0;
var setElementRule = SerializableMigrationRulesEngine.Rules[ruleArguments[1 + argumentsOffset]];
var setElementRuleArguments = new string[ruleArguments.Length - 2 - argumentsOffset];
Array.Copy(ruleArguments, 2 + argumentsOffset, setElementRuleArguments, 0, ruleArguments.Length - 2 - argumentsOffset);
var propertyName = property.Name;
var propertyVarPrefix = $"{char.ToLower(propertyName[0])}{propertyName.Substring(1, propertyName.Length - 1)}";
var propertyIndex = $"{propertyVarPrefix}Index";
var propertyEntry = $"{propertyVarPrefix}Entry";
var propertyCount = $"{propertyVarPrefix}Count";
source.AppendLine($"{indent}{ruleArguments[argumentsOffset]} {propertyEntry};");
source.AppendLine($"{indent}var {propertyCount} = reader.ReadEncodedInt();");
source.AppendLine($"{indent}{property.Name} = new System.Collections.Generic.HashSet<{ruleArguments[argumentsOffset]}>({propertyCount});");
source.AppendLine($"{indent}for (var {propertyIndex} = 0; i < {propertyCount}; {propertyIndex}++)");
source.AppendLine($"{indent}{{");
var serializableSetElement = new SerializableProperty
{
Name = propertyEntry,
Type = ruleArguments[argumentsOffset],
Rule = setElementRule.RuleName,
RuleArguments = setElementRuleArguments
};
setElementRule.GenerateDeserializationMethod(source, $"{indent} ", serializableSetElement, parentReference);
source.AppendLine($"{indent} {property.Name}.Add({propertyEntry});");
source.AppendLine($"{indent}}}");
}
public void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var ruleArguments = property.RuleArguments;
var hasExtraOptions = ruleArguments![0] == "" || ruleArguments[0].StartsWith("@", StringComparison.Ordinal);
var shouldTidy = hasExtraOptions && ruleArguments[0].Contains("@Tidy");
var argumentsOffset = hasExtraOptions ? 1 : 0;
var setElementRule = SerializableMigrationRulesEngine.Rules[ruleArguments[1 + argumentsOffset]];
var setElementRuleArguments = new string[ruleArguments.Length - 2 - argumentsOffset];
Array.Copy(ruleArguments, 2 + argumentsOffset, setElementRuleArguments, 0, ruleArguments.Length - 2 - argumentsOffset);
var propertyName = property.Name;
var propertyVarPrefix = $"{char.ToLower(propertyName[0])}{propertyName.Substring(1, propertyName.Length - 1)}";
var propertyEntry = $"{propertyVarPrefix}Entry";
var propertyCount = $"{propertyVarPrefix}Count";
if (shouldTidy)
{
source.AppendLine($"{indent}{property.Name}?.Tidy();");
}
source.AppendLine($"{indent}var {propertyCount} = {property.Name}?.Count ?? 0;");
source.AppendLine($"{indent}writer.WriteEncodedInt({propertyCount});");
source.AppendLine($"{indent}if ({propertyCount} > 0)");
source.AppendLine($"{indent}{{");
source.AppendLine($"{indent} foreach (var {propertyEntry} in {property.Name}!)");
source.AppendLine($"{indent} {{");
var serializableSetElement = new SerializableProperty
{
Name = propertyEntry,
Type = ruleArguments[argumentsOffset],
Rule = setElementRule.RuleName,
RuleArguments = setElementRuleArguments
};
setElementRule.GenerateSerializationMethod(source, $"{indent} ", serializableSetElement);
source.AppendLine($"{indent} }}");
source.AppendLine($"{indent}}}");
}
}
}

View file

@ -1,202 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: KeyValuePairMigrationRule.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.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializationGenerator;
namespace SerializableMigration
{
public class KeyValuePairMigrationRule : ISerializableMigrationRule
{
public string RuleName => nameof(KeyValuePairMigrationRule);
public bool GenerateRuleState(
Compilation compilation,
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
{
if (symbol is not INamedTypeSymbol namedTypeSymbol || !symbol.IsKeyValuePair(compilation))
{
ruleArguments = null;
return false;
}
var typeArguments = namedTypeSymbol.TypeArguments;
var keySerializedProperty = SerializableMigrationRulesEngine.GenerateSerializableProperty(
compilation,
"key",
typeArguments[0],
0,
attributes,
serializableTypes,
embeddedSerializableTypes,
parentSymbol,
null
);
var valueSerializedProperty = SerializableMigrationRulesEngine.GenerateSerializableProperty(
compilation,
"value",
typeArguments[1],
1,
attributes,
serializableTypes,
embeddedSerializableTypes,
parentSymbol,
null
);
var keyArgumentsLength = keySerializedProperty.RuleArguments?.Length ?? 0;
var valueArgumentsLength = valueSerializedProperty.RuleArguments?.Length ?? 0;
var index = 0;
// Key
ruleArguments = new string[5 + keyArgumentsLength + valueArgumentsLength];
ruleArguments[index++] = typeArguments[0].ToDisplayString();
ruleArguments[index++] = keySerializedProperty.Rule;
ruleArguments[index++] = keyArgumentsLength.ToString();
if (keyArgumentsLength > 0)
{
Array.Copy(keySerializedProperty.RuleArguments!, 0, ruleArguments, index, keyArgumentsLength);
}
// Value
ruleArguments[index++] = typeArguments[1].ToDisplayString();
ruleArguments[index++] = valueSerializedProperty.Rule;
if (valueArgumentsLength > 0)
{
Array.Copy(valueSerializedProperty.RuleArguments!, 0, ruleArguments, index, valueArgumentsLength);
}
return true;
}
public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property, string? parentReference)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var ruleArguments = property.RuleArguments;
var keyType = ruleArguments![0];
var keyRule = SerializableMigrationRulesEngine.Rules[ruleArguments[1]];
var keyRuleArguments = new string[int.Parse(ruleArguments[2])];
Array.Copy(ruleArguments, 3, keyRuleArguments, 0, keyRuleArguments.Length);
var serializableKeyProperty = new SerializableProperty
{
Name = "key",
Type = keyType,
Rule = keyRule.RuleName,
RuleArguments = keyRuleArguments
};
keyRule.GenerateDeserializationMethod(
source,
indent,
serializableKeyProperty,
parentReference
);
var valueIndex = 3 + keyRuleArguments.Length;
var valueType = ruleArguments[valueIndex++];
var valueRule = SerializableMigrationRulesEngine.Rules[ruleArguments[valueIndex++]];
var valueRuleArguments = new string[ruleArguments.Length - valueIndex];
Array.Copy(ruleArguments, valueIndex, valueRuleArguments, 0, valueRuleArguments.Length);
var serializableValueProperty = new SerializableProperty
{
Name = "value",
Type = valueType,
Rule = valueRule.RuleName,
RuleArguments = valueRuleArguments
};
keyRule.GenerateDeserializationMethod(
source,
indent,
serializableValueProperty,
parentReference
);
source.AppendLine(
$"{indent}{property.Name} = new {SymbolMetadata.KEYVALUEPAIR_STRUCT}<{keyType}, {valueType}>(key, value);"
);
}
public void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var ruleArguments = property.RuleArguments;
var keyType = ruleArguments![0];
var keyRule = SerializableMigrationRulesEngine.Rules[ruleArguments[1]];
var keyRuleArguments = new string[int.Parse(ruleArguments[2])];
Array.Copy(ruleArguments, 3, keyRuleArguments, 0, keyRuleArguments.Length);
var serializableKeyProperty = new SerializableProperty
{
Name = $"{property.Name}.Key",
Type = keyType,
Rule = keyRule.RuleName,
RuleArguments = keyRuleArguments
};
keyRule.GenerateSerializationMethod(
source,
indent,
serializableKeyProperty
);
var valueIndex = 3 + keyRuleArguments.Length;
var valueType = ruleArguments[valueIndex++];
var valueRule = SerializableMigrationRulesEngine.Rules[ruleArguments[valueIndex++]];
var valueRuleArguments = new string[ruleArguments.Length - valueIndex];
Array.Copy(ruleArguments, valueIndex, valueRuleArguments, 0, valueRuleArguments.Length);
var serializableValueProperty = new SerializableProperty
{
Name = $"{property.Name}.Value",
Type = valueType,
Rule = valueRule.RuleName,
RuleArguments = valueRuleArguments
};
keyRule.GenerateSerializationMethod(
source,
indent,
serializableValueProperty
);
}
}
}

View file

@ -1,171 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: ListMigrationRule.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.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializationGenerator;
namespace SerializableMigration
{
public class ListMigrationRule : ISerializableMigrationRule
{
public string RuleName => nameof(ListMigrationRule);
public bool GenerateRuleState(
Compilation compilation,
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
{
if (symbol is not INamedTypeSymbol namedTypeSymbol || !symbol.IsList(compilation))
{
ruleArguments = null;
return false;
}
var listTypeSymbol = namedTypeSymbol.TypeArguments[0];
var serializableListType = SerializableMigrationRulesEngine.GenerateSerializableProperty(
compilation,
"ListEntry",
listTypeSymbol,
0,
attributes,
serializableTypes,
embeddedSerializableTypes,
parentSymbol,
null
);
var extraOptions = "";
if (attributes.Any(a => a.IsTidy(compilation)))
{
extraOptions += "@Tidy";
}
var length = serializableListType.RuleArguments?.Length ?? 0;
ruleArguments = new string[length + 3];
ruleArguments[0] = extraOptions;
ruleArguments[1] = listTypeSymbol.ToDisplayString();
ruleArguments[2] = serializableListType.Rule;
if (length > 0)
{
Array.Copy(serializableListType.RuleArguments!, 0, ruleArguments, 3, length);
}
return true;
}
public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property, string? parentReference)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var ruleArguments = property.RuleArguments;
var hasExtraOptions = ruleArguments![0] == "" || ruleArguments[0].StartsWith("@", StringComparison.Ordinal);
var argumentsOffset = hasExtraOptions ? 1 : 0;
var listElementRule = SerializableMigrationRulesEngine.Rules[ruleArguments[argumentsOffset + 1]];
var listElementRuleArguments = new string[ruleArguments.Length - 2 - argumentsOffset];
Array.Copy(ruleArguments, 2 + argumentsOffset, listElementRuleArguments, 0, ruleArguments.Length - 2 - argumentsOffset);
var propertyName = property.Name;
var propertyVarPrefix = $"{char.ToLower(propertyName[0])}{propertyName.Substring(1, propertyName.Length - 1)}";
var propertyIndex = $"{propertyVarPrefix}Index";
var propertyEntry = $"{propertyVarPrefix}Entry";
var propertyCount = $"{propertyVarPrefix}Count";
source.AppendLine($"{indent}{ruleArguments[argumentsOffset]} {propertyEntry};");
source.AppendLine($"{indent}var {propertyCount} = reader.ReadEncodedInt();");
source.AppendLine($"{indent}{propertyName} = new System.Collections.Generic.List<{ruleArguments[argumentsOffset]}>({propertyCount});");
source.AppendLine($"{indent}for (var {propertyIndex} = 0; {propertyIndex} < {propertyCount}; {propertyIndex}++)");
source.AppendLine($"{indent}{{");
var serializableListElement = new SerializableProperty
{
Name = propertyEntry,
Type = ruleArguments[argumentsOffset],
Rule = listElementRule.RuleName,
RuleArguments = listElementRuleArguments
};
listElementRule.GenerateDeserializationMethod(source, $"{indent} ", serializableListElement, parentReference);
source.AppendLine($"{indent} {propertyName}.Add({propertyEntry});");
source.AppendLine($"{indent}}}");
}
public void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var ruleArguments = property.RuleArguments;
var hasExtraOptions = ruleArguments![0] == "" || ruleArguments[0].StartsWith("@", StringComparison.Ordinal);
var shouldTidy = hasExtraOptions && ruleArguments[0].Contains("@Tidy");
var argumentsOffset = hasExtraOptions ? 1 : 0;
var listElementRule = SerializableMigrationRulesEngine.Rules[ruleArguments[1 + argumentsOffset]];
var listElementRuleArguments = new string[ruleArguments.Length - 2 - argumentsOffset];
Array.Copy(ruleArguments, 2 + argumentsOffset, listElementRuleArguments, 0, ruleArguments.Length - 2 - argumentsOffset);
var propertyName = property.Name;
var propertyVarPrefix = $"{char.ToLower(propertyName[0])}{propertyName.Substring(1, propertyName.Length - 1)}";
var propertyEntry = $"{propertyVarPrefix}Entry";
var propertyCount = $"{propertyVarPrefix}Count";
if (shouldTidy)
{
source.AppendLine($"{indent}{property.Name}?.Tidy();");
}
source.AppendLine($"{indent}var {propertyCount} = {property.Name}?.Count ?? 0;");
source.AppendLine($"{indent}writer.WriteEncodedInt({propertyCount});");
source.AppendLine($"{indent}if ({propertyCount} > 0)");
source.AppendLine($"{indent}{{");
source.AppendLine($"{indent} foreach (var {propertyEntry} in {property.Name}!)");
source.AppendLine($"{indent} {{");
var serializableListElement = new SerializableProperty
{
Name = propertyEntry,
Type = ruleArguments[argumentsOffset],
Rule = listElementRule.RuleName,
RuleArguments = listElementRuleArguments
};
listElementRule.GenerateSerializationMethod(source, $"{indent} ", serializableListElement);
source.AppendLine($"{indent} }}");
source.AppendLine($"{indent}}}");
}
}
}

View file

@ -1,148 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: PrimitiveTypeMigrationRule.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.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializationGenerator;
namespace SerializableMigration
{
public class PrimitiveTypeMigrationRule : ISerializableMigrationRule
{
public string RuleName => nameof(PrimitiveTypeMigrationRule);
public bool GenerateRuleState(
Compilation compilation,
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
{
if (symbol.IsIpAddress(compilation) || symbol.IsTimeSpan(compilation))
{
ruleArguments = Array.Empty<string>();
return true;
}
if (
symbol is not ITypeSymbol {
SpecialType: not (not
SpecialType.System_Boolean and not
SpecialType.System_SByte and not
SpecialType.System_Int16 and not
SpecialType.System_Int32 and not
SpecialType.System_Int64 and not
SpecialType.System_Byte and not
SpecialType.System_UInt16 and not
SpecialType.System_UInt32 and not
SpecialType.System_UInt64 and not
SpecialType.System_Single and not
SpecialType.System_Double and not
SpecialType.System_String and not
SpecialType.System_Decimal and not
SpecialType.System_DateTime)
} typeSymbol
)
{
ruleArguments = null;
return false;
}
ruleArguments = typeSymbol.SpecialType switch
{
SpecialType.System_Int32 when attributes.Any(a => a.IsEncodedInt(compilation)) =>
new[] { "EncodedInt" },
SpecialType.System_DateTime when attributes.Any(a => a.IsDeltaDateTime(compilation)) =>
new[] { "DeltaTime" },
SpecialType.System_String when attributes.Any(a => a.IsInternString(compilation)) =>
new[] { "InternString" },
_ => new[] { "" }
};
return true;
}
public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property, string? parentReference)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var propertyName = property.Name;
var argument = property.RuleArguments?.Length >= 1 ? property.RuleArguments[0] : null;
const string ipAddress = SymbolMetadata.IPADDRESS_CLASS;
const string timeSpan = SymbolMetadata.TIMESPAN_STRUCT;
const string date = "System.DateTime";
var readMethod = property.Type switch
{
"bool" => "ReadBool",
"sbyte" => "ReadSByte",
"short" => "ReadShort",
"int" when argument == "EncodedInt" => "ReadEncodedInt",
"int" => "ReadInt",
"long" => "ReadLong",
"byte" => "ReadByte",
"ushort" => "ReadUShort",
"uint" => "ReadUInt",
"ulong" => "ReadULong",
"float" => "ReadFloat",
"double" => "ReadDouble",
"string" => "ReadString",
"decimal" => "ReadDecimal",
date when argument == "DeltaTime" => "ReadDeltaTime",
date => "ReadDateTime",
ipAddress => "ReadIPAddress",
timeSpan => "ReadTimeSpan"
};
var readArgument = readMethod == "ReadString" && argument == "InternString" ? "true" : "";
source.AppendLine($"{indent}{propertyName} = reader.{readMethod}({readArgument});");
}
public void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var propertyName = property.Name;
var argument = property.RuleArguments?.Length >= 1 ? property.RuleArguments[0] : null;
var writeMethod = property.Type switch
{
"System.DateTime" when argument == "DeltaTime" => "WriteDeltaTime",
"int" when argument == "EncodedInt" => "WriteEncodedInt",
_ => "Write"
};
source.AppendLine($"{indent}writer.{writeMethod}({propertyName});");
}
}
}

View file

@ -1,78 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: PrimitiveUOTypeMigrationRule.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.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializationGenerator;
namespace SerializableMigration
{
public class PrimitiveUOTypeMigrationRule : ISerializableMigrationRule
{
public string RuleName => nameof(PrimitiveUOTypeMigrationRule);
public bool GenerateRuleState(
Compilation compilation,
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
{
ruleArguments = symbol switch
{
_ when symbol.IsPoint2D(compilation) => new[] { "Point2D" },
_ when symbol.IsPoint3D(compilation) => new[] { "Point3D" },
_ when symbol.IsRectangle2D(compilation) => new[] { "Rect2D" },
_ when symbol.IsRectangle3D(compilation) => new[] { "Rect3D" },
_ when symbol.IsRace(compilation) => new[] { "Race" },
_ when symbol.IsMap(compilation) => new[] { "Map" },
_ => null
};
return ruleArguments != null;
}
public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property, string? parentReference)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var propertyName = property.Name;
source.AppendLine($"{indent}{propertyName} = reader.Read{property.RuleArguments?[0] ?? ""}();");
}
public void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var propertyName = property.Name;
source.AppendLine($"{indent}writer.Write({propertyName});");
}
}
}

View file

@ -1,81 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: RawSerializableMigrationRule.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.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializationGenerator;
namespace SerializableMigration
{
public class RawSerializableMigrationRule : ISerializableMigrationRule
{
public string RuleName => nameof(RawSerializableMigrationRule);
public bool GenerateRuleState(
Compilation compilation,
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
{
if (symbol is not ITypeSymbol typeSymbol)
{
ruleArguments = null;
return false;
}
if (!typeSymbol.HasRawSerializableInterface(compilation, embeddedSerializableTypes))
{
ruleArguments = null;
return false;
}
ruleArguments = new[] { "" };
return true;
}
public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property, string? parentReference)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var propertyName = property.Name;
source.AppendLine($"{indent}{propertyName} = new {property.Type}({parentReference ?? "this"});");
source.AppendLine($"{indent}{propertyName}.Deserialize(reader);");
}
public void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var propertyName = property.Name;
source.AppendLine($"{indent}{propertyName}.Serialize(writer);");
}
}
}

View file

@ -1,74 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializableInterfaceMigrationRule.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.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializationGenerator;
namespace SerializableMigration
{
public class SerializableInterfaceMigrationRule : ISerializableMigrationRule
{
public string RuleName => nameof(SerializableInterfaceMigrationRule);
public bool GenerateRuleState(
Compilation compilation,
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
{
if (symbol is ITypeSymbol typeSymbol && typeSymbol.HasSerializableInterface(compilation, serializableTypes))
{
ruleArguments = Array.Empty<string>();
return true;
}
ruleArguments = null;
return false;
}
public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property, string? parentReference)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var propertyName = property.Name;
source.AppendLine($"{indent}{propertyName} = reader.ReadEntity<{property.Type}>();");
}
public void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var propertyName = property.Name;
source.AppendLine($"{indent}writer.Write({propertyName});");
}
}
}

View file

@ -1,84 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializationMethodSignatureMigrationRule.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.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializationGenerator;
namespace SerializableMigration
{
public class SerializationMethodSignatureMigrationRule : ISerializableMigrationRule
{
public string RuleName => nameof(SerializationMethodSignatureMigrationRule);
public bool GenerateRuleState(
Compilation compilation,
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
{
if ((symbol as ITypeSymbol)?.HasPublicSerializeMethod(compilation, serializableTypes) != true)
{
ruleArguments = null;
return false;
}
if (symbol is not INamedTypeSymbol namedTypeSymbol ||
!namedTypeSymbol.HasGenericReaderCtor(compilation, parentSymbol, out var requiresParent))
{
ruleArguments = null;
return false;
}
ruleArguments = new[] { requiresParent ? "DeserializationRequiresParent" : "" };
return true;
}
public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property, string? parentReference)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var propertyName = property.Name;
var argument = property.RuleArguments?.Length >= 1 &&
property.RuleArguments[0] == "DeserializationRequiresParent" ? ", this" : "";
source.AppendLine($"{indent}{propertyName} = new {property.Type}(reader{argument});");
}
public void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var propertyName = property.Name;
source.AppendLine($"{indent}{propertyName}.Serialize(writer);");
}
}
}

View file

@ -1,126 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: TimerMigrationRule.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.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializationGenerator;
namespace SerializableMigration
{
public class TimerMigrationRule : ISerializableMigrationRule, IPostDeserializeMethod
{
public string RuleName => nameof(TimerMigrationRule);
public bool GenerateRuleState(
Compilation compilation,
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
{
if (!(symbol is ITypeSymbol typeSymbol && typeSymbol.IsTimer(compilation)))
{
ruleArguments = null;
return false;
}
ruleArguments = attributes.Any(a => a.IsTimerDrift(compilation))
? new[] { "@TimerDrift" }
: new[] { "" };
return true;
}
public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property, string? parentReference)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var propertyName = property.Name;
var ruleArguments = property.RuleArguments;
var driftTimer = ruleArguments![0].Contains("@TimerDrift");
var readTimer = driftTimer ? "reader.ReadDeltaTime()" : "reader.ReadDateTime()";
source.AppendLine($"{indent}var {propertyName}Next = {readTimer};");
source.AppendLine($"{indent}var {propertyName}Delay = {propertyName}Next == System.DateTime.MinValue ? System.TimeSpan.MinValue : {propertyName}Next - Core.Now;");
}
public void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var propertyName = property.Name;
var ruleArguments = property.RuleArguments;
var driftTimer = ruleArguments![0].Contains("@TimerDrift");
var writerMethod = driftTimer ? "WriteDeltaTime" : "Write";
source.AppendLine($"{indent}writer.{writerMethod}({propertyName}?.Next ?? System.DateTime.MinValue);");
}
public void PostDeserializeMethod(
StringBuilder source, string indent, SerializableProperty property, Compilation compilation, INamedTypeSymbol classSymbol
)
{
var deserializeTimerMethod = classSymbol
.GetMembers()
.OfType<IMethodSymbol>()
.FirstOrDefault(
m =>
{
if (!m.ReturnsVoid || m.Parameters.Length != 1 || !m.Parameters[0].Type.IsTimeSpan(compilation))
{
return false;
}
return m.GetAttributes()
.FirstOrDefault(
attr =>
{
if (!SymbolEqualityComparer.Default.Equals(
attr.AttributeClass,
compilation.GetTypeByMetadataName(
SymbolMetadata.DESERIALIZE_TIMER_FIELD_ATTRIBUTE
)
))
{
return false;
}
var order = (int)attr.ConstructorArguments[0].Value!;
return order == property.Order;
}
) != null;
}
) ?? throw new Exception("Serializing a timer requires a method with the DeserializeTimerField attribute to handle creating the timer itself.");
source.AppendLine($"{indent}{deserializeTimerMethod.Name}({property.Name}Delay);");
}
}
}

View file

@ -1,27 +0,0 @@
using System.Collections.Generic;
namespace SerializableMigration
{
public class SerializableMetadataComparer : IComparer<SerializableMetadata>
{
public int Compare(SerializableMetadata x, SerializableMetadata y)
{
if (ReferenceEquals(x, y))
{
return 0;
}
if (ReferenceEquals(null, y))
{
return 1;
}
if (ReferenceEquals(null, x))
{
return -1;
}
return x.Version.CompareTo(y.Version);
}
}
}

View file

@ -1,132 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializableMigrationRulesEngine.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.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using SerializationGenerator;
namespace SerializableMigration
{
public static class SerializableMigrationRulesEngine
{
public static readonly Dictionary<string, ISerializableMigrationRule> Rules = new();
static SerializableMigrationRulesEngine()
{
var rules = new ISerializableMigrationRule[]
{
new EnumMigrationRule(),
new ListMigrationRule(),
new ArrayMigrationRule(),
new HashSetMigrationRule(),
new KeyValuePairMigrationRule(),
new PrimitiveTypeMigrationRule(),
new PrimitiveUOTypeMigrationRule(),
new SerializableInterfaceMigrationRule(),
new SerializationMethodSignatureMigrationRule(),
new RawSerializableMigrationRule(),
new TimerMigrationRule()
};
foreach (var rule in rules)
{
Rules.Add(rule.RuleName, rule);
}
}
public static SerializableProperty? GenerateSerializableProperty(
Compilation compilation,
ISymbol fieldOrPropertySymbol,
int order,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
SerializableFieldSaveFlagMethods? serializableFieldSaveFlagMethods
)
{
string propertyName;
ITypeSymbol propertyType;
if (fieldOrPropertySymbol is IFieldSymbol fieldSymbol)
{
propertyName = fieldSymbol.Name;
propertyType = fieldSymbol.Type;
}
else if (fieldOrPropertySymbol is IPropertySymbol propertySymbol)
{
propertyName = fieldOrPropertySymbol.Name;
propertyType = propertySymbol.Type;
}
else
{
return null;
}
return GenerateSerializableProperty(
compilation,
propertyName,
propertyType,
order,
attributes,
serializableTypes,
embeddedSerializableTypes,
parentSymbol,
serializableFieldSaveFlagMethods
);
}
public static SerializableProperty GenerateSerializableProperty(
Compilation compilation,
string propertyName,
ISymbol propertyType,
int order,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
SerializableFieldSaveFlagMethods? serializableFieldSaveFlagMethods
)
{
foreach (var rule in Rules.Values)
{
if (rule.GenerateRuleState(
compilation,
propertyType,
attributes,
serializableTypes,
embeddedSerializableTypes,
parentSymbol,
out var ruleArguments
))
{
return new SerializableProperty
{
Name = propertyName,
Type = propertyType.ToDisplayString(),
Order = order,
UsesSaveFlag = serializableFieldSaveFlagMethods?.DetermineFieldShouldSerialize != null ? true : null,
Rule = rule.RuleName,
RuleArguments = ruleArguments.Length > 0 ? ruleArguments : null
};
}
}
throw new Exception($"No rule found for property {propertyName} of type {propertyType} ({Rules.Count})");
}
}
}

View file

@ -1,111 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializableMigrationSchema.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.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis;
namespace SerializableMigration
{
public static class SerializableMigrationSchema
{
public static JsonSerializerOptions GetJsonSerializerOptions() =>
new()
{
WriteIndented = true,
AllowTrailingCommas = true,
IgnoreNullValues = true,
ReadCommentHandling = JsonCommentHandling.Skip
};
private static Dictionary<string, SerializableMetadata> _cache = new();
private static readonly Regex _fileRegex = new(@"\S+\.v\d+\.json$");
public static List<SerializableMetadata> GetMigrations(
INamedTypeSymbol typeSymbol,
int version,
string migrationPath,
JsonSerializerOptions options
)
{
var typeName = typeSymbol.ToDisplayString();
var migrations = new SortedSet<SerializableMetadata>(new SerializableMetadataComparer());
var migrationFiles = Directory.GetFiles(migrationPath, $"{typeName}.v*.json");
foreach (var file in migrationFiles)
{
var fi = new FileInfo(file);
if (!_cache.TryGetValue(fi.Name, out var migration))
{
var text = File.ReadAllText(file, Encoding.UTF8);
migration = JsonSerializer.Deserialize<SerializableMetadata>(text, options);
_cache[fi.Name] = migration;
}
if (typeName == migration!.Type && version > migration.Version)
{
migrations.Add(migration);
}
}
return migrations.ToList();
}
public static List<SerializableMetadata> GetMigrationsByAnalyzerConfig(
this GeneratorExecutionContext context,
INamedTypeSymbol typeSymbol,
int version,
JsonSerializerOptions options
)
{
var typeName = typeSymbol.ToDisplayString();
var migrations = new SortedSet<SerializableMetadata>(new SerializableMetadataComparer());
foreach (var additionalText in context.AdditionalFiles)
{
var fi = new FileInfo(additionalText.Path);
if (!_fileRegex.IsMatch(fi.Name))
{
continue;
}
if (!_cache.TryGetValue(fi.Name, out var migration))
{
var text = additionalText.GetText(context.CancellationToken)?.ToString();
if (text == null)
{
continue;
}
migration = JsonSerializer.Deserialize<SerializableMetadata>(text, options);
_cache[fi.Name] = migration;
}
if (typeName == migration!.Type && version > migration.Version)
{
migrations.Add(migration);
}
}
return migrations.ToList();
}
}
}

View file

@ -1,40 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializableProperty.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.Text.Json.Serialization;
namespace SerializableMigration
{
public record SerializableProperty
{
[JsonPropertyName("name")]
public string Name { get; init; }
[JsonPropertyName("type")]
public string Type { get; init; }
[JsonPropertyName("usesSaveFlag")]
public bool? UsesSaveFlag { get; init; }
[JsonPropertyName("rule")]
public string Rule { get; init; }
[JsonPropertyName("ruleArguments")]
public string[]? RuleArguments { get; init; }
[JsonIgnore]
public int Order { get; init; }
}
}

View file

@ -1,42 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializablePropertyComparer.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.Collections.Generic;
namespace SerializableMigration
{
public class SerializablePropertyComparer : IComparer<SerializableProperty>
{
public int Compare(SerializableProperty x, SerializableProperty y)
{
if (Equals(x, y))
{
return 0;
}
if (Equals(null, y))
{
return 1;
}
if (Equals(null, x))
{
return -1;
}
return x.Order.CompareTo(y.Order);
}
}
}

View file

@ -1,27 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>preview</LangVersion>
<BuildOutputTargetFolder>analyzers</BuildOutputTargetFolder>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.11.0" />
<PackageReference Include="Humanizer.Core" Version="2.11.10" GeneratePathProperty="true" PrivateAssets="all" />
<PackageReference Include="System.Text.Json" Version="5.0.0" GeneratePathProperty="true" PrivateAssets="all" />
<PackageReference Include="System.Text.Encodings.Web" Version="5.0.0" GeneratePathProperty="true" PrivateAssets="all" />
</ItemGroup>
<PropertyGroup>
<GetTargetPathDependsOn>$(GetTargetPathDependsOn);GetDependencyTargetPaths</GetTargetPathDependsOn>
</PropertyGroup>
<Target Name="GetDependencyTargetPaths">
<ItemGroup>
<TargetPathWithTargetPlatformMoniker Include="$(PKGHumanizer_Core)\lib\netstandard2.0\*.dll" IncludeRuntimeDependency="false" />
<TargetPathWithTargetPlatformMoniker Include="$(PKGSystem_Text_Json)\lib\netstandard2.0\*.dll" IncludeRuntimeDependency="false" />
<TargetPathWithTargetPlatformMoniker Include="$(PKGSystem_Text_Encodings_Web)\lib\netstandard2.0\*.dll" IncludeRuntimeDependency="false" />
</ItemGroup>
</Target>
</Project>

View file

@ -1,133 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SyntaxReceiver.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.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace SerializationGenerator
{
public class SerializerSyntaxReceiver : ISyntaxContextReceiver
{
#pragma warning disable RS1024
public Dictionary<INamedTypeSymbol, (AttributeData?, List<ISymbol>)> ClassAndFields { get; } = new(SymbolEqualityComparer.Default);
public Dictionary<INamedTypeSymbol, (AttributeData?, List<ISymbol>)> EmbeddedClassAndFields { get; } = new(SymbolEqualityComparer.Default);
#pragma warning restore RS1024
public ImmutableArray<INamedTypeSymbol> SerializableList => ClassAndFields.Keys.ToImmutableArray();
public ImmutableArray<INamedTypeSymbol> EmbeddedSerializableList => EmbeddedClassAndFields.Keys.ToImmutableArray();
public void OnVisitSyntaxNode(SyntaxNode node, SemanticModel semanticModel)
{
var compilation = semanticModel.Compilation;
if (node is ClassDeclarationSyntax { AttributeLists: { Count: > 0 } } classDeclarationSyntax)
{
if (semanticModel.GetDeclaredSymbol(classDeclarationSyntax) is not INamedTypeSymbol classSymbol)
{
return;
}
if (classSymbol.IsEmbeddedSerializable(compilation, out var attrData))
{
if (EmbeddedClassAndFields.TryGetValue(classSymbol, out var value))
{
var (_, fieldsList) = value;
EmbeddedClassAndFields[classSymbol] = (attrData, fieldsList);
}
else
{
EmbeddedClassAndFields.Add(classSymbol, (attrData, new List<ISymbol>()));
}
}
else if (classSymbol.WillBeSerializable(compilation, out attrData))
{
if (ClassAndFields.TryGetValue(classSymbol, out var value))
{
var (_, fieldsList) = value;
ClassAndFields[classSymbol] = (attrData, fieldsList);
}
else
{
ClassAndFields.Add(classSymbol, (attrData, new List<ISymbol>()));
}
}
return;
}
if (node is FieldDeclarationSyntax { AttributeLists: { Count: > 0 } } fieldDeclarationSyntax)
{
foreach (var variable in fieldDeclarationSyntax.Declaration.Variables)
{
if (semanticModel.GetDeclaredSymbol(variable) is IFieldSymbol fieldSymbol)
{
AddFieldOrProperty(fieldSymbol, compilation);
}
}
return;
}
if (node is PropertyDeclarationSyntax { AttributeLists: { Count: > 0 } } propertyDeclarationSyntax)
{
if (semanticModel.GetDeclaredSymbol(propertyDeclarationSyntax) is IPropertySymbol propertySymbol)
{
AddFieldOrProperty(propertySymbol, compilation);
}
}
}
public void OnVisitSyntaxNode(GeneratorSyntaxContext context) =>
OnVisitSyntaxNode(context.Node, context.SemanticModel);
private void AddFieldOrProperty(ISymbol symbol, Compilation compilation)
{
var serializableFieldAttr = compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_FIELD_ATTRIBUTE);
var parentAttr = compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_PARENT_ATTRIBUTE);
if (symbol.GetAttribute(serializableFieldAttr) == null && symbol.GetAttribute(parentAttr) == null)
{
return;
}
var classSymbol = symbol.ContainingType;
if (ClassAndFields.TryGetValue(classSymbol, out var value))
{
var (_, fieldsList) = value;
fieldsList.Add(symbol);
return;
}
if (EmbeddedClassAndFields.TryGetValue(classSymbol, out value))
{
var (_, fieldsList) = value;
fieldsList.Add(symbol);
return;
}
if (classSymbol.WillBeSerializable(compilation, out var attrData))
{
ClassAndFields.Add(classSymbol, (attrData, new List<ISymbol> { symbol }));
}
else if (classSymbol.IsEmbeddedSerializable(compilation, out attrData))
{
EmbeddedClassAndFields.Add(classSymbol, (attrData, new List<ISymbol> { symbol }));
}
}
}
}

View file

@ -1,65 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: Helpers.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.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
namespace SerializationGenerator
{
public static class Helpers
{
public static bool ContainsInterface(this ITypeSymbol symbol, ISymbol interfaceSymbol) =>
symbol.Interfaces.Any(i => i.ConstructedFrom.Equals(interfaceSymbol, SymbolEqualityComparer.Default)) ||
symbol.AllInterfaces.Any(i => i.ConstructedFrom.Equals(interfaceSymbol, SymbolEqualityComparer.Default));
public static ImmutableArray<IMethodSymbol> GetAllMethods(this ITypeSymbol symbol, string name)
{
var methods = symbol.GetMembers(name).OfType<IMethodSymbol>().ToImmutableArray();
if (symbol.ContainingSymbol is not ITypeSymbol typeSymbol)
{
return methods;
}
var list = new List<IMethodSymbol>();
list.AddRange(methods.ToList());
list.AddRange(GetAllMethods(typeSymbol, name).ToList());
return list.ToImmutableArray();
}
public static string ToFriendlyString(this Accessibility accessibility) => SyntaxFacts.GetText(accessibility);
public static Accessibility GetAccessibility(string? value) =>
value switch
{
"private" => Accessibility.Private,
"protected" => Accessibility.Protected,
"internal" => Accessibility.Internal,
"public" => Accessibility.Public,
"protected internal" => Accessibility.ProtectedOrInternal,
"private protected" => Accessibility.ProtectedAndInternal,
_ => Accessibility.NotApplicable
};
public static bool CanBeConstructedFrom(this ITypeSymbol? symbol, ISymbol classSymbol) =>
symbol is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.ConstructedFrom.Equals(
classSymbol,
SymbolEqualityComparer.Default
) || symbol != null && CanBeConstructedFrom(symbol.BaseType, classSymbol);
}
}

View file

@ -1,125 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SourceGeneration.Arguments.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.Collections.Generic;
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
namespace SerializationGenerator
{
public static partial class SourceGeneration
{
public static void GetTypesFromTypedConstant(TypedConstant arg, List<ITypeSymbol> list)
{
if (arg.Kind == TypedConstantKind.Type)
{
list.Add((ITypeSymbol)arg.Value);
}
else if (arg.Kind == TypedConstantKind.Array)
{
for (var i = 0; i < arg.Values.Length; i++)
{
GetTypesFromTypedConstant(arg.Values[i], list);
}
}
}
public static void GenerateSignatureArguments(this StringBuilder source, ImmutableArray<(ITypeSymbol, string)> parameters)
{
for (var i = 0; i < parameters.Length; i++)
{
var (t, v) = parameters[i];
source.AppendFormat("{0} {1}", t.ToDisplayString(), v);
if (i < parameters.Length - 1)
{
source.Append(", ");
}
}
}
public static void GenerateNamedArgument(this StringBuilder source, KeyValuePair<string, TypedConstant> namedArg)
{
source.AppendFormat("{0} = ", namedArg.Key);
source.GenerateTypedConstant(namedArg.Value);
}
public static void GenerateTypedConstants(this StringBuilder source, ImmutableArray<TypedConstant> args)
{
source.Append("new []{");
for (var i = 0; i < args.Length; i++)
{
source.GenerateTypedConstant(args[i]);
if (i < args.Length - 1)
{
source.Append(", ");
}
}
source.Append('}');
}
public static void GenerateTypedConstant(this StringBuilder source, TypedConstant arg)
{
if (arg.IsNull)
{
source.Append("null");
return;
}
switch (arg.Kind)
{
default:
{
return;
}
case TypedConstantKind.Primitive:
{
if (arg.Value is string str)
{
source.AppendFormat("\"{0}\"", str);
}
else
{
source.Append(arg.Value);
}
break;
}
case TypedConstantKind.Enum:
{
if (arg.Type == null || arg.Value == null)
{
source.Append("null");
}
else
{
source.AppendFormat("({0}){1}", arg.Type.ToDisplayString(), arg.Value);
}
break;
}
case TypedConstantKind.Type:
{
source.AppendFormat("typeof({0})", ((ITypeSymbol)arg.Value)?.Name);
break;
}
case TypedConstantKind.Array:
{
source.GenerateTypedConstants(arg.Values);
break;
}
}
}
}
}

View file

@ -1,102 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SourceGeneration.Attribute.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.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
namespace SerializationGenerator
{
public static partial class SourceGeneration
{
public static void GenerateAttribute(
this StringBuilder source,
string indent,
string attrClassName,
ImmutableArray<TypedConstant> args
)
{
source.Append($"{indent}[{attrClassName}");
var hasArgs = args.Length > 0;
if (hasArgs)
{
source.Append("(");
}
for (var i = 0; i < args.Length; i++)
{
var arg = args[i];
source.GenerateTypedConstant(arg);
if (i < args.Length - 1)
{
source.Append(", ");
}
}
if (hasArgs)
{
source.Append(")");
}
source.AppendLine("]");
}
public static void GenerateAttribute(this StringBuilder source, AttributeData attr)
{
source.Append($" [{attr.AttributeClass?.Name}");
var ctorArgs = attr.ConstructorArguments;
var namedArgs = attr.NamedArguments;
var hasArgs = ctorArgs.Length + namedArgs.Length > 0;
if (hasArgs)
{
source.Append("(");
}
for (var i = 0; i < ctorArgs.Length; i++)
{
var arg = ctorArgs[i];
source.GenerateTypedConstant(arg);
if (i < ctorArgs.Length - 1)
{
source.Append(", ");
}
}
for (var i = 0; i < namedArgs.Length; i++)
{
var arg = namedArgs[i];
source.GenerateNamedArgument(arg);
if (i < namedArgs.Length - 1)
{
source.Append(", ");
}
}
if (hasArgs)
{
source.Append(")");
}
source.AppendLine("]");
}
public static void AggressiveInline(this StringBuilder source, string indent) =>
source.AppendLine(
$"{indent}[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]"
);
}
}

View file

@ -1,72 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SourceGeneration.Class.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.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
namespace SerializationGenerator
{
public static partial class SourceGeneration
{
public static void GenerateClassStart(
this StringBuilder source,
INamedTypeSymbol classSymbol,
string indent,
ImmutableArray<ITypeSymbol> interfaces,
bool isPartial = true
)
{
var accessor = classSymbol.DeclaredAccessibility;
source.Append($"{indent}{accessor.ToFriendlyString()} {(isPartial ? "partial " : "")}class {classSymbol.Name}");
if (!interfaces.IsEmpty)
{
source.Append(" : ");
for (var i = 0; i < interfaces.Length; i++)
{
source.Append(interfaces[i].ToDisplayString());
if (i < interfaces.Length - 1)
{
source.Append(", ");
}
}
}
source.AppendLine($"\n{indent}{{");
}
public static void GenerateClassEnd(this StringBuilder source, string indent)
{
source.AppendLine($"{indent}}}");
}
// TODO: Generalize this to any field using dynamic indentation
public static void GenerateClassField(
this StringBuilder source,
string indent,
Accessibility accessors,
InstanceModifier instance,
string type,
string variableName,
string value
)
{
var instanceStr = instance == InstanceModifier.None ? "" : $"{instance.ToFriendlyString()} ";
var accessorStr = accessors == Accessibility.NotApplicable ? "" : $"{accessors.ToFriendlyString()} ";
var valueStr = value == null ? "" : $" = {value}";
source.AppendLine($"{indent}{accessorStr}{instanceStr}{type} {variableName}{valueStr};");
}
}
}

View file

@ -1,50 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SourceGeneration.Enum.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.Text;
using Microsoft.CodeAnalysis;
namespace SerializationGenerator
{
public static partial class SourceGeneration
{
public static void GenerateEnumStart(
this StringBuilder source,
string enumName,
string indent,
bool useFlags,
Accessibility accessor = Accessibility.Public
)
{
if (useFlags)
{
source.AppendLine($"{indent}[System.Flags]");
}
source.AppendLine($"{indent}{accessor.ToFriendlyString()} enum {enumName}\n{indent}{{");
}
public static void GenerateEnumValue(this StringBuilder source, string indent, bool isFlag, string name, int value)
{
var number = value < 0 ? 0 : 1 << value;
var valueStr = isFlag ? $"0x{number:X8}" : value.ToString();
source.AppendLine($"{indent}{name} = {valueStr},");
}
public static void GenerateEnumEnd(this StringBuilder source, string indent)
{
source.AppendLine($"{indent}}}");
}
}
}

View file

@ -1,39 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SourceGeneration.InstanceModifier.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/>. *
*************************************************************************/
namespace SerializationGenerator
{
public enum InstanceModifier
{
None,
Const,
ReadOnly,
Static,
StaticReadOnly
}
public static partial class SourceGeneration
{
public static string ToFriendlyString(this InstanceModifier modifier) =>
modifier switch
{
InstanceModifier.Const => "const",
InstanceModifier.ReadOnly => "readonly",
InstanceModifier.Static => "static",
InstanceModifier.StaticReadOnly => "static readonly",
_ => ""
};
}
}

View file

@ -1,62 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SourceGeneration.Method.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.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
namespace SerializationGenerator
{
public static partial class SourceGeneration
{
public static void GenerateMethodStart(
this StringBuilder source, string indent, string methodName, Accessibility accessors, bool isOverride,
string returnType, ImmutableArray<(ITypeSymbol, string)> parameters
)
{
source.Append($"{indent}{accessors.ToFriendlyString()}{(isOverride ? " override" : "")} {returnType} {methodName}(");
source.GenerateSignatureArguments(parameters);
source.AppendLine($")\n{indent}{{");
}
public static void GenerateMethodEnd(this StringBuilder source, string indent) => source.AppendLine($"{indent}}}");
public static void GenerateConstructorStart(
this StringBuilder source, string indent, string className, Accessibility accessors, ImmutableArray<(ITypeSymbol, string)> parameters,
ImmutableArray<string> baseParameters, bool isOverload = false
)
{
source.Append($"{indent}{accessors.ToFriendlyString()} {className}(");
source.GenerateSignatureArguments(parameters);
source.Append(')');
bool hasBaseParams = baseParameters.Length > 0;
if (hasBaseParams)
{
source.AppendFormat(" : {0}(", isOverload ? "this" : "base");
for (int i = 0; i < baseParameters.Length; i++)
{
source.Append(baseParameters[i]);
if (i < baseParameters.Length - 1)
{
source.Append(',');
}
}
source.Append(')');
}
source.AppendLine($"\n{indent}{{");
}
}
}

View file

@ -1,33 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SourceGeneration.Namespace.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.Text;
namespace SerializationGenerator
{
public static partial class SourceGeneration
{
public static void GenerateNamespaceStart(this StringBuilder source, string namespaceName)
{
source.AppendLine($@"namespace {namespaceName}
{{");
}
public static void GenerateNamespaceEnd(this StringBuilder source)
{
source.AppendLine("}");
}
}
}

View file

@ -1,144 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SourceGeneration.Property.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.Text;
using Humanizer;
using Microsoft.CodeAnalysis;
namespace SerializationGenerator
{
public static partial class SourceGeneration
{
public static string GetPropertyName(this IFieldSymbol fieldSymbol)
{
var fieldName = fieldSymbol.Name;
var propertyName = fieldName;
if (propertyName.StartsWith("m_", StringComparison.OrdinalIgnoreCase))
{
propertyName = propertyName.Substring(2);
}
else if (propertyName.StartsWith("_", StringComparison.OrdinalIgnoreCase))
{
propertyName = propertyName.Substring(1);
}
return propertyName.Dehumanize();
}
public static void GeneratePropertyStart(
this StringBuilder source,
string indent,
Accessibility accessors,
bool isVirtual,
IFieldSymbol fieldSymbol
)
{
var propertyName = fieldSymbol.GetPropertyName();
var virt = isVirtual ? "virtual " : "";
source.AppendLine($"{indent}{accessors.ToFriendlyString()} {virt}{fieldSymbol.Type} {propertyName}");
source.AppendLine($"{indent}{{");
}
public static void GenerateAutoProperty(
this StringBuilder source,
Accessibility accessors,
string type,
string propertyName,
Accessibility? getAccessor,
Accessibility? setAccessor,
string indent,
bool useInit = false,
string defaultValue = null,
bool isOverride = false
)
{
if (getAccessor == null && setAccessor == null)
{
throw new ArgumentNullException($"Must specify a {nameof(getAccessor)} or {nameof(setAccessor)} parameter");
}
var getter = getAccessor == null ?
"" :
$"{(getAccessor != Accessibility.NotApplicable ? $"{getAccessor.Value.ToFriendlyString()} " : "")}get;";
var getterSpace = getAccessor != null ? " " : "";
var setOrInit = useInit ? "init;" : "set;";
var setterAccessor = setAccessor is null or Accessibility.NotApplicable
? ""
: $"{setAccessor.Value.ToFriendlyString() ?? ""} ";
var setter = setAccessor == null ? "" : $"{getterSpace}{setterAccessor}{setOrInit}";
var propertyAccessor = accessors == Accessibility.NotApplicable ? "" : $"{accessors.ToFriendlyString()} ";
var printOverride = isOverride ? "override " : "";
var printDefaultValue = defaultValue != null ? $"{(setAccessor != null ? " =" : "")} {defaultValue};" : "";
var printGetterSetter = setAccessor == null ? "=>" : $"{{ {getter}{setter} }}";
source.AppendLine($"{indent}{propertyAccessor}{printOverride}{type} {propertyName} {printGetterSetter}{printDefaultValue}");
}
public static void GeneratePropertyEnd(this StringBuilder source, string indent) => source.AppendLine($"{indent}}}");
public static void GeneratePropertyGetterReturnsField(
this StringBuilder source,
string indent,
IFieldSymbol fieldSymbol,
Accessibility Accessibility
)
{
var accessor = Accessibility != Accessibility.NotApplicable ? $"{Accessibility.ToFriendlyString()} " : "";
source.AppendLine($"{indent}{accessor}get => {fieldSymbol.Name};");
}
public static void GeneratePropertyGetterStart(
this StringBuilder source,
string indent,
bool useExpression,
Accessibility Accessibility
)
{
var accessor = Accessibility != Accessibility.NotApplicable ? $"{Accessibility.ToFriendlyString()} " : "";
var expression = useExpression ? " => " : $"\n{indent}{{";
source.AppendLine($"{indent}{accessor}get{expression}");
}
public static void GeneratePropertyGetSetEnd(this StringBuilder source, string indent, bool useExpression)
{
if (!useExpression)
{
source.AppendLine($"{indent}}}");
}
}
public static void GeneratePropertySetterStart(
this StringBuilder source,
string indent,
bool useExpression,
Accessibility Accessibility,
bool useInit = false
)
{
var init = useInit ? "init" : "set";
var expression = useExpression ? " => " : $"\n{indent}{{";
var accessor = Accessibility != Accessibility.NotApplicable ? $"{Accessibility.ToFriendlyString()} " : "";
source.AppendLine($"{indent}{accessor}{init}{expression}");
}
}
}

View file

@ -1,62 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SymbolMetadata.Builtin.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 Microsoft.CodeAnalysis;
namespace SerializationGenerator
{
public static partial class SymbolMetadata
{
public const string LIST_CLASS = "System.Collections.Generic.List`1";
public const string HASHSET_CLASS = "System.Collections.Generic.HashSet`1";
public const string IPADDRESS_CLASS = "System.Net.IPAddress";
public const string KEYVALUEPAIR_STRUCT = "System.Collections.Generic.KeyValuePair";
public const string TIMESPAN_STRUCT = "System.TimeSpan";
public static bool IsTimeSpan(this ISymbol symbol, Compilation compilation) =>
symbol.Equals(
compilation.GetTypeByMetadataName(TIMESPAN_STRUCT),
SymbolEqualityComparer.Default
);
public static bool IsIpAddress(this ISymbol symbol, Compilation compilation) =>
symbol.Equals(
compilation.GetTypeByMetadataName(IPADDRESS_CLASS),
SymbolEqualityComparer.Default
);
public static bool IsKeyValuePair(this ISymbol symbol, Compilation compilation) =>
(symbol as INamedTypeSymbol)?.ConstructedFrom.Equals(
compilation.GetTypeByMetadataName(KEYVALUEPAIR_STRUCT),
SymbolEqualityComparer.Default
) == true;
public static bool IsList(this ISymbol symbol, Compilation compilation) =>
(symbol as INamedTypeSymbol)?.ConstructedFrom.Equals(
compilation.GetTypeByMetadataName(LIST_CLASS),
SymbolEqualityComparer.Default
) == true;
public static bool IsHashSet(this ISymbol symbol, Compilation compilation) =>
(symbol as INamedTypeSymbol)?.ConstructedFrom.Equals(
compilation.GetTypeByMetadataName(HASHSET_CLASS),
SymbolEqualityComparer.Default
) == true;
public static bool IsPrimitiveFromTypeDisplayString(string type) =>
type is "bool" or "sbyte" or "short" or "int" or "long" or "byte" or "ushort"
or "uint" or "ulong" or "float" or "double" or "string" or "decimal";
}
}

View file

@ -1,220 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SymbolMetadata.UO.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.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
namespace SerializationGenerator
{
public static partial class SymbolMetadata
{
public const string INVALIDATEPROPERTIES_ATTRIBUTE = "Server.InvalidatePropertiesAttribute";
public const string AFTERDESERIALIZATION_ATTRIBUTE = "Server.AfterDeserializationAttribute";
public const string SERIALIZABLE_ATTRIBUTE = "Server.SerializableAttribute";
public const string EMBEDDED_SERIALIZABLE_ATTRIBUTE = "Server.EmbeddedSerializableAttribute";
public const string SERIALIZABLE_PARENT_ATTRIBUTE = "Server.SerializableParentAttribute";
public const string SERIALIZABLE_FIELD_ATTRIBUTE = "Server.SerializableFieldAttribute";
public const string SERIALIZABLE_FIELD_ATTR_ATTRIBUTE = "Server.SerializableFieldAttrAttribute";
public const string SERIALIZABLE_INTERFACE = "Server.ISerializable";
public const string GENERIC_WRITER_INTERFACE = "Server.IGenericWriter";
public const string GENERIC_READER_INTERFACE = "Server.IGenericReader";
public const string DELTA_DATE_TIME_ATTRIBUTE = "Server.DeltaDateTimeAttribute";
public const string INTERN_STRING_ATTRIBUTE = "Server.InternStringAttribute";
public const string ENCODED_INT_ATTRIBUTE = "Server.EncodedIntAttribute";
public const string TIDY_ATTRIBUTE = "Server.TidyAttribute";
public const string POINT2D_STRUCT = "Server.Point2D";
public const string POINT3D_STRUCT = "Server.Point3D";
public const string RECTANGLE2D_STRUCT = "Server.Rectangle2D";
public const string RECTANGLE3D_STRUCT = "Server.Rectangle3D";
public const string RACE_CLASS = "Server.Race";
public const string MAP_CLASS = "Server.Map";
public const string TIMER_CLASS = "Server.Timer";
public const string TIMER_DRIFT_ATTRIBUTE = "Server.TimerDriftAttribute";
public const string DESERIALIZE_TIMER_FIELD_ATTRIBUTE = "Server.DeserializeTimerFieldAttribute";
public const string SERIALIZABLE_FIELD_SAVE_FLAG_ATTRIBUTE = "Server.SerializableFieldSaveFlagAttribute";
public const string SERIALIZABLE_FIELD_DEFAULT_ATTRIBUTE = "Server.SerializableFieldDefaultAttribute";
public const string RAW_SERIALIZABLE_INTERFACE = "Server.IRawSerializable";
public static bool IsTimerDrift(this AttributeData attr, Compilation compilation) =>
attr?.IsAttribute(compilation.GetTypeByMetadataName(TIMER_DRIFT_ATTRIBUTE)) == true;
public static bool IsTimer(this ITypeSymbol symbol, Compilation compilation) =>
symbol.CanBeConstructedFrom(compilation.GetTypeByMetadataName(TIMER_CLASS));
public static bool IsEncodedInt(this AttributeData attr, Compilation compilation) =>
attr?.IsAttribute(compilation.GetTypeByMetadataName(ENCODED_INT_ATTRIBUTE)) == true;
public static bool IsDeltaDateTime(this AttributeData attr, Compilation compilation) =>
attr?.IsAttribute(compilation.GetTypeByMetadataName(DELTA_DATE_TIME_ATTRIBUTE)) == true;
public static bool IsInternString(this AttributeData attr, Compilation compilation) =>
attr?.IsAttribute(compilation.GetTypeByMetadataName(INTERN_STRING_ATTRIBUTE)) == true;
public static bool IsTidy(this AttributeData attr, Compilation compilation) =>
attr?.IsAttribute(compilation.GetTypeByMetadataName(TIDY_ATTRIBUTE)) == true;
public static bool IsAttribute(this AttributeData attr, ISymbol symbol) =>
attr?.AttributeClass?.Equals(symbol, SymbolEqualityComparer.Default) == true;
public static bool IsEnum(this ITypeSymbol symbol) =>
symbol.SpecialType == SpecialType.System_Enum || symbol.TypeKind == TypeKind.Enum;
public static bool HasSerializableInterface(
this ITypeSymbol symbol,
Compilation compilation,
ImmutableArray<INamedTypeSymbol> serializableTypes
) =>
symbol.ContainsInterface(compilation.GetTypeByMetadataName(SERIALIZABLE_INTERFACE)) ||
serializableTypes.Contains(symbol);
public static bool HasRawSerializableInterface(
this ITypeSymbol symbol,
Compilation compilation,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes
) =>
symbol.ContainsInterface(compilation.GetTypeByMetadataName(RAW_SERIALIZABLE_INTERFACE)) ||
embeddedSerializableTypes.Contains(symbol);
public static bool Contains(this ImmutableArray<INamedTypeSymbol> symbols, ITypeSymbol? symbol) =>
symbol is INamedTypeSymbol namedSymbol &&
symbols.Contains(namedSymbol, SymbolEqualityComparer.Default) || symbols.Contains(symbol?.BaseType);
public static bool HasGenericReaderCtor(
this INamedTypeSymbol symbol,
Compilation compilation,
ISymbol? parentSymbol,
out bool requiresParent
)
{
var genericReaderInterface = compilation.GetTypeByMetadataName(GENERIC_READER_INTERFACE);
var genericCtor = symbol.Constructors.FirstOrDefault(
m => !m.IsStatic &&
m.MethodKind == MethodKind.Constructor &&
m.Parameters.Length >= 1 &&
m.Parameters.Length <= 2 &&
SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, genericReaderInterface)
);
requiresParent = genericCtor?.Parameters.Length == 2 && SymbolEqualityComparer.Default.Equals(genericCtor.Parameters[1].Type, parentSymbol);
return genericCtor != null;
}
public static bool HasPublicSerializeMethod(
this ITypeSymbol symbol,
Compilation compilation,
ImmutableArray<INamedTypeSymbol> serializableTypes
)
{
var genericWriterInterface = compilation.GetTypeByMetadataName(GENERIC_WRITER_INTERFACE);
return symbol.GetAllMethods("Serialize")
.Any(
m => !m.IsStatic &&
m.ReturnsVoid &&
m.Parameters.Length == 1 &&
SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, genericWriterInterface) &&
m.DeclaredAccessibility == Accessibility.Public
);
}
public static bool HasPublicDeserializeMethod(
this ITypeSymbol symbol,
Compilation compilation,
ImmutableArray<INamedTypeSymbol> serializableTypes
)
{
var genericReaderInterface = compilation.GetTypeByMetadataName(GENERIC_READER_INTERFACE);
return symbol.GetAllMethods("Deserialize")
.Any(
m => !m.IsStatic &&
m.ReturnsVoid &&
m.Parameters.Length == 1 &&
SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, genericReaderInterface) &&
m.DeclaredAccessibility == Accessibility.Public
);
}
public static bool IsPoint2D(this ISymbol symbol, Compilation compilation) =>
symbol.Equals(
compilation.GetTypeByMetadataName(POINT2D_STRUCT),
SymbolEqualityComparer.Default
);
public static bool IsPoint3D(this ISymbol symbol, Compilation compilation) =>
symbol.Equals(
compilation.GetTypeByMetadataName(POINT3D_STRUCT),
SymbolEqualityComparer.Default
);
public static bool IsRectangle2D(this ISymbol symbol, Compilation compilation) =>
symbol.Equals(
compilation.GetTypeByMetadataName(RECTANGLE2D_STRUCT),
SymbolEqualityComparer.Default
);
public static bool IsRectangle3D(this ISymbol symbol, Compilation compilation) =>
symbol.Equals(
compilation.GetTypeByMetadataName(RECTANGLE3D_STRUCT),
SymbolEqualityComparer.Default
);
public static bool IsRace(this ISymbol symbol, Compilation compilation) =>
symbol.Equals(
compilation.GetTypeByMetadataName(RACE_CLASS),
SymbolEqualityComparer.Default
);
public static bool IsMap(this ISymbol symbol, Compilation compilation) =>
symbol.Equals(
compilation.GetTypeByMetadataName(MAP_CLASS),
SymbolEqualityComparer.Default
);
public static AttributeData? GetAttribute(this ISymbol symbol, ISymbol attrSymbol) =>
symbol
.GetAttributes()
.FirstOrDefault(
ad => ad.AttributeClass != null && SymbolEqualityComparer.Default.Equals(ad.AttributeClass, attrSymbol)
);
public static bool WillBeSerializable(this INamedTypeSymbol classSymbol, Compilation compilation, out AttributeData? attributeData)
{
var serializableInterface = compilation.GetTypeByMetadataName(SERIALIZABLE_INTERFACE);
if (!classSymbol.ContainsInterface(serializableInterface))
{
attributeData = null;
return false;
}
var serializableEntityAttribute =
compilation.GetTypeByMetadataName(SERIALIZABLE_ATTRIBUTE);
attributeData = classSymbol.GetAttribute(serializableEntityAttribute);
return attributeData != null;
}
public static bool IsEmbeddedSerializable(this INamedTypeSymbol classSymbol, Compilation compilation, out AttributeData? attributeData)
{
var embeddedSerializableEntityAttribute =
compilation.GetTypeByMetadataName(EMBEDDED_SERIALIZABLE_ATTRIBUTE);
attributeData = classSymbol.GetAttribute(embeddedSerializableEntityAttribute);
return attributeData != null;
}
}
}

View file

@ -1,28 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: Utility.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.Collections.Generic;
namespace SerializationGenerator
{
public static class Utility
{
public static void Deconstruct<T1, T2>(this KeyValuePair<T1, T2> tuple, out T1 key, out T2 value)
{
key = tuple.Key;
value = tuple.Value;
}
}
}

View file

@ -1 +0,0 @@
Output/

View file

@ -1,102 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: Application.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.Collections.Immutable;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
using SerializationGenerator;
namespace SerializationSchemaGenerator
{
public static class Application
{
public static void Main(string[] args)
{
if (args.Length < 1)
{
throw new ArgumentException("Usage: dotnet SerializationSchemaGenerator.dll <path to solution>");
}
var solutionPath = args[0];
Parallel.ForEach(
SourceCodeAnalysis.GetCompilation(solutionPath),
(projectCompilation) =>
{
var (project, compilation) = projectCompilation;
if (project.Name.EndsWith(".Tests", StringComparison.Ordinal) || project.Name == "Benchmarks")
{
return;
}
var projectFile = new FileInfo(project.FilePath!);
var projectPath = projectFile.Directory?.FullName;
var migrationPath = Path.Join(projectPath, "Migrations");
Directory.CreateDirectory(migrationPath);
var syntaxReceiver = new SerializerSyntaxReceiver();
foreach (var syntaxTree in compilation.SyntaxTrees)
{
var root = syntaxTree.GetRoot();
var syntaxVisitor = new SyntaxVisitor(compilation.GetSemanticModel(syntaxTree), syntaxReceiver);
syntaxVisitor.Visit(root);
}
var jsonOptions = new JsonSerializerOptions
{
WriteIndented = true,
AllowTrailingCommas = true,
IgnoreNullValues = true,
ReadCommentHandling = JsonCommentHandling.Skip
};
var serializableTypes = syntaxReceiver.SerializableList;
var embeddedSerializableTypes = syntaxReceiver.EmbeddedSerializableList;
foreach (var (classSymbol, (attributeData, fieldsList)) in syntaxReceiver.ClassAndFields)
{
var source = compilation.GenerateSerializationPartialClass(
classSymbol,
attributeData,
migrationPath,
false,
jsonOptions,
fieldsList.ToImmutableArray(),
serializableTypes,
embeddedSerializableTypes
);
}
foreach (var (classSymbol, (attributeData, fieldsList)) in syntaxReceiver.EmbeddedClassAndFields)
{
var source = compilation.GenerateSerializationPartialClass(
classSymbol,
attributeData,
migrationPath,
true,
jsonOptions,
fieldsList.ToImmutableArray(),
serializableTypes,
embeddedSerializableTypes
);
}
}
);
}
}
}

View file

@ -1,20 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<OutDir>Output</OutDir>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\SerializationGenerator\SerializationGenerator.csproj">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build.Locator" Version="1.4.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.11.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="3.11.0" />
</ItemGroup>
</Project>

View file

@ -1,49 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SourceCodeAnalysis.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.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.MSBuild;
namespace SerializationSchemaGenerator
{
public static class SourceCodeAnalysis
{
public static List<(Project, Compilation)> GetCompilation(string solutionPath)
{
if (!File.Exists(solutionPath) || !solutionPath.EndsWith(".sln", StringComparison.Ordinal))
{
throw new FileNotFoundException($"Could not open a valid solution at location {solutionPath}");
}
MSBuildLocator.RegisterDefaults();
var workspace = MSBuildWorkspace.Create();
var solutionToAnalyze = workspace.OpenSolutionAsync(solutionPath).Result;
var results = solutionToAnalyze.Projects.AsParallel()
.Select((project) => (project, project?.GetCompilationAsync().Result))
.Where((value) => value.Result != null)
.ToList();
return results;
}
}
}

View file

@ -1,52 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SyntaxVisitor.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 Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using SerializationGenerator;
namespace SerializationSchemaGenerator
{
public class SyntaxVisitor : CSharpSyntaxWalker
{
private readonly SemanticModel _semanticModel;
private readonly SerializerSyntaxReceiver _syntaxReceiver;
public SyntaxVisitor(SemanticModel semanticModel, SerializerSyntaxReceiver syntaxReceiver)
{
_semanticModel = semanticModel;
_syntaxReceiver = syntaxReceiver;
}
public override void VisitClassDeclaration(ClassDeclarationSyntax node)
{
base.VisitClassDeclaration(node);
_syntaxReceiver.OnVisitSyntaxNode(node, _semanticModel);
}
public override void VisitFieldDeclaration(FieldDeclarationSyntax node)
{
base.VisitFieldDeclaration(node);
_syntaxReceiver.OnVisitSyntaxNode(node, _semanticModel);
}
public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node)
{
base.VisitPropertyDeclaration(node);
_syntaxReceiver.OnVisitSyntaxNode(node, _semanticModel);
}
}
}

View file

@ -193,7 +193,7 @@ namespace Server.Tests.Network
public static void AppendTo(this GumpAlphaRegion g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpAlphaRegion.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("checkertrans"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
@ -202,7 +202,7 @@ namespace Server.Tests.Network
public static void AppendTo(this GumpBackground g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpBackground.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("resizepic"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.GumpID);
@ -212,7 +212,7 @@ namespace Server.Tests.Network
public static void AppendTo(this GumpButton g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpButton.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("button"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.NormalID);
@ -224,7 +224,7 @@ namespace Server.Tests.Network
public static void AppendTo(this GumpCheck g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpButton.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("checkbox"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.InactiveID);
@ -237,18 +237,18 @@ namespace Server.Tests.Network
public static void AppendTo(this GumpGroup g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpGroup.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("group"));
disp.AppendLayout(g.Group);
}
public static void AppendTo(this GumpECHandleInput g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpECHandleInput.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("echandleinput"));
}
public static void AppendTo(this GumpHtml g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpHtml.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("htmlgump"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
@ -264,7 +264,7 @@ namespace Server.Tests.Network
{
case GumpHtmlLocalizedType.Plain:
{
disp.AppendLayout(GumpHtmlLocalized.LayoutNamePlain);
disp.AppendLayout(Gump.StringToBuffer("xmfhtmlgump"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
@ -279,7 +279,7 @@ namespace Server.Tests.Network
case GumpHtmlLocalizedType.Color:
{
disp.AppendLayout(GumpHtmlLocalized.LayoutNameColor);
disp.AppendLayout(Gump.StringToBuffer("xmfhtmlgumpcolor"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
@ -295,7 +295,7 @@ namespace Server.Tests.Network
case GumpHtmlLocalizedType.Args:
{
disp.AppendLayout(GumpHtmlLocalized.LayoutNameArgs);
disp.AppendLayout(Gump.StringToBuffer("xmfhtmltok"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
@ -314,27 +314,27 @@ namespace Server.Tests.Network
public static void AppendTo(this GumpImage g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpImage.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("gumppic"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.GumpID);
if (g.Hue != 0)
{
disp.AppendLayout(GumpImage.HueEquals);
disp.AppendLayoutNS(" hue=");
disp.AppendLayoutNS(g.Hue);
}
if (!string.IsNullOrEmpty(g.Class))
{
disp.AppendLayout(GumpImage.ClassEquals);
disp.AppendLayoutNS(g.Class);
disp.AppendLayoutNS(" class=");
disp.AppendLayout(Gump.StringToBuffer(g.Class));
}
}
public static void AppendTo(this GumpImageTileButton g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpImageTileButton.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("buttontileart"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.NormalID);
@ -347,17 +347,11 @@ namespace Server.Tests.Network
disp.AppendLayout(g.Hue);
disp.AppendLayout(g.Width);
disp.AppendLayout(g.Height);
if (g.LocalizedTooltip > 0)
{
disp.AppendLayout(GumpImageTileButton.LayoutTooltip);
disp.AppendLayout(g.LocalizedTooltip);
}
}
public static void AppendTo(this GumpImageTiled g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpImageTiled.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("gumppictiled"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
@ -367,7 +361,7 @@ namespace Server.Tests.Network
public static void AppendTo(this GumpItem g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(g.Hue == 0 ? GumpItem.LayoutName : GumpItem.LayoutNameHue);
disp.AppendLayout(Gump.StringToBuffer(g.Hue == 0 ? "tilepic" : "tilepichue"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.ItemID);
@ -380,13 +374,13 @@ namespace Server.Tests.Network
public static void AppendTo(this GumpItemProperty g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpItemProperty.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("itemproperty"));
disp.AppendLayout(g.Serial);
}
public static void AppendTo(this GumpLabel g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpLabel.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("text"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Hue);
@ -395,7 +389,7 @@ namespace Server.Tests.Network
public static void AppendTo(this GumpLabelCropped g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpLabelCropped.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("croppedtext"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
@ -406,19 +400,19 @@ namespace Server.Tests.Network
public static void AppendTo(this GumpMasterGump g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpMasterGump.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("mastergump"));
disp.AppendLayout(g.GumpID);
}
public static void AppendTo(this GumpPage g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpPage.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("page"));
disp.AppendLayout(g.Page);
}
public static void AppendTo(this GumpRadio g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpRadio.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("radio"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.InactiveID);
@ -431,7 +425,7 @@ namespace Server.Tests.Network
public static void AppendTo(this GumpSpriteImage g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpSpriteImage.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("picinpic"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.GumpID);
@ -443,7 +437,7 @@ namespace Server.Tests.Network
public static void AppendTo(this GumpTextEntry g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpTextEntry.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("textentry"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
@ -457,7 +451,7 @@ namespace Server.Tests.Network
public static void AppendTo(this GumpTextEntryLimited g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpTextEntryLimited.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("textentrylimited"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
@ -472,7 +466,7 @@ namespace Server.Tests.Network
public static void AppendTo(this GumpTooltip g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpTooltip.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("tooltip"));
disp.AppendLayout(g.Number);
if (!string.IsNullOrEmpty(g.Args))

View file

@ -3,17 +3,13 @@
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Moq" Version="4.18.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="Zlib.Bindings" Version="1.5.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5" />
<ProjectReference Include="..\Server\Server.csproj" />
<ProjectReference Include="..\UOContent\UOContent.csproj" />
<DataFiles Include="$(SolutionDir)\Distribution\Data\**" />
<DataFiles Update="..\..\Distribution\Data\Professions\LBR\prof.txt">
<Link>Data\Professions\LBR\prof.txt</Link>
</DataFiles>
</ItemGroup>
<Target Name="CopyData" AfterTargets="AfterBuild">
<Copy SourceFiles="@(DataFiles)" DestinationFolder="$(OutDir)\Data\%(RecursiveDir)" />

View file

@ -0,0 +1,72 @@
using System;
using Server.Buffers;
using Xunit;
namespace Server.Tests.Tests.Buffers;
[Collection("Sequential Tests")]
public class STArrayPoolTests
{
[Theory]
[InlineData(0, 0)]
[InlineData(2, 16)]
[InlineData(56, 64)]
[InlineData(120, 128)]
[InlineData(65535, 65536)]
[InlineData(1024 * 1024 * 15, 1024 * 1024 * 16)]
public void ValidMinimumLengths(int requestedLength, int expectedLength)
{
var arr = STArrayPool<byte>.Shared.Rent(requestedLength);
Assert.Equal(expectedLength, arr.Length);
}
[Fact]
public void NegativeLengthThrows()
{
Assert.Throws<ArgumentOutOfRangeException>(
() =>
{
var arr = STArrayPool<byte>.Shared.Rent(-1);
}
);
}
[Fact]
public void CachesOnlyUpToCPUCountPerBucket()
{
STArrayPool<byte>.Shared.ResetForTesting();
var cores = Environment.ProcessorCount;
var arrays1 = new byte[cores * 8 + 2][]; // 1 for the cache, and 8 * CPU for the stacks
var weakReferences1 = new WeakReference[cores * 8 + 2];
var arrays2 = new byte[cores * 8 + 2][]; // 1 for the cache, and 8 * CPU for the stacks
var weakReferences2 = new WeakReference[cores * 8 + 2];
for (var i = 0; i < arrays1.Length; i++)
{
arrays1[i] = STArrayPool<byte>.Shared.Rent(32);
weakReferences1[i] = new WeakReference(arrays1[i]);
arrays2[i] = STArrayPool<byte>.Shared.Rent(64);
weakReferences2[i] = new WeakReference(arrays2[i]);
}
for (var i = 0; i < arrays1.Length; i++)
{
STArrayPool<byte>.Shared.Return(arrays1[i]);
arrays1[i] = null;
STArrayPool<byte>.Shared.Return(arrays2[i]);
arrays2[i] = null;
}
GC.Collect();
for (var i = 0; i < weakReferences1.Length; i++)
{
// When the last one is returned, the one right before it is dropped.
Assert.Equal(i != weakReferences1.Length - 2, weakReferences1[i].IsAlive);
Assert.Equal(i != weakReferences2.Length - 2, weakReferences2[i].IsAlive);
}
}
}

View file

@ -0,0 +1,29 @@
using Server.Collections;
using Xunit;
namespace Server.Tests;
public class BitArrayTests
{
[Fact]
public void TestBitArray()
{
var bitArray = new BitArray(700); // Restricted Spells;
bitArray.Set(5, true);
bitArray.Set(39, true);
bitArray.Set(125, true);
// Simulate World Saving
var writer = new BufferWriter(1024, false);
writer.Write(bitArray); // Save it to a file
// Simulate World Loading
var reader = new BufferReader(writer.Buffer);
var bitArrayTest = reader.ReadBitArray();
Assert.Equal(700, bitArrayTest.Length);
for (var i = 0; i < bitArrayTest.Length; i++)
{
Assert.Equal(i is 5 or 39 or 125, bitArrayTest.Get(i));
}
}
}

View file

@ -0,0 +1,102 @@
using System;
using Moq;
using Server.Collections;
using Server.Random;
using Xunit;
namespace Server.Tests;
public sealed class PooledRefQueueTests : IDisposable
{
public void Dispose() => RandomSources.SetRng(null);
private static void PrepareRng(int queueCount, int rngValue)
{
Mock<IRandomSource> mockRng = new Mock<IRandomSource>();
mockRng
.Setup(rng => rng.Next(It.IsAny<int>()))
.Returns(
(int size) =>
{
Assert.Equal(queueCount, size);
return rngValue;
}
);
RandomSources.SetRng(mockRng.Object);
}
[Fact]
public void TestPeekRandom1()
{
// Random value for _head = 0, _tail = 5, _size = 5,
using var queue = PooledRefQueue<int>.Create(10);
queue.Enqueue(0);
queue.Enqueue(1);
queue.Enqueue(2);
queue.Enqueue(3); // <-----
queue.Enqueue(4);
queue.Enqueue(5);
PrepareRng(6, 3);
Assert.Equal(3, queue.PeekRandom());
}
[Fact]
public void TestPeekRandom2()
{
// Random value for _head = 3, _tail = 10, _size = 7,
using var queue = PooledRefQueue<int>.Create(10);
queue.Enqueue(0);
queue.Enqueue(1);
queue.Enqueue(2);
queue.Enqueue(3);
queue.Enqueue(4);
queue.Enqueue(5);
queue.Enqueue(6); // <---
queue.Enqueue(7);
queue.Enqueue(8);
queue.Enqueue(9);
queue.Dequeue();
queue.Dequeue();
queue.Dequeue();
PrepareRng(7, 3);
Assert.Equal(6, queue.PeekRandom());
}
[Theory]
[InlineData(3, 6)]
[InlineData(8, 11)]
[InlineData(6, 9)]
[InlineData(7, 10)]
public void TestPeekRandom3(int rngValue, int expectedIndex)
{
// Random value for _head = 3, _tail = 2, _size = 10,
using var queue = PooledRefQueue<int>.Create(10);
queue.Enqueue(0);
queue.Enqueue(1);
queue.Enqueue(2);
queue.Enqueue(3);
queue.Enqueue(4);
queue.Enqueue(5);
queue.Enqueue(6);
queue.Enqueue(7);
queue.Enqueue(8);
queue.Enqueue(9);
queue.Dequeue();
queue.Dequeue();
queue.Dequeue();
queue.Enqueue(10);
queue.Enqueue(11);
queue.Enqueue(12);
PrepareRng(10, rngValue);
Assert.Equal(expectedIndex, queue.PeekRandom());
}
}

View file

@ -0,0 +1,19 @@
using Xunit;
namespace Server.Tests;
public class LocalizationEntryTests
{
[Fact]
public void TestClilocAsParameter()
{
Localization.Add("enu", 500002, "This tests ~1_NUMBER~ as parameters.");
Localization.Add("enu", 500003, "clilocs");
string numericFormatter = Localization.Format(500002, "enu", $"{500003:#}");
string stringParam = Localization.Format(500002, "enu", $"{"#500003"}");
Assert.Equal("This tests clilocs as parameters.", numericFormatter);
Assert.Equal("This tests clilocs as parameters.", stringParam);
}
}

View file

@ -24,9 +24,9 @@ namespace Server.Tests.Network
public void TestDisplaySignGump()
{
Serial gumpSerial = (Serial)0x1000;
var gumpId = 100;
var unknownString = "This is an unknown string";
var caption = "This is a caption";
const int gumpId = 100;
const string unknownString = "This is an unknown string";
const string caption = "This is a caption";
var expected = new DisplaySignGump(gumpSerial, gumpId, unknownString, caption).Compile();

View file

@ -50,7 +50,7 @@ namespace Server.Network
{
EnsureCapacity(256);
Stream.Write(!(vendor.FindItemOnLayer(Layer.ShopBuy) is Container buyPack) ? Serial.MinusOne : buyPack.Serial);
Stream.Write(vendor.FindItemOnLayer(Layer.ShopBuy) is not Container buyPack ? Serial.MinusOne : buyPack.Serial);
Stream.Write((byte)list.Count);

View file

@ -11,7 +11,7 @@ namespace Server.Tests.Network
{
private async void DelayedExecute(Action action)
{
await Task.Delay(5);
await Task.Delay(1);
action();
}
@ -130,8 +130,12 @@ namespace Server.Tests.Network
continue;
}
result.CopyFrom(new[] { expected_value, expected_value, expected_value, expected_value, expected_value, expected_value, expected_value, expected_value,
expected_value, expected_value, expected_value, expected_value, expected_value, expected_value, expected_value, expected_value });
result.CopyFrom(new[] {
expected_value, expected_value, expected_value, expected_value,
expected_value, expected_value, expected_value, expected_value,
expected_value, expected_value, expected_value, expected_value,
expected_value, expected_value, expected_value, expected_value
});
writer.Advance(16);
count += 16;

View file

@ -0,0 +1,37 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using Server.Network;
using Xunit;
namespace Server.Tests.Network;
public class PollGroupTests
{
[Fact]
public void TestPollGroup()
{
// var group = new KQueuePollGroup();
var nss = new NetState[2048];
var handles = new IntPtr[2048];
for (var i = 0; i < nss.Length; i++)
{
nss[i] = PacketTestUtilities.CreateTestNetState();
handles[i] = (IntPtr)nss[i].Handle;
}
GC.AddMemoryPressure(10000000000);
GC.Collect();
GC.RemoveMemoryPressure(10000000000);
GC.Collect();
Thread.Sleep(1000);
for (var i = 0; i < nss.Length; i++)
{
Assert.Equal(nss[i].Handle, (GCHandle)handles[i]);
}
// group.Dispose();
}
}

View file

@ -0,0 +1,37 @@
using System;
using Xunit;
namespace Server.Tests;
public class SerialTests
{
[Fact]
public void TestSerialTryFormatDefault()
{
var serial = (Serial)0xABCD1234u;
const string serialStr = "0xABCD1234";
Span<char> buffer = stackalloc char[serialStr.Length];
var result = serial.TryFormat(buffer, out var charsWritten, null, null);
Assert.True(result);
Assert.Equal(serialStr.Length, charsWritten);
Assert.Equal(serialStr, buffer.ToString());
var interpolated = $"{serial}";
Assert.Equal(serialStr, interpolated);
}
[Fact]
public void TestSerialTryFormatCustom()
{
var serial = (Serial)0xABCD1234u;
const string serialStr = "2882343476";
Span<char> buffer = stackalloc char[serialStr.Length];
var result = serial.TryFormat(buffer, out var charsWritten, "##", null);
Assert.True(result);
Assert.Equal(serialStr.Length, charsWritten);
Assert.Equal(serialStr, buffer.ToString());
var interpolated = $"{serial:##}";
Assert.Equal(serialStr, interpolated);
}
}

View file

@ -71,7 +71,7 @@ namespace Server
return 50;
}
if (!(objs[0] is CallPriorityAttribute attr))
if (objs[0] is not CallPriorityAttribute attr)
{
return 50;
}

View file

@ -129,7 +129,7 @@ namespace System.Buffers
public Span<T> GetSpan(int index)
{
if (index < 0 || index > 1)
if (index is < 0 or > 1)
{
throw new ArgumentOutOfRangeException(nameof(index));
}

View file

@ -32,6 +32,10 @@ namespace Server.Network
public int Position { get; private set; }
public int Remaining => Length - Position;
// Only used for debugging!
public ReadOnlySpan<byte> First => _first;
public ReadOnlySpan<byte> Second => _second;
public CircularBufferReader(ref CircularBuffer<byte> buffer) : this(buffer.GetSpan(0), buffer.GetSpan(1))
{
}
@ -58,12 +62,9 @@ namespace Server.Network
try
{
using var sw = new StreamWriter("Packets.log", true);
using var sw = new StreamWriter("unhandled-packets.log", true);
sw.WriteLine("Client: {0}: Unhandled packet 0x{1:X2}", state, _first[0]);
Utility.FormatBuffer(sw, _first.ToArray(), new Memory<byte>(_second.ToArray()));
sw.FormatBuffer(_first, _second, Length);
sw.WriteLine();
sw.WriteLine();
}
@ -244,7 +245,7 @@ namespace Server.Network
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadString(Encoding encoding, bool safeString = false, int fixedLength = -1)
{
int sizeT = TextEncoding.GetByteLengthForEncoding(encoding);
int byteLength = encoding.GetByteLengthForEncoding();
bool isFixedLength = fixedLength > -1;
@ -253,7 +254,7 @@ namespace Server.Network
if (isFixedLength)
{
size = fixedLength * sizeT;
size = fixedLength * byteLength;
if (size > Remaining)
{
throw new OutOfMemoryException();
@ -261,7 +262,7 @@ namespace Server.Network
}
else
{
size = remaining - (remaining & (sizeT - 1));
size = remaining - (remaining & (byteLength - 1));
}
ReadOnlySpan<byte> span;
@ -272,7 +273,7 @@ namespace Server.Network
var firstLength = Math.Min(_first.Length - Position, size);
// Find terminator
index = _first.Slice(Position, firstLength).IndexOfTerminator(sizeT);
index = _first.Slice(Position, firstLength).IndexOfTerminator(byteLength);
if (index < 0)
{
@ -284,7 +285,7 @@ namespace Server.Network
}
else
{
index = _second[..remaining].IndexOfTerminator(sizeT);
index = _second[..remaining].IndexOfTerminator(byteLength);
int secondLength = index < 0 ? remaining : index;
int length = firstLength + secondLength;
@ -294,7 +295,7 @@ namespace Server.Network
_first[Position..].CopyTo(bytes);
_second[..secondLength].CopyTo(bytes[firstLength..]);
Position += length + (index >= 0 ? sizeT : 0);
Position += length + (index >= 0 ? byteLength : 0);
return TextEncoding.GetString(bytes, encoding, safeString);
}
}
@ -305,7 +306,7 @@ namespace Server.Network
{
size = Math.Min(remaining, size);
span = _second.Slice( Position - _first.Length, size);
index = span.IndexOfTerminator(sizeT);
index = span.IndexOfTerminator(byteLength);
if (index >= 0)
{
@ -317,7 +318,7 @@ namespace Server.Network
}
}
Position += isFixedLength ? size : index + sizeT;
Position += isFixedLength ? size : index + byteLength;
return TextEncoding.GetString(span, encoding, safeString);
}

View file

@ -0,0 +1,70 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2022 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: PooledArraySpanFormattable.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/>. *
*************************************************************************/
#nullable enable
using System;
namespace Server.Buffers;
public struct PooledArraySpanFormattable : ISpanFormattable, IDisposable
{
private char[] _arrayToReturnToPool;
private int _pos;
private string _value;
public PooledArraySpanFormattable(char[] arrayToReturnToPool, int length)
{
_arrayToReturnToPool = arrayToReturnToPool;
_pos = length;
_value = null;
}
public ReadOnlySpan<char> Chars => _arrayToReturnToPool.AsSpan(.._pos);
public static implicit operator string(PooledArraySpanFormattable f) => f.ToString();
public string ToString(string? format = null, IFormatProvider formatProvider = null)
{
_value ??= new string(_arrayToReturnToPool.AsSpan(0, _pos));
STArrayPool<char>.Shared.Return(_arrayToReturnToPool);
_arrayToReturnToPool = null;
return _value;
}
public bool TryFormat(
Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default,
IFormatProvider provider = null
)
{
if (destination.Length < _pos)
{
charsWritten = 0;
return false;
}
_arrayToReturnToPool.AsSpan(0, _pos).CopyTo(destination);
charsWritten = _pos;
return true;
}
public void Dispose()
{
STArrayPool<char>.Shared.Return(_arrayToReturnToPool);
_arrayToReturnToPool = null;
this = default; // Defensive clear
}
}

View file

@ -0,0 +1,600 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.CompilerServices;
namespace Server.Buffers;
/// <summary>Provides a handler to interpolate strings which UNSAFELY exposes it's internal character span.</summary>
[InterpolatedStringHandler]
public ref struct RawInterpolatedStringHandler
{
// Implementation note:
// As this type lives in CompilerServices and is only intended to be targeted by the compiler,
// public APIs eschew argument validation logic in a variety of places, e.g. allowing a null input
// when one isn't expected to produce a NullReferenceException rather than an ArgumentNullException.
/// <summary>Expected average length of formatted data used for an individual interpolation expression result.</summary>
/// <remarks>
/// This is inherited from string.Format, and could be changed based on further data.
/// string.Format actually uses `format.Length + args.Length * 8`, but format.Length
/// includes the format items themselves, e.g. "{0}", and since it's rare to have double-digit
/// numbers of items, we bump the 8 up to 11 to account for the three extra characters in "{d}",
/// since the compiler-provided base length won't include the equivalent character count.
/// </remarks>
private const int GuessedLengthPerHole = 11;
/// <summary>Minimum size array to rent from the pool.</summary>
/// <remarks>Same as stack-allocation size used today by string.Format.</remarks>
private const int MinimumArrayPoolLength = 256;
/// <summary>Optional provider to pass to IFormattable.ToString or ISpanFormattable.TryFormat calls.</summary>
private readonly IFormatProvider? _provider;
/// <summary>Array rented from the array pool and used to back <see cref="_chars"/>.</summary>
private char[]? _arrayToReturnToPool;
/// <summary>The span to write into.</summary>
private Span<char> _chars;
/// <summary>Position at which to write the next character.</summary>
private int _pos;
/// <summary>Whether <see cref="_provider"/> provides an ICustomFormatter.</summary>
/// <remarks>
/// Custom formatters are very rare. We want to support them, but it's ok if we make them more expensive
/// in order to make them as pay-for-play as possible. So, we avoid adding another reference type field
/// to reduce the size of the handler and to reduce required zero'ing, by only storing whether the provider
/// provides a formatter, rather than actually storing the formatter. This in turn means, if there is a
/// formatter, we pay for the extra interface call on each AppendFormatted that needs it.
/// </remarks>
private readonly bool _hasCustomFormatter;
/// <summary>Creates a handler used to translate an interpolated string into a <see cref="string"/>.</summary>
/// <param name="literalLength">The number of constant characters outside of interpolation expressions in the interpolated string.</param>
/// <param name="formattedCount">The number of interpolation expressions in the interpolated string.</param>
/// <remarks>This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly.</remarks>
public RawInterpolatedStringHandler(int literalLength, int formattedCount)
{
_provider = null;
_chars = _arrayToReturnToPool = STArrayPool<char>.Shared.Rent(GetDefaultLength(literalLength, formattedCount));
_pos = 0;
_hasCustomFormatter = false;
}
/// <summary>Creates a handler used to translate an interpolated string into a <see cref="string"/>.</summary>
/// <param name="literalLength">The number of constant characters outside of interpolation expressions in the interpolated string.</param>
/// <param name="formattedCount">The number of interpolation expressions in the interpolated string.</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <remarks>This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly.</remarks>
public RawInterpolatedStringHandler(int literalLength, int formattedCount, IFormatProvider? provider)
{
_provider = provider;
_chars = _arrayToReturnToPool = STArrayPool<char>.Shared.Rent(GetDefaultLength(literalLength, formattedCount));
_pos = 0;
_hasCustomFormatter = provider is not null && HasCustomFormatter(provider);
}
/// <summary>Derives a default length with which to seed the handler.</summary>
/// <param name="literalLength">The number of constant characters outside of interpolation expressions in the interpolated string.</param>
/// <param name="formattedCount">The number of interpolation expressions in the interpolated string.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] // becomes a constant when inputs are constant
internal static int GetDefaultLength(int literalLength, int formattedCount) =>
Math.Max(MinimumArrayPoolLength, literalLength + formattedCount * GuessedLengthPerHole);
/// <summary>Clears the handler, returning any rented array to the pool.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] // used only on a few hot paths
public void Clear()
{
char[]? toReturn = _arrayToReturnToPool;
this = default; // defensive clear
if (toReturn is not null)
{
STArrayPool<char>.Shared.Return(toReturn);
}
}
/// <summary>Gets a span of the written characters thus far.</summary>
public ReadOnlySpan<char> Text => _chars[.._pos];
/// <summary>Writes the specified string to the handler.</summary>
/// <param name="value">The string to write.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AppendLiteral(string value)
{
if (value.Length == 1)
{
Span<char> chars = _chars;
int pos = _pos;
if ((uint)pos < (uint)chars.Length)
{
chars[pos] = value[0];
_pos = pos + 1;
}
else
{
GrowThenCopyString(value);
}
return;
}
AppendStringDirect(value);
}
/// <summary>Writes the specified string to the handler.</summary>
/// <param name="value">The string to write.</param>
private void AppendStringDirect(string value)
{
if (value.TryCopyTo(_chars[_pos..]))
{
_pos += value.Length;
}
else
{
GrowThenCopyString(value);
}
}
#region AppendFormatted
// Design note:
// The compiler requires a AppendFormatted overload for anything that might be within an interpolation expression;
// if it can't find an appropriate overload, for handlers in general it'll simply fail to compile.
// (For target-typing to string where it uses DefaultInterpolatedStringHandler implicitly, it'll instead fall back to
// its other mechanisms, e.g. using string.Format. This fallback has the benefit that if we miss a case,
// interpolated strings will still work, but it has the downside that a developer generally won't know
// if the fallback is happening and they're paying more.)
//
// At a minimum, then, we would need an overload that accepts:
// (object value, int alignment = 0, string? format = null)
// Such an overload would provide the same expressiveness as string.Format. However, this has several
// shortcomings:
// - Every value type in an interpolation expression would be boxed.
// - ReadOnlySpan<char> could not be used in interpolation expressions.
// - Every AppendFormatted call would have three arguments at the call site, bloating the IL further.
// - Every invocation would be more expensive, due to lack of specialization, every call needing to account
// for alignment and format, etc.
//
// To address that, we could just have overloads for T and ReadOnlySpan<char>:
// (T)
// (T, int alignment)
// (T, string? format)
// (T, int alignment, string? format)
// (ReadOnlySpan<char>)
// (ReadOnlySpan<char>, int alignment)
// (ReadOnlySpan<char>, string? format)
// (ReadOnlySpan<char>, int alignment, string? format)
// but this also has shortcomings:
// - Some expressions that would have worked with an object overload will now force a fallback to string.Format
// (or fail to compile if the handler is used in places where the fallback isn't provided), because the compiler
// can't always target type to T, e.g. `b switch { true => 1, false => null }` where `b` is a bool can successfully
// be passed as an argument of type `object` but not of type `T`.
// - Reference types get no benefit from going through the generic code paths, and actually incur some overheads
// from doing so.
// - Nullable value types also pay a heavy price, in particular around interface checks that would generally evaporate
// at compile time for value types but don't (currently) if the Nullable<T> goes through the same code paths
// (see https://github.com/dotnet/runtime/issues/50915).
//
// We could try to take a more elaborate approach for DefaultInterpolatedStringHandler, since it is the most common handler
// and we want to minimize overheads both at runtime and in IL size, e.g. have a complete set of overloads for each of:
// (T, ...) where T : struct
// (T?, ...) where T : struct
// (object, ...)
// (ReadOnlySpan<char>, ...)
// (string, ...)
// but this also has shortcomings, most importantly:
// - If you have an unconstrained T that happens to be a value type, it'll now end up getting boxed to use the object overload.
// This also necessitates the T? overload, since nullable value types don't meet a T : struct constraint, so without those
// they'd all map to the object overloads as well.
// - Any reference type with an implicit cast to ROS<char> will fail to compile due to ambiguities between the overloads. string
// is one such type, hence needing dedicated overloads for it that can be bound to more tightly.
//
// A middle ground we've settled on, which is likely to be the right approach for most other handlers as well, would be the set:
// (T, ...) with no constraint
// (ReadOnlySpan<char>) and (ReadOnlySpan<char>, int)
// (object, int alignment = 0, string? format = null)
// (string) and (string, int)
// This would address most of the concerns, at the expense of:
// - Most reference types going through the generic code paths and so being a bit more expensive.
// - Nullable types being more expensive until https://github.com/dotnet/runtime/issues/50915 is addressed.
// We could choose to add a T? where T : struct set of overloads if necessary.
// Strings don't require their own overloads here, but as they're expected to be very common and as we can
// optimize them in several ways (can copy the contents directly, don't need to do any interface checks, don't
// need to pay the shared generic overheads, etc.) we can add overloads specifically to optimize for them.
//
// Hole values are formatted according to the following policy:
// 1. If an IFormatProvider was supplied and it provides an ICustomFormatter, use ICustomFormatter.Format (even if the value is null).
// 2. If the type implements ISpanFormattable, use ISpanFormattable.TryFormat.
// 3. If the type implements IFormattable, use IFormattable.ToString.
// 4. Otherwise, use object.ToString.
// This matches the behavior of string.Format, StringBuilder.AppendFormat, etc. The only overloads for which this doesn't
// apply is ReadOnlySpan<char>, which isn't supported by either string.Format nor StringBuilder.AppendFormat, but more
// importantly which can't be boxed to be passed to ICustomFormatter.Format.
#region AppendFormatted T
/// <summary>Writes the specified value to the handler.</summary>
/// <param name="value">The value to write.</param>
public void AppendFormatted<T>(T value)
{
// This method could delegate to AppendFormatted with a null format, but explicitly passing
// default as the format to TryFormat helps to improve code quality in some cases when TryFormat is inlined,
// e.g. for Int32 it enables the JIT to eliminate code in the inlined method based on a length check on the format.
// If there's a custom formatter, always use it.
if (_hasCustomFormatter)
{
AppendCustomFormatter(value, format: null);
return;
}
// Check first for IFormattable, even though we'll prefer to use ISpanFormattable, as the latter
// requires the former. For value types, it won't matter as the type checks devolve into
// JIT-time constants. For reference types, they're more likely to implement IFormattable
// than they are to implement ISpanFormattable: if they don't implement either, we save an
// interface check over first checking for ISpanFormattable and then for IFormattable, and
// if it only implements IFormattable, we come out even: only if it implements both do we
// end up paying for an extra interface check.
string? s;
if (value is IFormattable)
{
// If the value can format itself directly into our buffer, do so.
if (value is ISpanFormattable)
{
int charsWritten;
while (!((ISpanFormattable)value).TryFormat(_chars[_pos..], out charsWritten, default, _provider)) // constrained call avoiding boxing for value types
{
Grow();
}
_pos += charsWritten;
return;
}
s = ((IFormattable)value).ToString(format: null, _provider); // constrained call avoiding boxing for value types
}
else
{
s = value?.ToString();
}
if (s is not null)
{
AppendStringDirect(s);
}
}
/// <summary>Writes the specified value to the handler.</summary>
/// <param name="value">The value to write.</param>
/// <param name="format">The format string.</param>
public void AppendFormatted<T>(T value, string? format)
{
// If there's a custom formatter, always use it.
if (_hasCustomFormatter)
{
AppendCustomFormatter(value, format);
return;
}
// Check first for IFormattable, even though we'll prefer to use ISpanFormattable, as the latter
// requires the former. For value types, it won't matter as the type checks devolve into
// JIT-time constants. For reference types, they're more likely to implement IFormattable
// than they are to implement ISpanFormattable: if they don't implement either, we save an
// interface check over first checking for ISpanFormattable and then for IFormattable, and
// if it only implements IFormattable, we come out even: only if it implements both do we
// end up paying for an extra interface check.
string? s;
if (value is IFormattable)
{
// If the value can format itself directly into our buffer, do so.
if (value is ISpanFormattable)
{
int charsWritten;
while (!((ISpanFormattable)value).TryFormat(_chars[_pos..], out charsWritten, format, _provider)) // constrained call avoiding boxing for value types
{
Grow();
}
_pos += charsWritten;
return;
}
s = ((IFormattable)value).ToString(format, _provider); // constrained call avoiding boxing for value types
}
else
{
s = value?.ToString();
}
if (s is not null)
{
AppendStringDirect(s);
}
}
/// <summary>Writes the specified value to the handler.</summary>
/// <param name="value">The value to write.</param>
/// <param name="alignment">Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value.</param>
public void AppendFormatted<T>(T value, int alignment)
{
int startingPos = _pos;
AppendFormatted(value);
if (alignment != 0)
{
AppendOrInsertAlignmentIfNeeded(startingPos, alignment);
}
}
/// <summary>Writes the specified value to the handler.</summary>
/// <param name="value">The value to write.</param>
/// <param name="format">The format string.</param>
/// <param name="alignment">Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value.</param>
public void AppendFormatted<T>(T value, int alignment, string? format)
{
int startingPos = _pos;
AppendFormatted(value, format);
if (alignment != 0)
{
AppendOrInsertAlignmentIfNeeded(startingPos, alignment);
}
}
#endregion
#region AppendFormatted ReadOnlySpan<char>
/// <summary>Writes the specified character span to the handler.</summary>
/// <param name="value">The span to write.</param>
public void AppendFormatted(ReadOnlySpan<char> value)
{
// Fast path for when the value fits in the current buffer
if (value.TryCopyTo(_chars[_pos..]))
{
_pos += value.Length;
}
else
{
GrowThenCopySpan(value);
}
}
/// <summary>Writes the specified string of chars to the handler.</summary>
/// <param name="value">The span to write.</param>
/// <param name="alignment">Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value.</param>
/// <param name="format">The format string.</param>
public void AppendFormatted(ReadOnlySpan<char> value, int alignment = 0, string? format = null)
{
bool leftAlign = false;
if (alignment < 0)
{
leftAlign = true;
alignment = -alignment;
}
int paddingRequired = alignment - value.Length;
if (paddingRequired <= 0)
{
// The value is as large or larger than the required amount of padding,
// so just write the value.
AppendFormatted(value);
return;
}
// Write the value along with the appropriate padding.
EnsureCapacityForAdditionalChars(value.Length + paddingRequired);
if (leftAlign)
{
value.CopyTo(_chars[_pos..]);
_pos += value.Length;
_chars.Slice(_pos, paddingRequired).Fill(' ');
_pos += paddingRequired;
}
else
{
_chars.Slice(_pos, paddingRequired).Fill(' ');
_pos += paddingRequired;
value.CopyTo(_chars[_pos..]);
_pos += value.Length;
}
}
#endregion
#region AppendFormatted string
/// <summary>Writes the specified value to the handler.</summary>
/// <param name="value">The value to write.</param>
public void AppendFormatted(string? value)
{
// Fast-path for no custom formatter and a non-null string that fits in the current destination buffer.
if (!_hasCustomFormatter && value?.TryCopyTo(_chars[_pos..]) == true)
{
_pos += value.Length;
}
else
{
AppendFormattedSlow(value);
}
}
/// <summary>Writes the specified value to the handler.</summary>
/// <param name="value">The value to write.</param>
/// <remarks>
/// Slow path to handle a custom formatter, potentially null value,
/// or a string that doesn't fit in the current buffer.
/// </remarks>
[MethodImpl(MethodImplOptions.NoInlining)]
private void AppendFormattedSlow(string? value)
{
if (_hasCustomFormatter)
{
AppendCustomFormatter(value, format: null);
}
else if (value is not null)
{
EnsureCapacityForAdditionalChars(value.Length);
value.CopyTo(_chars[_pos..]);
_pos += value.Length;
}
}
/// <summary>Writes the specified value to the handler.</summary>
/// <param name="value">The value to write.</param>
/// <param name="alignment">Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value.</param>
/// <param name="format">The format string.</param>
public void AppendFormatted(string? value, int alignment = 0, string? format = null) =>
// Format is meaningless for strings and doesn't make sense for someone to specify. We have the overload
// simply to disambiguate between ROS<char> and object, just in case someone does specify a format, as
// string is implicitly convertible to both. Just delegate to the T-based implementation.
AppendFormatted<string?>(value, alignment, format);
#endregion
#region AppendFormatted object
/// <summary>Writes the specified value to the handler.</summary>
/// <param name="value">The value to write.</param>
/// <param name="alignment">Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value.</param>
/// <param name="format">The format string.</param>
public void AppendFormatted(object? value, int alignment = 0, string? format = null) =>
// This overload is expected to be used rarely, only if either a) something strongly typed as object is
// formatted with both an alignment and a format, or b) the compiler is unable to target type to T. It
// exists purely to help make cases from (b) compile. Just delegate to the T-based implementation.
AppendFormatted<object?>(value, alignment, format);
#endregion
#endregion
/// <summary>Gets whether the provider provides a custom formatter.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] // only used in a few hot path call sites
internal static bool HasCustomFormatter(IFormatProvider provider)
{
Debug.Assert(provider is not null);
Debug.Assert(provider is not CultureInfo || provider.GetFormat(typeof(ICustomFormatter)) is null, "Expected CultureInfo to not provide a custom formatter");
return
provider.GetType() != typeof(CultureInfo) && // optimization to avoid GetFormat in the majority case
provider.GetFormat(typeof(ICustomFormatter)) != null;
}
/// <summary>Formats the value using the custom formatter from the provider.</summary>
/// <param name="value">The value to write.</param>
/// <param name="format">The format string.</param>
[MethodImpl(MethodImplOptions.NoInlining)]
private void AppendCustomFormatter<T>(T value, string? format)
{
// This case is very rare, but we need to handle it prior to the other checks in case
// a provider was used that supplied an ICustomFormatter which wanted to intercept the particular value.
// We do the cast here rather than in the ctor, even though this could be executed multiple times per
// formatting, to make the cast pay for play.
Debug.Assert(_hasCustomFormatter);
Debug.Assert(_provider != null);
ICustomFormatter? formatter = (ICustomFormatter?)_provider.GetFormat(typeof(ICustomFormatter));
Debug.Assert(formatter != null, "An incorrectly written provider said it implemented ICustomFormatter, and then didn't");
if (formatter?.Format(format, value, _provider) is string customFormatted)
{
AppendStringDirect(customFormatted);
}
}
/// <summary>Handles adding any padding required for aligning a formatted value in an interpolation expression.</summary>
/// <param name="startingPos">The position at which the written value started.</param>
/// <param name="alignment">Non-zero minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value.</param>
private void AppendOrInsertAlignmentIfNeeded(int startingPos, int alignment)
{
Debug.Assert(startingPos >= 0 && startingPos <= _pos);
Debug.Assert(alignment != 0);
int charsWritten = _pos - startingPos;
bool leftAlign = false;
if (alignment < 0)
{
leftAlign = true;
alignment = -alignment;
}
int paddingNeeded = alignment - charsWritten;
if (paddingNeeded > 0)
{
EnsureCapacityForAdditionalChars(paddingNeeded);
if (leftAlign)
{
_chars.Slice(_pos, paddingNeeded).Fill(' ');
}
else
{
_chars.Slice(startingPos, charsWritten).CopyTo(_chars[(startingPos + paddingNeeded)..]);
_chars.Slice(startingPos, paddingNeeded).Fill(' ');
}
_pos += paddingNeeded;
}
}
/// <summary>Ensures <see cref="_chars"/> has the capacity to store <paramref name="additionalChars"/> beyond <see cref="_pos"/>.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EnsureCapacityForAdditionalChars(int additionalChars)
{
if (_chars.Length - _pos < additionalChars)
{
Grow(additionalChars);
}
}
/// <summary>Fallback for fast path in <see cref="AppendStringDirect"/> when there's not enough space in the destination.</summary>
/// <param name="value">The string to write.</param>
[MethodImpl(MethodImplOptions.NoInlining)]
private void GrowThenCopyString(string value)
{
Grow(value.Length);
value.CopyTo(_chars[_pos..]);
_pos += value.Length;
}
/// <summary>Fallback for <see cref="AppendFormatted(ReadOnlySpan{char})"/> for when not enough space exists in the current buffer.</summary>
/// <param name="value">The span to write.</param>
[MethodImpl(MethodImplOptions.NoInlining)]
private void GrowThenCopySpan(ReadOnlySpan<char> value)
{
Grow(value.Length);
value.CopyTo(_chars[_pos..]);
_pos += value.Length;
}
/// <summary>Grows <see cref="_chars"/> to have the capacity to store at least <paramref name="additionalChars"/> beyond <see cref="_pos"/>.</summary>
[MethodImpl(MethodImplOptions.NoInlining)] // keep consumers as streamlined as possible
private void Grow(int additionalChars)
{
// This method is called when the remaining space (_chars.Length - _pos) is
// insufficient to store a specific number of additional characters. Thus, we
// need to grow to at least that new total. GrowCore will handle growing by more
// than that if possible.
Debug.Assert(additionalChars > _chars.Length - _pos);
GrowCore((uint)_pos + (uint)additionalChars);
}
/// <summary>Grows the size of <see cref="_chars"/>.</summary>
[MethodImpl(MethodImplOptions.NoInlining)] // keep consumers as streamlined as possible
private void Grow()
{
// This method is called when the remaining space in _chars isn't sufficient to continue
// the operation. Thus, we need at least one character beyond _chars.Length. GrowCore
// will handle growing by more than that if possible.
GrowCore((uint)_chars.Length + 1);
}
/// <summary>Grow the size of <see cref="_chars"/> to at least the specified <paramref name="requiredMinCapacity"/>.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] // but reuse this grow logic directly in both of the above grow routines
private void GrowCore(uint requiredMinCapacity)
{
// We want the max of how much space we actually required and doubling our capacity (without going beyond the max allowed length). We
// also want to avoid asking for small arrays, to reduce the number of times we need to grow, and since we're working with unsigned
// ints that could technically overflow if someone tried to, for example, append a huge string to a huge string, we also clamp to int.MaxValue.
// Even if the array creation fails in such a case, we may later fail in ToStringAndClear.
uint newCapacity = Math.Max(requiredMinCapacity, Math.Min((uint)_chars.Length * 2, 0x3FFFFFDF));
int arraySize = (int)Math.Clamp(newCapacity, MinimumArrayPoolLength, int.MaxValue);
char[] newArray = STArrayPool<char>.Shared.Rent(arraySize);
_chars[.._pos].CopyTo(newArray);
char[]? toReturn = _arrayToReturnToPool;
_chars = _arrayToReturnToPool = newArray;
if (toReturn is not null)
{
STArrayPool<char>.Shared.Return(toReturn);
}
}
}

View file

@ -0,0 +1,337 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Buffers;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading;
namespace Server.Buffers;
/**
* Adaptation of the ArrayPool<T>.Shared (TlsOverPerCoreLockedStacksArrayPool) for single threaded *unsafe* usage.
*/
public class STArrayPool<T> : ArrayPool<T>
{
private const int StackArraySize = 8;
private const int BucketCount = 27; // SelectBucketIndex(1024 * 1024 * 1024 + 1)
private static readonly STArrayPool<T> _shared = new();
public static STArrayPool<T> Shared => _shared;
private int _trimCallbackCreated;
private static STArray[] _cacheBuckets;
private STArrayStack[] _buckets = new STArrayStack[BucketCount];
private STArrayPool() {}
public override T[] Rent(int minimumLength)
{
T[] buffer;
var bucketIndex = SelectBucketIndex(minimumLength);
var cachedBuckets = _cacheBuckets;
if (cachedBuckets is not null && (uint)bucketIndex < (uint)cachedBuckets.Length)
{
buffer = cachedBuckets[bucketIndex].Array;
if (buffer is not null)
{
cachedBuckets[bucketIndex].Array = null;
return buffer;
}
}
var buckets = _buckets;
if ((uint)bucketIndex < (uint)buckets.Length)
{
var b = buckets[bucketIndex];
if (b is not null)
{
buffer = b.TryPop();
if (buffer is not null)
{
return buffer;
}
}
minimumLength = GetMaxSizeForBucket(bucketIndex);
}
if (minimumLength == 0)
{
// We aren't renting.
return Array.Empty<T>();
}
if (minimumLength < 0)
{
throw new ArgumentOutOfRangeException(nameof(minimumLength));
}
buffer = GC.AllocateUninitializedArray<T>(minimumLength);
return buffer;
}
public override void Return(T[] array, bool clearArray = false)
{
if (array is null)
{
return;
}
var bucketIndex = SelectBucketIndex(array.Length);
var cacheBuckets = _cacheBuckets ?? InitializeBuckets();
if ((uint)bucketIndex < (uint)_cacheBuckets!.Length)
{
if (clearArray)
{
Array.Clear(array);
}
if (array.Length != GetMaxSizeForBucket(bucketIndex))
{
throw new ArgumentException("Buffer is not from the pool", nameof(array));
}
ref var bucketArray = ref cacheBuckets[bucketIndex];
var prev = bucketArray.Array;
bucketArray = new STArray(array);
if (prev is not null)
{
var bucket = _buckets[bucketIndex] ?? CreateBucketStack(bucketIndex);
bucket.TryPush(prev);
}
}
}
public void ResetForTesting()
{
if (Core.IsRunningFromXUnit)
{
_cacheBuckets = null;
_buckets = new STArrayStack[BucketCount];
}
}
public bool Trim()
{
var ticks = Core.TickCount;
var pressure = GetMemoryPressure();
var buckets = _buckets;
for (var i = 0; i < buckets.Length; i++)
{
buckets[i]?.Trim(ticks, pressure, GetMaxSizeForBucket(i));
}
if (_cacheBuckets == null)
{
return true;
}
// Under high pressure, release all cached buckets
if (pressure == MemoryPressure.High)
{
Array.Clear(_cacheBuckets);
}
else
{
uint threshold = pressure switch
{
MemoryPressure.Medium => 10000,
_ => 30000,
};
var cacheBuckets = _cacheBuckets;
for (var i = 0; i < cacheBuckets.Length; i++)
{
ref var b = ref cacheBuckets[i];
if (b.Array is null)
{
continue;
}
var lastSeen = b.Ticks;
if (lastSeen == 0)
{
b.Ticks = ticks;
}
else if (ticks - lastSeen >= threshold)
{
b.Array = null;
}
}
}
return true;
}
private STArrayStack CreateBucketStack(int bucketIndex)
{
return _buckets[bucketIndex] = new STArrayStack();
}
private STArray[] InitializeBuckets()
{
Debug.Assert(_cacheBuckets is null, $"Non-null {nameof(_cacheBuckets)}");
var buckets = new STArray[BucketCount];
if (Interlocked.Exchange(ref _trimCallbackCreated, 1) == 0)
{
Gen2GcCallback.Register(o => ((STArrayPool<T>)o).Trim(), this);
}
return _cacheBuckets = buckets;
}
// Buffers are bucketed so that a request between 2^(n-1) + 1 and 2^n is given a buffer of 2^n
// Bucket index is log2(bufferSize - 1) with the exception that buffers between 1 and 16 bytes
// are combined, and the index is slid down by 3 to compensate.
// Zero is a valid bufferSize, and it is assigned the highest bucket index so that zero-length
// buffers are not retained by the pool. The pool will return the Array.Empty singleton for these.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static int SelectBucketIndex(int bufferSize) => BitOperations.Log2((uint)bufferSize - 1 | 15) - 3;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static int GetMaxSizeForBucket(int binIndex)
{
int maxSize = 16 << binIndex;
Debug.Assert(maxSize >= 0);
return maxSize;
}
internal enum MemoryPressure
{
Low,
Medium,
High
}
internal static MemoryPressure GetMemoryPressure()
{
GCMemoryInfo memoryInfo = GC.GetGCMemoryInfo();
if (memoryInfo.MemoryLoadBytes >= memoryInfo.HighMemoryLoadThresholdBytes * 0.90)
{
return MemoryPressure.High;
}
if (memoryInfo.MemoryLoadBytes >= memoryInfo.HighMemoryLoadThresholdBytes * 0.70)
{
return MemoryPressure.Medium;
}
return MemoryPressure.Low;
}
private sealed class STArrayStack
{
// Maximum buffers we will store in our stack
private readonly T[][] _arrays = new T[StackArraySize * Environment.ProcessorCount][];
private int _count;
private long _ticks;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryPush(T[] array)
{
var arrays = _arrays;
var count = _count;
if ((uint)count < (uint)_arrays.Length)
{
arrays[count] = array;
_count = count + 1;
return true;
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T[] TryPop()
{
var arrays = _arrays;
var count = _count - 1;
if ((uint)count < (uint)arrays.Length)
{
var arr = arrays[count];
arrays[count] = null;
_count = count;
return arr;
}
return null;
}
public void Trim(long now, MemoryPressure pressure, int bucketSize)
{
if (_count == 0)
{
return;
}
// 10 seconds under high pressure, otherwise 60 seconds
var threshold = pressure == MemoryPressure.High ? 10000 : 60000;
if (_ticks == 0)
{
_ticks = now;
return;
}
if (now - _ticks <= threshold)
{
return;
}
int trimCount = 1;
switch (pressure)
{
case MemoryPressure.Medium:
{
trimCount = 2;
break;
}
case MemoryPressure.High:
{
if (bucketSize > 16384)
{
trimCount++;
}
var size = Unsafe.SizeOf<T>();
if (size > 32)
{
trimCount += 2;
}
else if (size > 16)
{
trimCount++;
}
break;
}
}
while (_count > 0 && trimCount-- > 0)
{
_arrays[--_count] = null;
}
}
}
private struct STArray
{
public T[] Array;
public long Ticks;
public STArray(T[] array)
{
Array = array;
Ticks = 0;
}
}
}

View file

@ -166,7 +166,7 @@ namespace System.Buffers
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadString(Encoding encoding, bool safeString = false, int fixedLength = -1)
{
int sizeT = TextEncoding.GetByteLengthForEncoding(encoding);
int byteLength = encoding.GetByteLengthForEncoding();
bool isFixedLength = fixedLength > -1;
@ -174,7 +174,7 @@ namespace System.Buffers
int size;
if (isFixedLength)
{
size = fixedLength * sizeT;
size = fixedLength * byteLength;
if (size > Remaining)
{
throw new OutOfMemoryException();
@ -183,8 +183,8 @@ namespace System.Buffers
else
{
// In case the remaining is not evenly divisible
size = remaining - (remaining & (sizeT - 1));
int index = _buffer.Slice(Position, size).IndexOfTerminator(sizeT);
size = remaining - (remaining & (byteLength - 1));
int index = _buffer.Slice(Position, size).IndexOfTerminator(byteLength);
size = index < 0 ? size : index;
}

View file

@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2020 - ModernUO Development Team *
* Copyright 2019-2022 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SpanWriter.cs *
* *
@ -14,7 +14,6 @@
*************************************************************************/
using System.Buffers.Binary;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
@ -22,40 +21,41 @@ using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Toolkit.HighPerformance;
using Server;
using Server.Buffers;
using Server.Text;
namespace System.Buffers
namespace System.Buffers;
public ref struct SpanWriter
{
public ref struct SpanWriter
private readonly bool _resize;
private byte[] _arrayToReturnToPool;
private Span<byte> _buffer;
private int _position;
public int BytesWritten { get; private set; }
public int Position
{
private readonly bool _resize;
private byte[] _arrayToReturnToPool;
private Span<byte> _buffer;
private int _position;
public int BytesWritten { get; private set; }
public int Position
get => _position;
private set
{
get => _position;
private set
{
_position = value;
_position = value;
if (value > BytesWritten)
{
BytesWritten = value;
}
if (value > BytesWritten)
{
BytesWritten = value;
}
}
}
public int Capacity => _buffer.Length;
public int Capacity => _buffer.Length;
public ReadOnlySpan<byte> Span => _buffer[..Position];
public ReadOnlySpan<byte> Span => _buffer[..Position];
public Span<byte> RawBuffer => _buffer;
public Span<byte> RawBuffer => _buffer;
/**
/**
* Converts the writer to a Span<byte> using a SpanOwner.
* If the buffer was stackalloc, it will be copied to a rented buffer.
* Otherwise the existing rented buffer is used.
@ -64,395 +64,425 @@ namespace System.Buffers
* Do not use the SpanWriter after calling this method.
* This method will effectively dispose of the SpanWriter and is therefore considered terminal.
*/
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SpanOwner ToSpan()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SpanOwner ToSpan()
{
var toReturn = _arrayToReturnToPool;
SpanOwner apo;
if (_position == 0)
{
var toReturn = _arrayToReturnToPool;
SpanOwner apo;
if (_position == 0)
{
apo = new SpanOwner(_position, Array.Empty<byte>());
if (toReturn != null)
{
ArrayPool<byte>.Shared.Return(toReturn);
}
}
else if (toReturn != null)
{
apo = new SpanOwner(_position, toReturn);
}
else
{
var buffer = ArrayPool<byte>.Shared.Rent(_position);
_buffer.CopyTo(buffer);
apo = new SpanOwner(_position, buffer);
}
this = default; // Don't allow two references to the same buffer
return apo;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SpanWriter(Span<byte> initialBuffer, bool resize = false)
{
_resize = resize;
_buffer = initialBuffer;
_position = 0;
BytesWritten = 0;
_arrayToReturnToPool = null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SpanWriter(int initialCapacity, bool resize = false)
{
_resize = resize;
_arrayToReturnToPool = ArrayPool<byte>.Shared.Rent(initialCapacity);
_buffer = _arrayToReturnToPool;
_position = 0;
BytesWritten = 0;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void Grow(int additionalCapacity)
{
var newSize = Math.Max(BytesWritten + additionalCapacity, _buffer.Length * 2);
byte[] poolArray = ArrayPool<byte>.Shared.Rent(newSize);
_buffer[..BytesWritten].CopyTo(poolArray);
byte[] toReturn = _arrayToReturnToPool;
_buffer = _arrayToReturnToPool = poolArray;
apo = new SpanOwner(_position, Array.Empty<byte>());
if (toReturn != null)
{
ArrayPool<byte>.Shared.Return(toReturn);
STArrayPool<byte>.Shared.Return(toReturn);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void GrowIfNeeded(int count)
else if (toReturn != null)
{
if (_position + count > _buffer.Length)
apo = new SpanOwner(_position, toReturn);
}
else
{
var buffer = STArrayPool<byte>.Shared.Rent(_position);
_buffer.CopyTo(buffer);
apo = new SpanOwner(_position, buffer);
}
this = default; // Don't allow two references to the same buffer
return apo;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SpanWriter(Span<byte> initialBuffer, bool resize = false)
{
_resize = resize;
_buffer = initialBuffer;
_position = 0;
BytesWritten = 0;
_arrayToReturnToPool = null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SpanWriter(int initialCapacity, bool resize = false)
{
_resize = resize;
_arrayToReturnToPool = STArrayPool<byte>.Shared.Rent(initialCapacity);
_buffer = _arrayToReturnToPool;
_position = 0;
BytesWritten = 0;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void Grow(int additionalCapacity)
{
var newSize = Math.Max(BytesWritten + additionalCapacity, _buffer.Length * 2);
byte[] poolArray = STArrayPool<byte>.Shared.Rent(newSize);
_buffer[..BytesWritten].CopyTo(poolArray);
byte[] toReturn = _arrayToReturnToPool;
_buffer = _arrayToReturnToPool = poolArray;
if (toReturn != null)
{
STArrayPool<byte>.Shared.Return(toReturn);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void GrowIfNeeded(int count)
{
if (_position + count > _buffer.Length)
{
if (!_resize)
{
if (!_resize)
{
throw new OutOfMemoryException();
}
Grow(count);
}
}
public ref byte GetPinnableReference() => ref MemoryMarshal.GetReference(_buffer);
public void EnsureCapacity(int capacity)
{
if (capacity > _buffer.Length)
{
if (!_resize)
{
throw new OutOfMemoryException();
}
Grow(capacity - BytesWritten);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void Write(bool value)
{
GrowIfNeeded(1);
_buffer[Position++] = *(byte*)&value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(byte value)
{
GrowIfNeeded(1);
_buffer[Position++] = value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(sbyte value)
{
GrowIfNeeded(1);
_buffer[Position++] = (byte)value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(short value)
{
GrowIfNeeded(2);
BinaryPrimitives.WriteInt16BigEndian(_buffer[_position..], value);
Position += 2;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLE(short value)
{
GrowIfNeeded(2);
BinaryPrimitives.WriteInt16LittleEndian(_buffer[_position..], value);
Position += 2;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ushort value)
{
GrowIfNeeded(2);
BinaryPrimitives.WriteUInt16BigEndian(_buffer[_position..], value);
Position += 2;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLE(ushort value)
{
GrowIfNeeded(2);
BinaryPrimitives.WriteUInt16LittleEndian(_buffer[_position..], value);
Position += 2;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(int value)
{
GrowIfNeeded(4);
BinaryPrimitives.WriteInt32BigEndian(_buffer[_position..], value);
Position += 4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLE(int value)
{
GrowIfNeeded(4);
BinaryPrimitives.WriteInt32LittleEndian(_buffer[_position..], value);
Position += 4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(uint value)
{
GrowIfNeeded(4);
BinaryPrimitives.WriteUInt32BigEndian(_buffer[_position..], value);
Position += 4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(Serial serial) => Write(serial.Value);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLE(uint value)
{
GrowIfNeeded(4);
BinaryPrimitives.WriteUInt32LittleEndian(_buffer[_position..], value);
Position += 4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(long value)
{
GrowIfNeeded(8);
BinaryPrimitives.WriteInt64BigEndian(_buffer[_position..], value);
Position += 8;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ulong value)
{
GrowIfNeeded(8);
BinaryPrimitives.WriteUInt64BigEndian(_buffer[_position..], value);
Position += 8;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ReadOnlySpan<byte> buffer)
{
var count = buffer.Length;
GrowIfNeeded(count);
buffer.CopyTo(_buffer[_position..]);
Position += count;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteAscii(char chr) => Write((byte)chr);
public void WriteString<T>(string value, Encoding encoding, int fixedLength = -1) where T : struct, IEquatable<T>
{
int sizeT = Unsafe.SizeOf<T>();
if (sizeT > 2)
{
throw new InvalidConstraintException("WriteString only accepts byte, sbyte, char, short, and ushort as a constraint");
throw new OutOfMemoryException();
}
value ??= string.Empty;
Grow(count);
}
}
var charLength = Math.Min(fixedLength > -1 ? fixedLength : value.Length, value.Length);
var src = value.AsSpan(0, charLength);
public ref byte GetPinnableReference() => ref MemoryMarshal.GetReference(_buffer);
var byteCount = fixedLength > -1 ? fixedLength * sizeT : encoding.GetByteCount(value);
if (byteCount == 0)
public void EnsureCapacity(int capacity)
{
if (capacity > _buffer.Length)
{
if (!_resize)
{
return;
throw new OutOfMemoryException();
}
GrowIfNeeded(byteCount);
Grow(capacity - BytesWritten);
}
}
var bytesWritten = encoding.GetBytes(src, _buffer[_position..]);
Position += bytesWritten;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void Write(bool value)
{
GrowIfNeeded(1);
_buffer[Position++] = *(byte*)&value;
}
if (fixedLength > -1)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(byte value)
{
GrowIfNeeded(1);
_buffer[Position++] = value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(sbyte value)
{
GrowIfNeeded(1);
_buffer[Position++] = (byte)value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(short value)
{
GrowIfNeeded(2);
BinaryPrimitives.WriteInt16BigEndian(_buffer[_position..], value);
Position += 2;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLE(short value)
{
GrowIfNeeded(2);
BinaryPrimitives.WriteInt16LittleEndian(_buffer[_position..], value);
Position += 2;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ushort value)
{
GrowIfNeeded(2);
BinaryPrimitives.WriteUInt16BigEndian(_buffer[_position..], value);
Position += 2;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLE(ushort value)
{
GrowIfNeeded(2);
BinaryPrimitives.WriteUInt16LittleEndian(_buffer[_position..], value);
Position += 2;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(int value)
{
GrowIfNeeded(4);
BinaryPrimitives.WriteInt32BigEndian(_buffer[_position..], value);
Position += 4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLE(int value)
{
GrowIfNeeded(4);
BinaryPrimitives.WriteInt32LittleEndian(_buffer[_position..], value);
Position += 4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(uint value)
{
GrowIfNeeded(4);
BinaryPrimitives.WriteUInt32BigEndian(_buffer[_position..], value);
Position += 4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(Serial serial) => Write(serial.Value);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLE(uint value)
{
GrowIfNeeded(4);
BinaryPrimitives.WriteUInt32LittleEndian(_buffer[_position..], value);
Position += 4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(long value)
{
GrowIfNeeded(8);
BinaryPrimitives.WriteInt64BigEndian(_buffer[_position..], value);
Position += 8;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ulong value)
{
GrowIfNeeded(8);
BinaryPrimitives.WriteUInt64BigEndian(_buffer[_position..], value);
Position += 8;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ReadOnlySpan<byte> buffer)
{
var count = buffer.Length;
GrowIfNeeded(count);
buffer.CopyTo(_buffer[_position..]);
Position += count;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteAscii(char chr) => Write((byte)chr);
public void WriteAscii(
ref RawInterpolatedStringHandler handler)
{
Write(handler.Text, Encoding.ASCII);
handler.Clear();
}
public void WriteAscii(
IFormatProvider? formatProvider,
[InterpolatedStringHandlerArgument("formatProvider")]
ref RawInterpolatedStringHandler handler)
{
Write(handler.Text, Encoding.ASCII);
handler.Clear();
}
public void Write(
Encoding encoding,
ref RawInterpolatedStringHandler handler)
{
Write(handler.Text, encoding);
handler.Clear();
}
public void Write(
Encoding encoding,
IFormatProvider? formatProvider,
[InterpolatedStringHandlerArgument("formatProvider")]
ref RawInterpolatedStringHandler handler)
{
Write(handler.Text, encoding);
handler.Clear();
}
public void Write(ReadOnlySpan<char> value, Encoding encoding, int fixedLength = -1)
{
var charLength = Math.Min(fixedLength > -1 ? fixedLength : value.Length, value.Length);
var src = value[..charLength];
var byteLength = encoding.GetByteLengthForEncoding();
var byteCount = encoding.GetByteCount(src);
if (fixedLength > src.Length)
{
byteCount += (fixedLength - src.Length) * byteLength;
}
if (byteCount == 0)
{
return;
}
GrowIfNeeded(byteCount);
var bytesWritten = encoding.GetBytes(src, _buffer[_position..]);
Position += bytesWritten;
if (fixedLength > -1)
{
var extra = fixedLength * byteLength - bytesWritten;
if (extra > 0)
{
var extra = fixedLength * sizeT - bytesWritten;
if (extra > 0)
{
Clear(extra);
}
Clear(extra);
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLittleUni(string value) => WriteString<char>(value, TextEncoding.UnicodeLE);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLittleUni(string value) => Write(value, TextEncoding.UnicodeLE);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLittleUniNull(string value)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLittleUniNull(string value)
{
Write(value, TextEncoding.UnicodeLE);
Write((ushort)0);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLittleUni(string value, int fixedLength) => Write(value, TextEncoding.UnicodeLE, fixedLength);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteBigUni(string value) => Write(value, TextEncoding.Unicode);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteBigUniNull(string value)
{
Write(value, TextEncoding.Unicode);
Write((ushort)0); // '\0'
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteBigUni(string value, int fixedLength) => Write(value, TextEncoding.Unicode, fixedLength);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteUTF8(string value) => Write(value, TextEncoding.UTF8);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteUTF8Null(string value)
{
Write(value, TextEncoding.UTF8);
Write((byte)0); // '\0'
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteAscii(string value) => Write(value, Encoding.ASCII);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteAsciiNull(string value)
{
Write(value, Encoding.ASCII);
Write((byte)0); // '\0'
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteAscii(string value, int fixedLength) => Write(value, Encoding.ASCII, fixedLength);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear(int count)
{
GrowIfNeeded(count);
_buffer.Slice(_position, count).Clear();
Position += count;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int Seek(int offset, SeekOrigin origin)
{
Debug.Assert(
origin != SeekOrigin.End || _resize || offset <= 0,
"Attempting to seek to a position beyond capacity using SeekOrigin.End without resize"
);
Debug.Assert(
origin != SeekOrigin.End || offset >= -_buffer.Length,
"Attempting to seek to a negative position using SeekOrigin.End"
);
Debug.Assert(
origin != SeekOrigin.Begin || offset >= 0,
"Attempting to seek to a negative position using SeekOrigin.Begin"
);
Debug.Assert(
origin != SeekOrigin.Begin || _resize || offset <= _buffer.Length,
"Attempting to seek to a position beyond the capacity using SeekOrigin.Begin without resize"
);
Debug.Assert(
origin != SeekOrigin.Current || _position + offset >= 0,
"Attempting to seek to a negative position using SeekOrigin.Current"
);
Debug.Assert(
origin != SeekOrigin.Current || _resize || _position + offset <= _buffer.Length,
"Attempting to seek to a position beyond the capacity using SeekOrigin.Current without resize"
);
var newPosition = Math.Max(0, origin switch
{
WriteString<char>(value, TextEncoding.UnicodeLE);
Write((ushort)0);
SeekOrigin.Current => _position + offset,
SeekOrigin.End => BytesWritten + offset,
_ => offset // Begin
});
if (newPosition >= _buffer.Length)
{
Grow(newPosition - _buffer.Length + 1);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLittleUni(string value, int fixedLength) => WriteString<char>(value, TextEncoding.UnicodeLE, fixedLength);
return Position = newPosition;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteBigUni(string value) => WriteString<char>(value, TextEncoding.Unicode);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteBigUniNull(string value)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
byte[] toReturn = _arrayToReturnToPool;
this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again
if (toReturn != null)
{
WriteString<char>(value, TextEncoding.Unicode);
Write((ushort)0); // '\0'
STArrayPool<byte>.Shared.Return(toReturn);
}
}
public struct SpanOwner : IDisposable
{
private readonly int _length;
private readonly byte[] _arrayToReturnToPool;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal SpanOwner(int length, byte[] buffer)
{
_length = length;
_arrayToReturnToPool = buffer;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteBigUni(string value, int fixedLength) => WriteString<char>(value, TextEncoding.Unicode, fixedLength);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteUTF8(string value) => WriteString<byte>(value, TextEncoding.UTF8);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteUTF8Null(string value)
public Span<byte> Span
{
WriteString<byte>(value, TextEncoding.UTF8);
Write((byte)0); // '\0'
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteAscii(string value) => WriteString<byte>(value, Encoding.ASCII);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteAsciiNull(string value)
{
WriteString<byte>(value, Encoding.ASCII);
Write((byte)0); // '\0'
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteAscii(string value, int fixedLength) => WriteString<byte>(value, Encoding.ASCII, fixedLength);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear(int count)
{
GrowIfNeeded(count);
_buffer.Slice(_position, count).Clear();
Position += count;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int Seek(int offset, SeekOrigin origin)
{
Debug.Assert(
origin != SeekOrigin.End || _resize || offset <= 0,
"Attempting to seek to a position beyond capacity using SeekOrigin.End without resize"
);
Debug.Assert(
origin != SeekOrigin.End || offset >= -_buffer.Length,
"Attempting to seek to a negative position using SeekOrigin.End"
);
Debug.Assert(
origin != SeekOrigin.Begin || offset >= 0,
"Attempting to seek to a negative position using SeekOrigin.Begin"
);
Debug.Assert(
origin != SeekOrigin.Begin || _resize || offset <= _buffer.Length,
"Attempting to seek to a position beyond the capacity using SeekOrigin.Begin without resize"
);
Debug.Assert(
origin != SeekOrigin.Current || _position + offset >= 0,
"Attempting to seek to a negative position using SeekOrigin.Current"
);
Debug.Assert(
origin != SeekOrigin.Current || _resize || _position + offset <= _buffer.Length,
"Attempting to seek to a position beyond the capacity using SeekOrigin.Current without resize"
);
var newPosition = Math.Max(0, origin switch
{
SeekOrigin.Current => _position + offset,
SeekOrigin.End => BytesWritten + offset,
_ => offset // Begin
});
if (newPosition >= _buffer.Length)
{
Grow(newPosition - _buffer.Length + 1);
}
return Position = newPosition;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => MemoryMarshal.CreateSpan(ref _arrayToReturnToPool.DangerousGetReference(), _length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
byte[] toReturn = _arrayToReturnToPool;
this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again
if (toReturn != null)
this = default;
if (_length > 0)
{
ArrayPool<byte>.Shared.Return(toReturn);
}
}
public struct SpanOwner : IDisposable
{
private readonly int _length;
private readonly byte[] _arrayToReturnToPool;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal SpanOwner(int length, byte[] buffer)
{
_length = length;
_arrayToReturnToPool = buffer;
}
public Span<byte> Span
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => MemoryMarshal.CreateSpan(ref _arrayToReturnToPool.DangerousGetReference(), _length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
byte[] toReturn = _arrayToReturnToPool;
this = default;
if (_length > 0)
{
ArrayPool<byte>.Shared.Return(toReturn);
}
STArrayPool<byte>.Shared.Return(toReturn);
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,8 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Copyright 2019-2022 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: EmbeddedSerializableAttribute.cs *
* File: ValueStringBuilderExtensions.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 *
@ -13,20 +13,19 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Runtime.CompilerServices;
namespace Server
namespace Server.Buffers;
public static class ValueStringBuilderExtensions
{
[AttributeUsage(AttributeTargets.Class)]
public sealed class EmbeddedSerializableAttribute : Attribute
// Compiler generated
public static void Append(
this ref ValueStringBuilder stringBuilder,
[InterpolatedStringHandlerArgument("stringBuilder")]
ref ValueStringBuilder.AppendInterpolatedStringHandler handler)
{
public int Version { get; }
public bool EncodedVersion { get; }
public EmbeddedSerializableAttribute(int version, bool encodedVersion = true)
{
Version = version;
EncodedVersion = encodedVersion;
}
// Reassign since the string builder stored on the interpolated handler is by-value
stringBuilder = handler._stringBuilder;
}
}

View file

@ -0,0 +1,262 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2022 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: ClientVersion.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.Collections.Generic;
using System.Runtime.CompilerServices;
using Server.Buffers;
namespace Server;
public enum ClientType
{
Regular,
UOTD,
God,
SA
}
public class ClientVersion : IComparable<ClientVersion>, IComparer<ClientVersion>
{
public static readonly ClientVersion Version400a = new("4.0.0a");
public static readonly ClientVersion Version407a = new("4.0.7a");
public static readonly ClientVersion Version500a = new("5.0.0a");
public static readonly ClientVersion Version502b = new("5.0.2b");
public static readonly ClientVersion Version6000 = new("6.0.0.0");
public static readonly ClientVersion Version6017 = new("6.0.1.7");
public static readonly ClientVersion Version60142 = new("6.0.14.2");
public static readonly ClientVersion Version7000 = new("7.0.0.0");
public static readonly ClientVersion Version7090 = new("7.0.9.0");
public static readonly ClientVersion Version70120 = new("7.0.12.0"); // Plant localization change
public static readonly ClientVersion Version70130 = new("7.0.13.0");
public static readonly ClientVersion Version70160 = new("7.0.16.0");
public static readonly ClientVersion Version70300 = new("7.0.30.0");
public static readonly ClientVersion Version70331 = new("7.0.33.1");
public static readonly ClientVersion Version704565 = new("7.0.45.65");
public static readonly ClientVersion Version70500 = new("7.0.50.0");
public static readonly ClientVersion Version70610 = new("7.0.61.0");
public ClientVersion(int maj, int min, int rev, int pat, ClientType type = ClientType.Regular)
{
Major = maj;
Minor = min;
Revision = rev;
Patch = pat;
Type = type;
SourceString = Utility.Intern(ToStringImpl());
}
public ClientVersion(string fmt)
{
fmt = fmt.ToLower();
SourceString = Utility.Intern(fmt);
try
{
var br1 = fmt.IndexOfOrdinal('.');
var br2 = fmt.IndexOf('.', br1 + 1);
var br3 = br2 + 1;
while (br3 < fmt.Length && char.IsDigit(fmt, br3))
{
br3++;
}
Major = Utility.ToInt32(fmt.AsSpan()[..br1]);
Minor = Utility.ToInt32(fmt.AsSpan(br1 + 1, br2 - br1 - 1));
Revision = Utility.ToInt32(fmt.AsSpan(br2 + 1, br3 - br2 - 1));
if (br3 < fmt.Length)
{
if (Major <= 5 && Minor <= 0 && Revision <= 6) // Anything before 5.0.7
{
if (!char.IsWhiteSpace(fmt, br3))
{
Patch = fmt[br3] - 'a' + 1;
}
}
else
{
Patch = Utility.ToInt32(fmt.AsSpan(br3 + 1, fmt.Length - br3 - 1));
}
}
if (fmt.InsensitiveContains("god") || fmt.InsensitiveContains("gq"))
{
Type = ClientType.God;
}
else if (fmt.InsensitiveContains("third dawn") ||
fmt.InsensitiveContains("uo:td") ||
fmt.InsensitiveContains("uotd") ||
fmt.InsensitiveContains("uo3d") ||
fmt.InsensitiveContains("uo:3d"))
{
Type = ClientType.UOTD;
}
else
{
Type = ClientType.Regular;
}
}
catch
{
Major = 0;
Minor = 0;
Revision = 0;
Patch = 0;
Type = ClientType.Regular;
}
}
public int Major { get; }
public int Minor { get; }
public int Revision { get; }
public int Patch { get; }
public ClientType Type { get; }
public string SourceString { get; }
public int CompareTo(ClientVersion o)
{
if (o == null)
{
return 1;
}
if (Major > o.Major)
{
return 1;
}
if (Major < o.Major)
{
return -1;
}
if (Minor > o.Minor)
{
return 1;
}
if (Minor < o.Minor)
{
return -1;
}
if (Revision > o.Revision)
{
return 1;
}
if (Revision < o.Revision)
{
return -1;
}
if (Patch > o.Patch)
{
return 1;
}
if (Patch < o.Patch)
{
return -1;
}
return 0;
}
int IComparer<ClientVersion>.Compare(ClientVersion x, ClientVersion y) => Compare(x, y);
public static bool operator ==(ClientVersion l, ClientVersion r) => Compare(l, r) == 0;
public static bool operator !=(ClientVersion l, ClientVersion r) => Compare(l, r) != 0;
public static bool operator >=(ClientVersion l, ClientVersion r) => Compare(l, r) >= 0;
public static bool operator >(ClientVersion l, ClientVersion r) => Compare(l, r) > 0;
public static bool operator <=(ClientVersion l, ClientVersion r) => Compare(l, r) <= 0;
public static bool operator <(ClientVersion l, ClientVersion r) => Compare(l, r) < 0;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => HashCode.Combine(Major, Minor, Revision, Patch, Type);
public override bool Equals(object obj)
{
var v = obj as ClientVersion;
return Major == v?.Major
&& Minor == v.Minor
&& Revision == v.Revision
&& Patch == v.Patch
&& Type == v.Type;
}
private string ToStringImpl()
{
using var builder = new ValueStringBuilder(stackalloc char[32]);
if (Major > 5 || Minor > 0 || Revision > 6)
{
builder.Append($"{Major}.{Minor}.{Revision}.{Patch}");
}
else if (Patch > 0)
{
builder.Append($"{Major}.{Minor}.{Revision}{(char)('a' + (Patch - 1))}");
}
else
{
builder.Append($"{Major}.{Minor}.{Revision}");
}
if (Type == ClientType.UOTD)
{
builder.Append(" uotd");
}
return builder.ToString();
}
public override string ToString() => SourceString;
public static bool IsNull(object x) => ReferenceEquals(x, null);
public static int Compare(ClientVersion a, ClientVersion b)
{
if (IsNull(a) && IsNull(b))
{
return 0;
}
if (IsNull(a))
{
return -1;
}
if (IsNull(b))
{
return 1;
}
return a.CompareTo(b);
}
}

View file

@ -0,0 +1,135 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2022 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: UOClient.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.IO;
using System.Text.Json.Serialization;
using Server.Json;
using Server.Logging;
namespace Server;
public static class UOClient
{
private static readonly ILogger logger = LogFactory.GetLogger(typeof(UOClient));
private static bool _automaticallyDetected;
public static CUOSettings CuoSettings { get; private set; }
public static ClientVersion ServerClientVersion { get; private set; }
public static void Load()
{
ServerClientVersion = ServerConfiguration.GetSetting("clientData.clientVersion", (ClientVersion)null);
if (ServerClientVersion == null)
{
ServerClientVersion = DetectCUOClient() ?? DetectClassicClient();
_automaticallyDetected = true;
}
}
public static void Configure()
{
if (ServerClientVersion == null)
{
logger.Warning("Could not detect client version. This may cause data files to load improperly.");
return;
}
if (_automaticallyDetected)
{
logger.Information(
CuoSettings?.ClientVersion == ServerClientVersion
? "Automatically detected client version {ServerClientVersion} from CUO settings."
: "Automatically detected client version {ServerClientVersion}",
ServerClientVersion
);
return;
}
logger.Information("Manually configured to use client version {ServerClientVersion}", ServerClientVersion);
}
private static ClientVersion DetectCUOClient()
{
var path = Core.FindDataFile("settings.json", false);
if (File.Exists(path))
{
var settings = JsonConfig.Deserialize<CUOSettings>(path);
var file = new FileInfo(path);
if (settings.UltimaOnlineDirectory != null)
{
settings.UltimaOnlineDirectory = PathUtility.GetFullPath(settings.UltimaOnlineDirectory, file.DirectoryName);
if (Directory.Exists(settings.UltimaOnlineDirectory))
{
CuoSettings = settings;
}
}
return settings.ClientVersion;
}
return null;
}
private static ClientVersion DetectClassicClient()
{
var path = Core.FindDataFile("client.exe", false);
if (File.Exists(path))
{
using FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
var buffer = GC.AllocateUninitializedArray<byte>((int)fs.Length, true);
fs.Read(buffer);
// VS_VERSION_INFO (unicode)
Span<byte> vsVersionInfo = stackalloc byte[]
{
0x56, 0x00, 0x53, 0x00, 0x5F, 0x00, 0x56, 0x00,
0x45, 0x00, 0x52, 0x00, 0x53, 0x00, 0x49, 0x00,
0x4F, 0x00, 0x4E, 0x00, 0x5F, 0x00, 0x49, 0x00,
0x4E, 0x00, 0x46, 0x00, 0x4F, 0x00
};
for (var i = 0; i < buffer.Length; i++)
{
if (vsVersionInfo.SequenceEqual(buffer.AsSpan(i, 30)))
{
var offset = i + 42; // 30 + 12
var minorPart = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(offset));
var majorPart = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(offset + 2));
var privatePart = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(offset + 4));
var buildPart = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(offset + 6));
return new ClientVersion(majorPart, minorPart, buildPart, privatePart);
}
}
}
return null;
}
public record CUOSettings
{
[JsonPropertyName("clientversion")]
public ClientVersion ClientVersion { get; set; }
[JsonPropertyName("ultimaonlinedirectory")]
public string UltimaOnlineDirectory { get; set; }
}
}

View file

@ -1,237 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Server.Buffers;
namespace Server
{
public enum ClientType
{
Regular,
UOTD,
God,
SA
}
public class ClientVersion : IComparable<ClientVersion>, IComparer<ClientVersion>
{
public ClientVersion(int maj, int min, int rev, int pat, ClientType type = ClientType.Regular)
{
Major = maj;
Minor = min;
Revision = rev;
Patch = pat;
Type = type;
SourceString = Utility.Intern(ToStringImpl());
}
public ClientVersion(string fmt)
{
fmt = fmt.ToLower();
SourceString = Utility.Intern(fmt);
try
{
var br1 = fmt.IndexOfOrdinal('.');
var br2 = fmt.IndexOf('.', br1 + 1);
var br3 = br2 + 1;
while (br3 < fmt.Length && char.IsDigit(fmt, br3))
{
br3++;
}
Major = Utility.ToInt32(fmt.AsSpan()[..br1]);
Minor = Utility.ToInt32(fmt.Substring(br1 + 1, br2 - br1 - 1));
Revision = Utility.ToInt32(fmt.Substring(br2 + 1, br3 - br2 - 1));
if (br3 < fmt.Length)
{
if (Major <= 5 && Minor <= 0 && Revision <= 6) // Anything before 5.0.7
{
if (!char.IsWhiteSpace(fmt, br3))
{
Patch = fmt[br3] - 'a' + 1;
}
}
else
{
Patch = Utility.ToInt32(fmt.Substring(br3 + 1, fmt.Length - br3 - 1));
}
}
if (fmt.InsensitiveContains("god") || fmt.InsensitiveContains("gq"))
{
Type = ClientType.God;
}
else if (fmt.InsensitiveContains("third dawn") ||
fmt.InsensitiveContains("uo:td") ||
fmt.InsensitiveContains("uotd") ||
fmt.InsensitiveContains("uo3d") ||
fmt.InsensitiveContains("uo:3d"))
{
Type = ClientType.UOTD;
}
else
{
Type = ClientType.Regular;
}
}
catch
{
Major = 0;
Minor = 0;
Revision = 0;
Patch = 0;
Type = ClientType.Regular;
}
}
public int Major { get; }
public int Minor { get; }
public int Revision { get; }
public int Patch { get; }
public ClientType Type { get; }
public string SourceString { get; }
public int CompareTo(ClientVersion o)
{
if (o == null)
{
return 1;
}
if (Major > o.Major)
{
return 1;
}
if (Major < o.Major)
{
return -1;
}
if (Minor > o.Minor)
{
return 1;
}
if (Minor < o.Minor)
{
return -1;
}
if (Revision > o.Revision)
{
return 1;
}
if (Revision < o.Revision)
{
return -1;
}
if (Patch > o.Patch)
{
return 1;
}
if (Patch < o.Patch)
{
return -1;
}
return 0;
}
int IComparer<ClientVersion>.Compare(ClientVersion x, ClientVersion y) => Compare(x, y);
public static bool operator ==(ClientVersion l, ClientVersion r) => Compare(l, r) == 0;
public static bool operator !=(ClientVersion l, ClientVersion r) => Compare(l, r) != 0;
public static bool operator >=(ClientVersion l, ClientVersion r) => Compare(l, r) >= 0;
public static bool operator >(ClientVersion l, ClientVersion r) => Compare(l, r) > 0;
public static bool operator <=(ClientVersion l, ClientVersion r) => Compare(l, r) <= 0;
public static bool operator <(ClientVersion l, ClientVersion r) => Compare(l, r) < 0;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => HashCode.Combine(Major, Minor, Revision, Patch, Type);
public override bool Equals(object obj)
{
var v = obj as ClientVersion;
return Major == v?.Major
&& Minor == v.Minor
&& Revision == v.Revision
&& Patch == v.Patch
&& Type == v.Type;
}
private string ToStringImpl()
{
using var builder = new ValueStringBuilder(stackalloc char[32]);
builder.Append(Major.ToString());
builder.Append('.');
builder.Append(Minor.ToString());
builder.Append('.');
builder.Append(Revision.ToString());
if (Major <= 5 && Minor <= 0 && Revision <= 6) // Anything before 5.0.7
{
if (Patch > 0)
{
builder.Append((char)('a' + (Patch - 1)));
}
}
else
{
builder.Append('.');
builder.Append(Patch.ToString());
}
if (Type != ClientType.Regular)
{
builder.Append(' ');
builder.Append(Type.ToString().ToLower());
}
return builder.ToString();
}
public override string ToString() => SourceString;
public static bool IsNull(object x) => ReferenceEquals(x, null);
public static int Compare(ClientVersion a, ClientVersion b)
{
if (IsNull(a) && IsNull(b))
{
return 0;
}
if (IsNull(a))
{
return -1;
}
if (IsNull(b))
{
return 1;
}
return a.CompareTo(b);
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -13,33 +13,43 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
namespace Server.Collections
namespace Server.Collections;
public static class CollectionThrowStrings
{
public static class CollectionThrowStrings
{
public const string ArgumentOutOfRange_Index =
"Index was out of range. Must be non-negative and less than the size of the collection.";
public const string ArgumentOutOfRange_Index =
"Index was out of range. Must be non-negative and less than the size of the collection.";
public const string ArgumentOutOfRange_NeedNonNegNum = "Non-negative number required.";
public const string ArgumentOutOfRange_NeedNonNegNum = "Non-negative number required.";
public const string Argument_InvalidOffLen =
"Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection.";
public const string Argument_InvalidOffLen =
"Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection.";
public const string Argument_AddingDuplicate = "An item with the same value has already been added. Value: {0}";
public const string Argument_AddingDuplicate = "An item with the same value has already been added. Value: {0}";
public const string Arg_ArrayPlusOffTooSmall =
"Destination array is not long enough to copy all the items in the collection. Check array index and length.";
public const string Arg_ArrayPlusOffTooSmall =
"Destination array is not long enough to copy all the items in the collection. Check array index and length.";
public const string InvalidOperation_ConcurrentOperationsNotSupported =
"Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection's state is no longer correct.";
public const string InvalidOperation_ConcurrentOperationsNotSupported =
"Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection's state is no longer correct.";
public const string InvalidOperation_EnumFailedVersion =
"Collection was modified after the enumerator was instantiated.";
public const string InvalidOperation_EnumFailedVersion =
"Collection was modified after the enumerator was instantiated.";
public const string InvalidOperation_EmptyQueue = "Queue empty.";
public const string InvalidOperation_EmptyQueue = "Queue empty.";
public const string InvalidOperation_EnumNotStarted = "Enumeration has not started. Call MoveNext.";
public const string InvalidOperation_EnumNotStarted = "Enumeration has not started. Call MoveNext.";
public const string InvalidOperation_EnumEnded = "Enumeration already finished.";
}
public const string InvalidOperation_EnumEnded = "Enumeration already finished.";
public const string Argument_ArrayTooLarge =
"The input array length must not exceed Int32.MaxValue / {0}. Otherwise BitArray.Length would exceed Int32.MaxValue.";
public const string Arg_ArrayLengthsDiffer = "Array lengths must be the same.";
public const string Arg_RankMultiDimNotSupported =
"Only single dimensional arrays are supported for the requested action.";
public const string Arg_BitArrayTypeUnsupported =
"Only supported array types for CopyTo on BitArrays are Boolean[], Int32[] and Byte[].";
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -6,465 +6,492 @@ using System.Buffers;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Server.Buffers;
namespace Server.Collections
namespace Server.Collections;
// A simple Queue of generic objects. Internally it is implemented as a
// circular buffer, so Enqueue can be O(n). Dequeue is O(1).
[DebuggerDisplay("Count = {Count}")]
public ref struct PooledRefQueue<T>
{
// A simple Queue of generic objects. Internally it is implemented as a
// circular buffer, so Enqueue can be O(n). Dequeue is O(1).
[DebuggerDisplay("Count = {Count}")]
[System.Serializable]
public ref struct PooledRefQueue<T>
private T[] _array;
private int _head; // The index from which to dequeue if the queue isn't empty.
private int _tail; // The index at which to enqueue if the queue isn't full.
private int _size; // Number of elements.
private bool _mt;
private int _version;
#pragma warning disable CA1825 // avoid the extra generic instantiation for Array.Empty<T>()
private static readonly T[] s_emptyArray = new T[0];
#pragma warning restore CA1825
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PooledRefQueue<T> Create(int capacity = 32, bool mt = false) => new(capacity, mt);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PooledRefQueue<T> CreateMT(int capacity = 32) => new(capacity, true);
// Creates a queue with room for capacity objects. The default grow factor
// is used.
public PooledRefQueue(int capacity, bool mt = false)
{
private T[] _array;
private int _head; // The index from which to dequeue if the queue isn't empty.
private int _tail; // The index at which to enqueue if the queue isn't full.
private int _size; // Number of elements.
private int _version;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PooledRefQueue<T> Create(int capacity = 32) => new(capacity);
// Creates a queue with room for capacity objects. The default grow factor
// is used.
public PooledRefQueue(int capacity)
_mt = mt;
_array = capacity switch
{
_array = capacity switch
{
< 0 => throw new ArgumentOutOfRangeException(nameof(capacity), capacity, CollectionThrowStrings.ArgumentOutOfRange_NeedNonNegNum),
0 => Array.Empty<T>(),
_ => ArrayPool<T>.Shared.Rent(capacity)
};
< 0 => throw new ArgumentOutOfRangeException(nameof(capacity), capacity, CollectionThrowStrings.ArgumentOutOfRange_NeedNonNegNum),
0 => s_emptyArray,
_ => (mt ? ArrayPool<T>.Shared : STArrayPool<T>.Shared).Rent(capacity)
};
_head = 0;
_tail = 0;
_size = 0;
_version = 0;
}
_head = 0;
_tail = 0;
_size = 0;
_version = 0;
}
public int Count => _size;
public int Count => _size;
// Removes all Objects from the queue.
public void Clear()
// Removes all Objects from the queue.
public void Clear()
{
if (_size != 0)
{
if (_size != 0)
{
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
{
if (_head < _tail)
{
Array.Clear(_array, _head, _size);
}
else
{
Array.Clear(_array, _head, _array.Length - _head);
Array.Clear(_array, 0, _tail);
}
}
_size = 0;
}
_head = 0;
_tail = 0;
_version++;
}
// CopyTo copies a collection into an Array, starting at a particular
// index into the array.
public void CopyTo(T[] array, int arrayIndex)
{
if (array == null)
{
throw new ArgumentNullException(nameof(array));
}
if (arrayIndex < 0 || arrayIndex > array.Length)
{
throw new ArgumentOutOfRangeException(nameof(arrayIndex), arrayIndex, CollectionThrowStrings.ArgumentOutOfRange_Index);
}
if (array.Length - arrayIndex < _size)
{
throw new ArgumentException(CollectionThrowStrings.Argument_InvalidOffLen);
}
int numToCopy = _size;
if (numToCopy == 0)
{
return;
}
int firstPart = Math.Min(_array.Length - _head, numToCopy);
Array.Copy(_array, _head, array, arrayIndex, firstPart);
numToCopy -= firstPart;
if (numToCopy > 0)
{
Array.Copy(_array, 0, array, arrayIndex + _array.Length - _head, numToCopy);
}
}
// Adds item to the tail of the queue.
public void Enqueue(T item)
{
if (_size == _array.Length)
{
Grow(_size + 1);
}
_array[_tail] = item;
MoveNext(ref _tail);
_size++;
_version++;
}
// GetEnumerator returns an IEnumerator over this Queue. This
// Enumerator will support removing.
public Enumerator GetEnumerator() => new(this);
// Removes the object at the head of the queue and returns it. If the queue
// is empty, this method throws an
// InvalidOperationException.
public T Dequeue()
{
int head = _head;
T[] array = _array;
if (_size == 0)
{
ThrowForEmptyQueue();
}
T removed = array[head];
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
{
array[head] = default!;
}
MoveNext(ref _head);
_size--;
_version++;
return removed;
}
public bool TryDequeue([MaybeNullWhen(false)] out T result)
{
int head = _head;
T[] array = _array;
if (_size == 0)
{
result = default!;
return false;
}
result = array[head];
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
{
array[head] = default!;
}
MoveNext(ref _head);
_size--;
_version++;
return true;
}
// Returns the object at the head of the queue. The object remains in the
// queue. If the queue is empty, this method throws an
// InvalidOperationException.
public T Peek()
{
if (_size == 0)
{
ThrowForEmptyQueue();
}
return _array[_head];
}
public bool TryPeek([MaybeNullWhen(false)] out T result)
{
if (_size == 0)
{
result = default!;
return false;
}
result = _array[_head];
return true;
}
// Returns true if the queue contains at least one object equal to item.
// Equality is determined using EqualityComparer<T>.Default.Equals().
public bool Contains(T item)
{
if (_size == 0)
{
return false;
}
if (_head < _tail)
{
return Array.IndexOf(_array, item, _head, _size) >= 0;
}
// We've wrapped around. Check both partitions, the least recently enqueued first.
return
Array.IndexOf(_array, item, _head, _array.Length - _head) >= 0 ||
Array.IndexOf(_array, item, 0, _tail) >= 0;
}
// Iterates over the objects in the queue, returning an array of the
// objects in the Queue, or an empty array if the queue is empty.
// The order of elements in the array is first in to last in, the same
// order produced by successive calls to Dequeue.
public T[] ToArray()
{
if (_size == 0)
{
return Array.Empty<T>();
}
T[] arr = new T[_size];
if (_head < _tail)
{
Array.Copy(_array, _head, arr, 0, _size);
}
else
{
Array.Copy(_array, _head, arr, 0, _array.Length - _head);
Array.Copy(_array, 0, arr, _array.Length - _head, _tail);
}
return arr;
}
public T[] ToPooledArray()
{
if (_size == 0)
{
return Array.Empty<T>();
}
T[] arr = ArrayPool<T>.Shared.Rent(_size);
if (_head < _tail)
{
Array.Copy(_array, _head, arr, 0, _size);
}
else
{
Array.Copy(_array, _head, arr, 0, _array.Length - _head);
Array.Copy(_array, 0, arr, _array.Length - _head, _tail);
}
return arr;
}
// PRIVATE Grows or shrinks the buffer to hold capacity objects. Capacity
// must be >= _size.
private void SetCapacity(int capacity)
{
T[] newarray = ArrayPool<T>.Shared.Rent(capacity);
if (_size > 0)
{
if (_head < _tail)
{
Array.Copy(_array, _head, newarray, 0, _size);
Array.Clear(_array, _head, _size);
}
else
{
Array.Copy(_array, _head, newarray, 0, _array.Length - _head);
Array.Copy(_array, 0, newarray, _array.Length - _head, _tail);
Array.Clear(_array, _head, _array.Length - _head);
Array.Clear(_array, 0, _tail);
}
}
if (_array.Length > 0)
{
ArrayPool<T>.Shared.Return(_array, true);
}
_array = newarray;
_head = 0;
_tail = _size == capacity ? 0 : _size;
_version++;
_size = 0;
}
// Increments the index wrapping it if necessary.
private void MoveNext(ref int index)
_head = 0;
_tail = 0;
_version++;
}
// CopyTo copies a collection into an Array, starting at a particular
// index into the array.
public void CopyTo(T[] array, int arrayIndex)
{
if (array == null)
{
// It is tempting to use the remainder operator here but it is actually much slower
// than a simple comparison and a rarely taken branch.
// JIT produces better code than with ternary operator ?:
int tmp = index + 1;
if (tmp == _array.Length)
{
tmp = 0;
}
index = tmp;
throw new ArgumentNullException(nameof(array));
}
private void ThrowForEmptyQueue()
if (arrayIndex < 0 || arrayIndex > array.Length)
{
Debug.Assert(_size == 0);
throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EmptyQueue);
throw new ArgumentOutOfRangeException(nameof(arrayIndex), arrayIndex, CollectionThrowStrings.ArgumentOutOfRange_Index);
}
/// <summary>
/// Ensures that the capacity of this Queue is at least the specified <paramref name="capacity"/>.
/// </summary>
/// <param name="capacity">The minimum capacity to ensure.</param>
public int EnsureCapacity(int capacity)
if (array.Length - arrayIndex < _size)
{
if (capacity < 0)
{
throw new ArgumentOutOfRangeException(nameof(capacity), capacity, CollectionThrowStrings.ArgumentOutOfRange_NeedNonNegNum);
}
if (_array.Length < capacity)
{
Grow(capacity);
}
return _array.Length;
throw new ArgumentException(CollectionThrowStrings.Argument_InvalidOffLen);
}
private void Grow(int capacity)
int numToCopy = _size;
if (numToCopy == 0)
{
const int GrowFactor = 2;
const int MinimumGrow = 4;
int newcapacity = GrowFactor * _array.Length;
// Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow.
// Note that this check works even when _items.Length overflowed thanks to the (uint) cast
if ((uint)newcapacity > int.MaxValue)
{
newcapacity = int.MaxValue;
}
// Ensure minimum growth is respected.
newcapacity = Math.Max(newcapacity, _array.Length + MinimumGrow);
// If the computed capacity is still less than specified, set to the original argument.
// Capacities exceeding Array.MaxLength will be surfaced as OutOfMemoryException by Array.Resize.
if (newcapacity < capacity)
{
newcapacity = capacity;
}
SetCapacity(newcapacity);
return;
}
int firstPart = Math.Min(_array.Length - _head, numToCopy);
Array.Copy(_array, _head, array, arrayIndex, firstPart);
numToCopy -= firstPart;
if (numToCopy > 0)
{
Array.Copy(_array, 0, array, arrayIndex + _array.Length - _head, numToCopy);
}
}
// Adds item to the tail of the queue.
public void Enqueue(T item)
{
if (_size == _array.Length)
{
Grow(_size + 1);
}
_array[_tail] = item;
MoveNext(ref _tail);
_size++;
_version++;
}
// GetEnumerator returns an IEnumerator over this Queue. This
// Enumerator will support removing.
public Enumerator GetEnumerator() => new(this);
// Removes the object at the head of the queue and returns it. If the queue
// is empty, this method throws an
// InvalidOperationException.
public T Dequeue()
{
int head = _head;
T[] array = _array;
if (_size == 0)
{
ThrowForEmptyQueue();
}
T removed = array[head];
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
{
array[head] = default!;
}
MoveNext(ref _head);
_size--;
_version++;
return removed;
}
public bool TryDequeue([MaybeNullWhen(false)] out T result)
{
int head = _head;
T[] array = _array;
if (_size == 0)
{
result = default!;
return false;
}
result = array[head];
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
{
array[head] = default!;
}
MoveNext(ref _head);
_size--;
_version++;
return true;
}
// Returns the object at the head of the queue. The object remains in the
// queue. If the queue is empty, this method throws an
// InvalidOperationException.
public T Peek()
{
if (_size == 0)
{
ThrowForEmptyQueue();
}
return _array[_head];
}
public T PeekRandom()
{
if (_size == 0)
{
ThrowForEmptyQueue();
}
var index = _head + Utility.Random(_size);
if (index >= _array.Length)
{
index -= _array.Length;
}
return _array[index];
}
public bool TryPeek([MaybeNullWhen(false)] out T result)
{
if (_size == 0)
{
result = default!;
return false;
}
result = _array[_head];
return true;
}
// Returns true if the queue contains at least one object equal to item.
// Equality is determined using EqualityComparer<T>.Default.Equals().
public bool Contains(T item)
{
if (_size == 0)
{
return false;
}
if (_head < _tail)
{
return Array.IndexOf(_array, item, _head, _size) >= 0;
}
// We've wrapped around. Check both partitions, the least recently enqueued first.
return
Array.IndexOf(_array, item, _head, _array.Length - _head) >= 0 ||
Array.IndexOf(_array, item, 0, _tail) >= 0;
}
// Iterates over the objects in the queue, returning an array of the
// objects in the Queue, or an empty array if the queue is empty.
// The order of elements in the array is first in to last in, the same
// order produced by successive calls to Dequeue.
public T[] ToArray()
{
if (_size == 0)
{
return s_emptyArray;
}
T[] arr = new T[_size];
if (_head < _tail)
{
Array.Copy(_array, _head, arr, 0, _size);
}
else
{
Array.Copy(_array, _head, arr, 0, _array.Length - _head);
Array.Copy(_array, 0, arr, _array.Length - _head, _tail);
}
return arr;
}
public T[] ToPooledArray(bool mt = false)
{
if (_size == 0)
{
return s_emptyArray;
}
T[] arr = (mt ? ArrayPool<T>.Shared : STArrayPool<T>.Shared).Rent(_size);
if (_head < _tail)
{
Array.Copy(_array, _head, arr, 0, _size);
}
else
{
Array.Copy(_array, _head, arr, 0, _array.Length - _head);
Array.Copy(_array, 0, arr, _array.Length - _head, _tail);
}
return arr;
}
// PRIVATE Grows or shrinks the buffer to hold capacity objects. Capacity
// must be >= _size.
private void SetCapacity(int capacity)
{
T[] newarray = (_mt ? ArrayPool<T>.Shared : STArrayPool<T>.Shared).Rent(capacity);
if (_size > 0)
{
if (_head < _tail)
{
Array.Copy(_array, _head, newarray, 0, _size);
}
else
{
Array.Copy(_array, _head, newarray, 0, _array.Length - _head);
Array.Copy(_array, 0, newarray, _array.Length - _head, _tail);
}
}
if (_array.Length > 0)
{
Clear();
(_mt ? ArrayPool<T>.Shared : STArrayPool<T>.Shared).Return(_array);
}
_array = newarray;
_head = 0;
_tail = _size == capacity ? 0 : _size;
_version++;
}
// Increments the index wrapping it if necessary.
private void MoveNext(ref int index)
{
// It is tempting to use the remainder operator here but it is actually much slower
// than a simple comparison and a rarely taken branch.
// JIT produces better code than with ternary operator ?:
int tmp = index + 1;
if (tmp == _array.Length)
{
tmp = 0;
}
index = tmp;
}
private void ThrowForEmptyQueue()
{
Debug.Assert(_size == 0);
throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EmptyQueue);
}
/// <summary>
/// Ensures that the capacity of this Queue is at least the specified <paramref name="capacity"/>.
/// </summary>
/// <param name="capacity">The minimum capacity to ensure.</param>
public int EnsureCapacity(int capacity)
{
if (capacity < 0)
{
throw new ArgumentOutOfRangeException(nameof(capacity), capacity, CollectionThrowStrings.ArgumentOutOfRange_NeedNonNegNum);
}
if (_array.Length < capacity)
{
Grow(capacity);
}
return _array.Length;
}
private void Grow(int capacity)
{
const int GrowFactor = 2;
const int MinimumGrow = 4;
int newcapacity = GrowFactor * _array.Length;
// Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow.
// Note that this check works even when _items.Length overflowed thanks to the (uint) cast
if ((uint)newcapacity > int.MaxValue)
{
newcapacity = int.MaxValue;
}
// Ensure minimum growth is respected.
newcapacity = Math.Max(newcapacity, _array.Length + MinimumGrow);
// If the computed capacity is still less than specified, set to the original argument.
// Capacities exceeding Array.MaxLength will be surfaced as OutOfMemoryException by Array.Resize.
if (newcapacity < capacity)
{
newcapacity = capacity;
}
SetCapacity(newcapacity);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
var array = _array;
if (array.Length > 0)
{
Clear();
(_mt ? ArrayPool<T>.Shared : STArrayPool<T>.Shared).Return(array);
}
this = default;
}
// Implements an enumerator for a Queue. The enumerator uses the
// internal version number of the list to ensure that no modifications are
// made to the list while an enumeration is in progress.
public ref struct Enumerator
{
private readonly PooledRefQueue<T> _q;
private readonly int _version;
private int _index; // -1 = not started, -2 = ended/disposed
private T? _currentElement;
internal Enumerator(PooledRefQueue<T> q)
{
_q = q;
_version = q._version;
_index = -1;
_currentElement = default;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
var array = _array;
if (array.Length > 0)
{
ArrayPool<T>.Shared.Return(array, true);
}
this = default;
_index = -2;
_currentElement = default;
}
// Implements an enumerator for a Queue. The enumerator uses the
// internal version number of the list to ensure that no modifications are
// made to the list while an enumeration is in progress.
public ref struct Enumerator
public bool MoveNext()
{
private readonly PooledRefQueue<T> _q;
private readonly int _version;
private int _index; // -1 = not started, -2 = ended/disposed
private T? _currentElement;
internal Enumerator(PooledRefQueue<T> q)
if (_version != _q._version)
{
_q = q;
_version = q._version;
_index = -1;
_currentElement = default;
throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion);
}
public void Dispose()
if (_index == -2)
{
return false;
}
_index++;
if (_index == _q._size)
{
// We've run past the last element
_index = -2;
_currentElement = default;
return false;
}
public bool MoveNext()
// Cache some fields in locals to decrease code size
T[] array = _q._array;
int capacity = array.Length;
// _index represents the 0-based index into the queue, however the queue
// doesn't have to start from 0 and it may not even be stored contiguously in memory.
int arrayIndex = _q._head + _index; // this is the actual index into the queue's backing array
if (arrayIndex >= capacity)
{
if (_version != _q._version)
{
throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion);
}
// NOTE: Originally we were using the modulo operator here, however
// on Intel processors it has a very high instruction latency which
// was slowing down the loop quite a bit.
// Replacing it with simple comparison/subtraction operations sped up
// the average foreach loop by 2x.
if (_index == -2)
{
return false;
}
_index++;
if (_index == _q._size)
{
// We've run past the last element
_index = -2;
_currentElement = default;
return false;
}
// Cache some fields in locals to decrease code size
T[] array = _q._array;
int capacity = array.Length;
// _index represents the 0-based index into the queue, however the queue
// doesn't have to start from 0 and it may not even be stored contiguously in memory.
int arrayIndex = _q._head + _index; // this is the actual index into the queue's backing array
if (arrayIndex >= capacity)
{
// NOTE: Originally we were using the modulo operator here, however
// on Intel processors it has a very high instruction latency which
// was slowing down the loop quite a bit.
// Replacing it with simple comparison/subtraction operations sped up
// the average foreach loop by 2x.
arrayIndex -= capacity; // wrap around if needed
}
_currentElement = array[arrayIndex];
return true;
arrayIndex -= capacity; // wrap around if needed
}
public T Current
_currentElement = array[arrayIndex];
return true;
}
public T Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
get
if (_index < 0)
{
if (_index < 0)
{
ThrowEnumerationNotStartedOrEnded();
}
return _currentElement!;
}
}
private void ThrowEnumerationNotStartedOrEnded()
{
Debug.Assert(_index == -1 || _index == -2);
throw new InvalidOperationException(_index == -1 ? CollectionThrowStrings.InvalidOperation_EnumNotStarted : CollectionThrowStrings.InvalidOperation_EnumEnded);
}
public void Reset()
{
if (_version != _q._version)
{
throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion);
ThrowEnumerationNotStartedOrEnded();
}
_index = -1;
_currentElement = default;
return _currentElement!;
}
}
private void ThrowEnumerationNotStartedOrEnded()
{
Debug.Assert(_index is -1 or -2);
throw new InvalidOperationException(_index == -1 ? CollectionThrowStrings.InvalidOperation_EnumNotStarted : CollectionThrowStrings.InvalidOperation_EnumEnded);
}
public void Reset()
{
if (_version != _q._version)
{
throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion);
}
_index = -1;
_currentElement = default;
}
}
}

View file

@ -26,65 +26,20 @@ namespace Server
public int Length => Arguments.Length;
public string GetString(int index)
{
if (index < 0 || index >= Arguments.Length)
{
return "";
}
public string GetString(int index) => index < 0 || index >= Arguments.Length ? "" : Arguments[index];
return Arguments[index];
}
public int GetInt32(int index) => index < 0 || index >= Arguments.Length ? 0 : Utility.ToInt32(Arguments[index]);
public int GetInt32(int index)
{
if (index < 0 || index >= Arguments.Length)
{
return 0;
}
public uint GetUInt32(int index) =>
index < 0 || index >= Arguments.Length ? 0 : Utility.ToUInt32(Arguments[index]);
return Utility.ToInt32(Arguments[index]);
}
public bool GetBoolean(int index) => index >= 0 && index < Arguments.Length && Utility.ToBoolean(Arguments[index]);
public uint GetUInt32(int index)
{
if (index < 0 || index >= Arguments.Length)
{
return 0;
}
public double GetDouble(int index) =>
index < 0 || index >= Arguments.Length ? 0.0 : Utility.ToDouble(Arguments[index]);
return Utility.ToUInt32(Arguments[index]);
}
public bool GetBoolean(int index)
{
if (index < 0 || index >= Arguments.Length)
{
return false;
}
return Utility.ToBoolean(Arguments[index]);
}
public double GetDouble(int index)
{
if (index < 0 || index >= Arguments.Length)
{
return 0.0;
}
return Utility.ToDouble(Arguments[index]);
}
public TimeSpan GetTimeSpan(int index)
{
if (index < 0 || index >= Arguments.Length)
{
return TimeSpan.Zero;
}
return Utility.ToTimeSpan(Arguments[index]);
}
public TimeSpan GetTimeSpan(int index) =>
index < 0 || index >= Arguments.Length ? TimeSpan.Zero : Utility.ToTimeSpan(Arguments[index]);
}
public static partial class EventSink
@ -137,27 +92,7 @@ namespace Server
}
}
public record CommandInfo
{
public CommandInfo(AccessLevel accessLevel, string name, string[] aliases, string usage, string description)
{
AccessLevel = accessLevel;
Name = name;
Aliases = aliases;
Usage = usage;
Description = description;
}
public AccessLevel AccessLevel { get; }
public string Name { get; }
public string[] Aliases { get; }
public string Usage { get; }
public string Description { get; }
}
public record CommandInfo(AccessLevel AccessLevel, string Name, string[] Aliases, string Usage, string Description);
public class CommandInfoSorter : IComparer<CommandInfo>
{

View file

@ -15,424 +15,303 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using Server.Json;
using Server.Logging;
namespace Server
namespace Server;
public static class ServerConfiguration
{
public static class ServerConfiguration
private static readonly ILogger logger = LogFactory.GetLogger(typeof(ServerConfiguration));
private const string _relPath = "Configuration/modernuo.json";
private static readonly string m_FilePath = Path.Join(Core.BaseDirectory, _relPath);
private static ServerSettings m_Settings;
private static bool m_Mocked;
public static List<string> AssemblyDirectories => m_Settings.AssemblyDirectories;
public static HashSet<string> DataDirectories => m_Settings.DataDirectories;
public static List<IPEndPoint> Listeners => m_Settings.Listeners;
public static ClientVersion GetSetting(string key, ClientVersion defaultValue) =>
m_Settings.Settings.TryGetValue(key, out var value) ? new ClientVersion(value) : defaultValue;
public static string GetSetting(string key, string defaultValue) =>
m_Settings.Settings.TryGetValue(key, out var value) ? value : defaultValue;
public static int GetSetting(string key, int defaultValue)
{
private static readonly ILogger logger = LogFactory.GetLogger(typeof(ServerConfiguration));
m_Settings.Settings.TryGetValue(key, out var strValue);
return int.TryParse(strValue, out var value) ? value : defaultValue;
}
private const string _relPath = "Configuration/modernuo.json";
private static readonly string m_FilePath = Path.Join(Core.BaseDirectory, _relPath);
private static ServerSettings m_Settings;
private static bool m_Mocked;
public static long GetSetting(string key, long defaultValue)
{
m_Settings.Settings.TryGetValue(key, out var strValue);
return long.TryParse(strValue, out var value) ? value : defaultValue;
}
public static List<string> AssemblyDirectories => m_Settings.AssemblyDirectories;
public static bool GetSetting(string key, bool defaultValue)
{
m_Settings.Settings.TryGetValue(key, out var strValue);
return bool.TryParse(strValue, out var value) ? value : defaultValue;
}
public static List<string> DataDirectories => m_Settings.DataDirectories;
public static T GetSetting<T>(string key, T defaultValue) where T : struct, Enum
{
m_Settings.Settings.TryGetValue(key, out var strValue);
return Enum.TryParse(strValue, out T value) ? value : defaultValue;
}
public static List<IPEndPoint> Listeners => m_Settings.Listeners;
public static double GetSetting(string key, double defaultValue)
{
m_Settings.Settings.TryGetValue(key, out var strValue);
return double.TryParse(strValue, out var value) ? value : defaultValue;
}
public static string GetSetting(string key, string defaultValue)
public static T? GetSetting<T>(string key) where T : struct, Enum
{
if (!m_Settings.Settings.TryGetValue(key, out var strValue))
{
m_Settings.Settings.TryGetValue(key, out var value);
return value ?? defaultValue;
return null;
}
public static int GetSetting(string key, int defaultValue)
return Enum.TryParse(strValue, out T value) ? value : null;
}
public static string GetOrUpdateSetting(string key, string defaultValue)
{
if (m_Settings.Settings.TryGetValue(key, out var value))
{
m_Settings.Settings.TryGetValue(key, out var strValue);
return int.TryParse(strValue, out var value) ? value : defaultValue;
}
public static long GetSetting(string key, long defaultValue)
{
m_Settings.Settings.TryGetValue(key, out var strValue);
return long.TryParse(strValue, out var value) ? value : defaultValue;
}
public static bool GetSetting(string key, bool defaultValue)
{
m_Settings.Settings.TryGetValue(key, out var strValue);
return bool.TryParse(strValue, out var value) ? value : defaultValue;
}
public static T GetSetting<T>(string key, T defaultValue) where T : struct, Enum
{
m_Settings.Settings.TryGetValue(key, out var strValue);
return Enum.TryParse(strValue, out T value) ? value : defaultValue;
}
public static T? GetSetting<T>(string key) where T : struct, Enum
{
if (!m_Settings.Settings.TryGetValue(key, out var strValue))
{
return null;
}
return Enum.TryParse(strValue, out T value) ? value : null;
}
public static string GetOrUpdateSetting(string key, string defaultValue)
{
if (m_Settings.Settings.TryGetValue(key, out var value))
{
return value;
}
SetSetting(key, value = defaultValue);
return value;
}
public static int GetOrUpdateSetting(string key, int defaultValue)
SetSetting(key, value = defaultValue);
return value;
}
public static int GetOrUpdateSetting(string key, int defaultValue)
{
int value;
if (m_Settings.Settings.TryGetValue(key, out var strValue))
{
int value;
if (m_Settings.Settings.TryGetValue(key, out var strValue))
{
value = int.TryParse(strValue, out value) ? value : defaultValue;
}
else
{
SetSetting(key, (value = defaultValue).ToString());
}
return value;
value = int.TryParse(strValue, out value) ? value : defaultValue;
}
else
{
SetSetting(key, (value = defaultValue).ToString());
}
public static long GetOrUpdateSetting(string key, long defaultValue)
return value;
}
public static long GetOrUpdateSetting(string key, long defaultValue)
{
long value;
if (m_Settings.Settings.TryGetValue(key, out var strValue))
{
long value;
if (m_Settings.Settings.TryGetValue(key, out var strValue))
{
value = long.TryParse(strValue, out value) ? value : defaultValue;
}
else
{
SetSetting(key, (value = defaultValue).ToString());
}
return value;
value = long.TryParse(strValue, out value) ? value : defaultValue;
}
else
{
SetSetting(key, (value = defaultValue).ToString());
}
public static bool GetOrUpdateSetting(string key, bool defaultValue)
return value;
}
public static bool GetOrUpdateSetting(string key, bool defaultValue)
{
bool value;
if (m_Settings.Settings.TryGetValue(key, out var strValue))
{
bool value;
if (m_Settings.Settings.TryGetValue(key, out var strValue))
{
value = bool.TryParse(strValue, out value) ? value : defaultValue;
}
else
{
SetSetting(key, (value = defaultValue).ToString());
}
return value;
value = bool.TryParse(strValue, out value) ? value : defaultValue;
}
else
{
SetSetting(key, (value = defaultValue).ToString());
}
public static TimeSpan GetOrUpdateSetting(string key, TimeSpan defaultValue)
return value;
}
public static TimeSpan GetOrUpdateSetting(string key, TimeSpan defaultValue)
{
TimeSpan value;
if (m_Settings.Settings.TryGetValue(key, out var strValue))
{
TimeSpan value;
if (m_Settings.Settings.TryGetValue(key, out var strValue))
{
value = TimeSpan.TryParse(strValue, out value) ? value : defaultValue;
}
else
{
SetSetting(key, (value = defaultValue).ToString());
}
return value;
value = TimeSpan.TryParse(strValue, out value) ? value : defaultValue;
}
else
{
SetSetting(key, (value = defaultValue).ToString());
}
public static T GetOrUpdateSetting<T>(string key, T defaultValue) where T : struct, Enum
return value;
}
public static T GetOrUpdateSetting<T>(string key, T defaultValue) where T : struct, Enum
{
T value;
if (m_Settings.Settings.TryGetValue(key, out var strValue))
{
T value;
if (m_Settings.Settings.TryGetValue(key, out var strValue))
{
value = Enum.TryParse(strValue, out value) ? value : defaultValue;
}
else
{
SetSetting(key, (value = defaultValue).ToString());
}
return value;
value = Enum.TryParse(strValue, out value) ? value : defaultValue;
}
else
{
SetSetting(key, (value = defaultValue).ToString());
}
public static void SetSetting(string key, TimeSpan value) => SetSetting(key, value.ToString());
return value;
}
public static void SetSetting(string key, int value) => SetSetting(key, value.ToString());
public static double GetOrUpdateSetting(string key, double defaultValue)
{
double value;
public static void SetSetting(string key, long value) => SetSetting(key, value.ToString());
public static void SetSetting(string key, bool value) => SetSetting(key, value.ToString());
public static void SetSetting<T>(string key, T value) where T : struct, Enum =>
SetSetting(key, value.ToString());
public static void SetSetting(string key, string value)
if (m_Settings.Settings.TryGetValue(key, out var strValue))
{
m_Settings.Settings[key] = value;
Save();
value = double.TryParse(strValue, out value) ? value : defaultValue;
}
else
{
SetSetting(key, (value = defaultValue).ToString());
}
// If mock is enabled we skip the console readline.
public static void Load(bool mocked = false)
return value;
}
public static void SetSetting(string key, double value) => SetSetting(key, value.ToString());
public static void SetSetting(string key, TimeSpan value) => SetSetting(key, value.ToString());
public static void SetSetting(string key, int value) => SetSetting(key, value.ToString());
public static void SetSetting(string key, long value) => SetSetting(key, value.ToString());
public static void SetSetting(string key, bool value) => SetSetting(key, value.ToString());
public static void SetSetting<T>(string key, T value) where T : struct, Enum =>
SetSetting(key, value.ToString());
public static void SetSetting(string key, string value)
{
m_Settings.Settings[key] = value;
Save();
}
// If mock is enabled we skip the console readline.
public static void Load(bool mocked = false)
{
m_Mocked = mocked;
var updated = false;
if (File.Exists(m_FilePath))
{
m_Mocked = mocked;
var updated = false;
logger.Information("Reading server configuration from {Path}...", _relPath);
m_Settings = JsonConfig.Deserialize<ServerSettings>(m_FilePath);
if (File.Exists(m_FilePath))
if (m_Settings == null)
{
logger.Information($"Reading server configuration from {_relPath}...");
m_Settings = JsonConfig.Deserialize<ServerSettings>(m_FilePath);
if (m_Settings == null)
{
logger.Error("Reading server configuration failed");
throw new FileNotFoundException($"Failed to deserialize {m_FilePath}.");
}
logger.Information("Reading server configuration done");
}
else
{
updated = true;
m_Settings = new ServerSettings();
logger.Error("Reading server configuration failed");
throw new FileNotFoundException($"Failed to deserialize {m_FilePath}.");
}
if (mocked)
logger.Information("Reading server configuration done");
}
else
{
updated = true;
m_Settings = new ServerSettings();
}
if (mocked)
{
return;
}
if (m_Settings.DataDirectories.Count == 0)
{
updated = true;
foreach (var directory in ServerConfigurationPrompts.GetDataDirectories())
{
return;
}
if (m_Settings.DataDirectories.Count == 0)
{
updated = true;
m_Settings.DataDirectories.AddRange(GetDataDirectories());
}
if (m_Settings.Listeners.Count == 0)
{
updated = true;
m_Settings.Listeners.AddRange(GetListeners());
}
if (m_Settings.Expansion == null)
{
var expansion = GetSetting<Expansion>("currentExpansion");
var hasExpansion = expansion != null;
expansion ??= GetExpansion();
if (expansion <= Expansion.ML && !hasExpansion)
{
SetPre6000Support();
}
updated = true;
m_Settings.Expansion = expansion;
}
Core.Expansion = m_Settings.Expansion.Value;
if (updated)
{
Save();
Console.Write("Server configuration saved to ");
Utility.PushColor(ConsoleColor.Green);
Console.WriteLine($"{_relPath}.");
Utility.PopColor();
m_Settings.DataDirectories.Add(directory);
}
}
private static void SetPre6000Support()
UOClient.Load();
var cuoClientFiles = UOClient.CuoSettings?.UltimaOnlineDirectory;
if (cuoClientFiles != null)
{
Console.WriteLine("Will you be using a client version older than 6.0.0.0?");
DataDirectories.Add(cuoClientFiles);
}
do
if (m_Settings.Listeners.Count == 0)
{
updated = true;
m_Settings.Listeners.AddRange(ServerConfigurationPrompts.GetListeners());
}
bool? isPre60000 = null;
if (m_Settings.Expansion == null)
{
var expansion = GetSetting<Expansion>("currentExpansion");
var hasExpansion = expansion != null;
expansion ??= ServerConfigurationPrompts.GetExpansion();
if (expansion <= Expansion.ML && !hasExpansion)
{
Console.Write("y or [n]> ");
var input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input) || input.InsensitiveStartsWith("n"))
{
Utility.PushColor(ConsoleColor.Yellow);
Console.WriteLine("Client >= 6.0.0.0 chosen.");
Utility.PopColor();
return;
}
if (input.InsensitiveStartsWith("y"))
isPre60000 = ServerConfigurationPrompts.GetIsClientPre6000();
if (isPre60000 == true)
{
SetSetting("maps.enablePre6000Trammel", true.ToString());
Utility.PushColor(ConsoleColor.Yellow);
Console.WriteLine("Client <= 5.0.9.1 chosen.");
Utility.PopColor();
return;
}
Console.Write("Invalid option ");
Utility.PushColor(ConsoleColor.Red);
Console.Write(input);
Utility.PopColor();
Console.WriteLine(". Press y for yes or n for no.");
} while (true);
}
private static Expansion GetExpansion()
{
Console.WriteLine("Please choose an expansion by typing the number or short name:");
var expansions = ExpansionInfo.Table;
for (int i = 0; i < expansions.Length; i++)
{
var info = expansions[i];
Console.WriteLine(" - {0,2}: {1} ({2})", i, ((Expansion)info.ID).ToString(), info.Name);
}
var maxExpansion = (Expansion)expansions[^1].ID;
var maxExpansionName = maxExpansion.ToString();
do
{
Console.Write("[enter for {0}]> ", maxExpansionName);
var input = Console.ReadLine();
Expansion expansion;
if (string.IsNullOrWhiteSpace(input))
{
expansion = maxExpansion;
}
else if (int.TryParse(input, NumberStyles.Integer, null, out var number) &&
number >= 0 && number < expansions.Length)
{
expansion = (Expansion)number;
}
else if (!Enum.TryParse(input, out expansion))
{
Utility.PushColor(ConsoleColor.Red);
Console.Write(input);
Utility.PopColor();
Console.WriteLine(" is an invalid expansion option.");
continue;
}
Console.Write("Expansion set to ");
Utility.PushColor(ConsoleColor.Green);
Console.Write(ExpansionInfo.GetInfo(expansion).Name);
Utility.PopColor();
Console.WriteLine(".");
return expansion;
} while (true);
updated = true;
m_Settings.Expansion = expansion;
}
private static List<string> GetDataDirectories()
if (isPre60000 != true)
{
Console.WriteLine("Please enter the absolute path to the Ultima Online data:");
var directories = new List<string>();
do
if (ServerConfigurationPrompts.GetIsClient7090())
{
Console.Write("{0}> ", directories.Count > 0 ? "[enter to finish]" : " ");
var directory = Console.ReadLine();
if (string.IsNullOrWhiteSpace(directory))
{
break;
}
if (Directory.Exists(directory))
{
directories.Add(directory);
Console.Write("Added ");
Utility.PushColor(ConsoleColor.Green);
Console.Write(directory);
Utility.PopColor();
Console.WriteLine(".");
}
else
{
Utility.PushColor(ConsoleColor.Red);
Console.Write(directory);
Utility.PopColor();
Console.WriteLine(" does not exist.");
}
} while (true);
return directories;
}
private static List<IPEndPoint> GetListeners()
{
Console.WriteLine("Please enter the IP and ports to listen:");
Console.WriteLine(" - Only enter IP addresses directly bound to this machine");
Console.WriteLine(" - To listen to all IP addresses enter 0.0.0.0");
var ips = new List<IPEndPoint>();
do
{
// IP:Port?
Console.Write("[{0}]> ", ips.Count > 0 ? "enter to finish" : "0.0.0.0:2593");
var ipStr = Console.ReadLine();
IPEndPoint ip;
if (string.IsNullOrWhiteSpace(ipStr))
{
if (ips.Count > 0)
{
break;
}
ip = new IPEndPoint(IPAddress.Any, 2593);
}
else
{
if (!ipStr.ContainsOrdinal(':'))
{
ipStr += ":2593";
}
if (!IPEndPoint.TryParse(ipStr, out ip))
{
Utility.PushColor(ConsoleColor.Red);
Console.Write(ipStr);
Utility.PopColor();
Console.WriteLine(" is not a valid IP or port.");
continue;
}
}
ips.Add(ip);
Console.Write("Added ");
Utility.PushColor(ConsoleColor.Green);
Console.Write(ip);
Utility.PopColor();
Console.WriteLine(".");
} while (true);
return ips;
}
public static void Save()
{
if (m_Mocked)
{
return;
updated = true;
SetSetting("maps.enablePostHSMultiComponentFormat", true);
}
}
JsonConfig.Serialize(m_FilePath, m_Settings);
Core.Expansion = m_Settings.Expansion.Value;
if (updated)
{
Save();
Console.Write("Server configuration saved to ");
Utility.PushColor(ConsoleColor.Green);
Console.WriteLine($"{_relPath}.");
Utility.PopColor();
}
}
public static void Save()
{
if (m_Mocked)
{
return;
}
JsonConfig.Serialize(m_FilePath, m_Settings);
}
}

View file

@ -0,0 +1,220 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
namespace Server;
public static class ServerConfigurationPrompts
{
internal static bool GetIsClient7090()
{
if (UOClient.ServerClientVersion != null)
{
return UOClient.ServerClientVersion >= ClientVersion.Version7090;
}
Console.WriteLine("Will you be using a client version 7.0.9.0 or newer?");
do
{
Console.Write("[y] or n> ");
var input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input) || input.InsensitiveStartsWith("y"))
{
Utility.PushColor(ConsoleColor.Yellow);
Console.WriteLine("Client >= 7.0.9.0 chosen.");
Utility.PopColor();
return true;
}
if (input.InsensitiveStartsWith("n"))
{
Utility.PushColor(ConsoleColor.Yellow);
Console.WriteLine("Client < 7.0.9.0 chosen.");
Utility.PopColor();
return false;
}
Console.Write("Invalid option ");
Utility.PushColor(ConsoleColor.Red);
Console.Write(input);
Utility.PopColor();
Console.WriteLine(". Press y for yes or n for no.");
} while (true);
}
internal static bool GetIsClientPre6000()
{
if (UOClient.ServerClientVersion != null)
{
return UOClient.ServerClientVersion < ClientVersion.Version6000;
}
Console.WriteLine("Will you be using a client version older than 6.0.0.0?");
do
{
Console.Write("y or [n]> ");
var input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input) || input.InsensitiveStartsWith("n"))
{
Utility.PushColor(ConsoleColor.Yellow);
Console.WriteLine("Client >= 6.0.0.0 chosen.");
Utility.PopColor();
return false;
}
if (input.InsensitiveStartsWith("y"))
{
Utility.PushColor(ConsoleColor.Yellow);
Console.WriteLine("Client < 6.0.0.0 chosen.");
Utility.PopColor();
return true;
}
Console.Write("Invalid option ");
Utility.PushColor(ConsoleColor.Red);
Console.Write(input);
Utility.PopColor();
Console.WriteLine(". Press y for yes or n for no.");
} while (true);
}
internal static Expansion GetExpansion()
{
Console.WriteLine("Please choose an expansion by typing the number or short name:");
var expansions = ExpansionInfo.Table;
for (int i = 0; i < expansions.Length; i++)
{
var info = expansions[i];
Console.WriteLine(" - {0,2}: {1} ({2})", i, ((Expansion)info.ID).ToString(), info.Name);
}
var maxExpansion = (Expansion)expansions[^1].ID;
var maxExpansionName = maxExpansion.ToString();
do
{
Console.Write("[enter for {0}]> ", maxExpansionName);
var input = Console.ReadLine();
Expansion expansion;
if (string.IsNullOrWhiteSpace(input))
{
expansion = maxExpansion;
}
else if (int.TryParse(input, NumberStyles.Integer, null, out var number) &&
number >= 0 && number < expansions.Length)
{
expansion = (Expansion)number;
}
else if (!Enum.TryParse(input, out expansion))
{
Utility.PushColor(ConsoleColor.Red);
Console.Write(input);
Utility.PopColor();
Console.WriteLine(" is an invalid expansion option.");
continue;
}
Console.Write("Expansion set to ");
Utility.PushColor(ConsoleColor.Green);
Console.Write(ExpansionInfo.GetInfo(expansion).Name);
Utility.PopColor();
Console.WriteLine(".");
return expansion;
} while (true);
}
internal static List<string> GetDataDirectories()
{
Console.WriteLine("Please enter the absolute path to your ClassicUO or Ultima Online data:");
var directories = new List<string>();
do
{
Console.Write("{0}> ", directories.Count > 0 ? "[enter to finish]" : " ");
var directory = Console.ReadLine();
if (string.IsNullOrWhiteSpace(directory))
{
break;
}
if (Directory.Exists(directory))
{
directories.Add(directory);
Console.Write("Added ");
Utility.PushColor(ConsoleColor.Green);
Console.Write(directory);
Utility.PopColor();
Console.WriteLine(".");
}
else
{
Utility.PushColor(ConsoleColor.Red);
Console.Write(directory);
Utility.PopColor();
Console.WriteLine(" does not exist.");
}
} while (true);
return directories;
}
internal static List<IPEndPoint> GetListeners()
{
Console.WriteLine("Please enter the IP and ports to listen:");
Console.WriteLine(" - Only enter IP addresses directly bound to this machine");
Console.WriteLine(" - To listen to all IP addresses enter 0.0.0.0");
var ips = new List<IPEndPoint>();
do
{
// IP:Port?
Console.Write("[{0}]> ", ips.Count > 0 ? "enter to finish" : "0.0.0.0:2593");
var ipStr = Console.ReadLine();
IPEndPoint ip;
if (string.IsNullOrWhiteSpace(ipStr))
{
if (ips.Count > 0)
{
break;
}
ip = new IPEndPoint(IPAddress.Any, 2593);
}
else
{
if (!ipStr.ContainsOrdinal(':'))
{
ipStr += ":2593";
}
if (!IPEndPoint.TryParse(ipStr, out ip))
{
Utility.PushColor(ConsoleColor.Red);
Console.Write(ipStr);
Utility.PopColor();
Console.WriteLine(" is not a valid IP or port.");
continue;
}
}
ips.Add(ip);
Console.Write("Added ");
Utility.PushColor(ConsoleColor.Green);
Console.Write(ip);
Utility.PopColor();
Console.WriteLine(".");
} while (true);
return ips;
}
}

View file

@ -25,7 +25,7 @@ namespace Server
public List<string> AssemblyDirectories { get; set; } = new();
[JsonPropertyName("dataDirectories")]
public List<string> DataDirectories { get; set; } = new();
public HashSet<string> DataDirectories { get; set; } = new();
[JsonPropertyName("listeners")]
public List<IPEndPoint> Listeners { get; set; } = new();

View file

@ -70,7 +70,7 @@ namespace Server.ContextMenus
for (var i = 0; i < Entries.Length; ++i)
{
var number = Entries[i].Number;
if (number < 3000000 || number > 3032767)
if (number is < 3000000 or > 3032767)
{
return true;
}

View file

@ -64,6 +64,9 @@ namespace Server
public static event Action<Mobile> Connected;
public static void InvokeConnected(Mobile m) => Connected?.Invoke(m);
public static event Action<Mobile> BeforeDisconnected;
public static void InvokeBeforeDisconnected(Mobile m) => BeforeDisconnected?.Invoke(m);
public static event Action<Mobile> Disconnected;
public static void InvokeDisconnected(Mobile m) => Disconnected?.Invoke(m);

View file

@ -57,17 +57,17 @@ namespace Server
{
None = 0x00000000,
T2A = 0x00000001,
UOR = 0x00000002,
UOR = 0x00000002, // In later clients, the T2A/UOR flags are negative feature flags to disable body replacement of Pre-AOS graphics.
UOTD = 0x00000004,
LBR = 0x00000008,
AOS = 0x00000010,
SixthCharacterSlot = 0x00000020,
SE = 0x00000040,
ML = 0x00000080,
EigthAge = 0x00000100,
NinthAge = 0x00000200, /* Crystal/Shadow Custom House Tiles */
EighthAge = 0x00000100,
NinthAge = 0x00000200, // Crystal/Shadow Custom House Tiles
TenthAge = 0x00000400,
IncreasedStorage = 0x00000800, /* Increased Housing/Bank Storage */
IncreasedStorage = 0x00000800, // Increased Housing/Bank Storage
SeventhCharacterSlot = 0x00001000,
RoleplayFaces = 0x00002000,
TrialAccount = 0x00004000,
@ -86,7 +86,7 @@ namespace Server
ExpansionUOR = ExpansionT2A | UOR,
ExpansionUOTD = ExpansionUOR | UOTD,
ExpansionLBR = ExpansionUOTD | LBR,
ExpansionAOS = ExpansionLBR | AOS | LiveAccount,
ExpansionAOS = LBR | AOS | LiveAccount,
ExpansionSE = ExpansionAOS | SE,
ExpansionML = ExpansionSE | ML | NinthAge,
ExpansionSA = ExpansionML | SA | Gothic | Rustic,
@ -158,6 +158,12 @@ namespace Server
public class ExpansionInfo
{
public static bool ForceOldAnimations { get; private set; }
public static void Configure()
{
ForceOldAnimations = ServerConfiguration.GetSetting("expansion.forceOldAnimations", false);
}
public static string GetEraFolder(string parentDirectory)
{
var expansion = Core.Expansion;
@ -264,32 +270,6 @@ namespace Server
public ClientVersion RequiredClient { get; set; }
public HousingFlags CustomHousingFlag { get; set; }
public static FeatureFlags GetFeatures(Expansion ex)
{
var info = GetInfo(ex);
if (info != null)
{
return info.SupportedFeatures;
}
return ex switch
{
Expansion.T2A => FeatureFlags.ExpansionT2A,
Expansion.UOR => FeatureFlags.ExpansionUOR,
Expansion.UOTD => FeatureFlags.ExpansionUOTD,
Expansion.LBR => FeatureFlags.ExpansionLBR,
Expansion.AOS => FeatureFlags.ExpansionAOS,
Expansion.SE => FeatureFlags.ExpansionSE,
Expansion.ML => FeatureFlags.ExpansionML,
Expansion.SA => FeatureFlags.ExpansionSA,
Expansion.HS => FeatureFlags.ExpansionHS,
Expansion.TOL => FeatureFlags.ExpansionTOL,
Expansion.EJ => FeatureFlags.EJ,
_ => FeatureFlags.ExpansionNone
};
}
public static ExpansionInfo GetInfo(Expansion ex) => GetInfo((int)ex);
public static ExpansionInfo GetInfo(int ex)

View file

@ -0,0 +1,75 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
namespace System;
/// <summary>
/// Schedules a callback roughly every gen 2 GC (you may see a Gen 0 an Gen 1 but only once)
/// (We can fix this by capturing the Gen 2 count at startup and testing, but I mostly don't care)
/// </summary>
internal sealed class Gen2GcCallback : CriticalFinalizerObject
{
private readonly Func<object, bool> _callback;
private GCHandle _weakTargetObj;
private Gen2GcCallback(Func<object, bool> callback, object targetObj)
{
_callback = callback;
_weakTargetObj = GCHandle.Alloc(targetObj, GCHandleType.Weak);
}
/// <summary>
/// Schedule 'callback' to be called in the next GC. If the callback returns true it is
/// rescheduled for the next Gen 2 GC. Otherwise the callbacks stop.
///
/// NOTE: This callback will be kept alive until either the callback function returns false,
/// or the target object dies.
/// </summary>
public static void Register(Func<object, bool> callback, object targetObj)
{
// Create a unreachable object that remembers the callback function and target object.
new Gen2GcCallback(callback, targetObj);
}
~Gen2GcCallback()
{
if (_weakTargetObj.IsAllocated)
{
// Check to see if the target object is still alive.
object? targetObj = _weakTargetObj.Target;
if (targetObj == null)
{
// The target object is dead, so this callback object is no longer needed.
_weakTargetObj.Free();
return;
}
// Execute the callback method.
try
{
Debug.Assert(_callback != null);
if (_callback?.Invoke(targetObj) != true)
{
// If the callback returns false, this callback object is no longer needed.
_weakTargetObj.Free();
return;
}
}
catch
{
// Ensure that we still get a chance to resurrect this object, even if the callback throws an exception.
#if DEBUG
// Except in DEBUG, as we really shouldn't be hitting any exceptions here.
throw;
#endif
}
}
// Resurrect ourselves by re-registering for finalization.
GC.ReRegisterForFinalize(this);
}
}

View file

@ -47,7 +47,7 @@ namespace Server
m_Y = y;
}
public Point2D(IPoint2D p) : this(p.X, p.Y)
public Point2D(Point2D p) : this(p.X, p.Y)
{
}
@ -58,12 +58,12 @@ namespace Server
var start = value.IndexOfOrdinal('(');
var end = value.IndexOf(',', start + 1);
Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out var x);
Utility.ToInt32(value.AsSpan(start + 1, end - (start + 1)).Trim(), out var x);
start = end;
end = value.IndexOf(')', start + 1);
Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out var y);
Utility.ToInt32(value.AsSpan(start + 1, end - (start + 1)).Trim(), out var y);
return new Point2D(x, y);
}

Some files were not shown because too many files have changed in this diff Show more