Merge branch 'main' into behavior-tree
This commit is contained in:
commit
62c7acf5d3
2484 changed files with 47079 additions and 54459 deletions
|
|
@ -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>
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public class FeatureFlag<T> where T : Item
|
||||
{
|
||||
public Type Type { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<Project>
|
||||
<!-- Only here so that the default Directory.Build.props will not be used. -->
|
||||
</Project>
|
||||
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
namespace System.Runtime.CompilerServices
|
||||
{
|
||||
internal static class IsExternalInit {}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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});");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace SerializationGenerator
|
||||
{
|
||||
public record SerializableFieldSaveFlagMethods
|
||||
{
|
||||
public IMethodSymbol? DetermineFieldShouldSerialize { get; init; }
|
||||
|
||||
public IMethodSymbol? GetFieldDefaultValue { get; init; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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}}}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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}}}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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});");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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}}}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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}}}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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});");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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});");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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});");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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 }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)]"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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};");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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}}}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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",
|
||||
_ => ""
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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}{{");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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("}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
Output/
|
||||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)" />
|
||||
|
|
|
|||
72
Projects/Server.Tests/Tests/Buffers/STArrayPoolTests.cs
Normal file
72
Projects/Server.Tests/Tests/Buffers/STArrayPoolTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
Projects/Server.Tests/Tests/Collections/BitArrayTests.cs
Normal file
29
Projects/Server.Tests/Tests/Collections/BitArrayTests.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
102
Projects/Server.Tests/Tests/Collections/PooledRefQueueTests.cs
Normal file
102
Projects/Server.Tests/Tests/Collections/PooledRefQueueTests.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
37
Projects/Server.Tests/Tests/Network/PollGroupTests.cs
Normal file
37
Projects/Server.Tests/Tests/Network/PollGroupTests.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
37
Projects/Server.Tests/Tests/SerialTests.cs
Normal file
37
Projects/Server.Tests/Tests/SerialTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -71,7 +71,7 @@ namespace Server
|
|||
return 50;
|
||||
}
|
||||
|
||||
if (!(objs[0] is CallPriorityAttribute attr))
|
||||
if (objs[0] is not CallPriorityAttribute attr)
|
||||
{
|
||||
return 50;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
70
Projects/Server/Buffers/PooledArraySpanFormattable.cs
Normal file
70
Projects/Server/Buffers/PooledArraySpanFormattable.cs
Normal 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
|
||||
}
|
||||
}
|
||||
600
Projects/Server/Buffers/RawInterpolatedStringHandler.cs
Normal file
600
Projects/Server/Buffers/RawInterpolatedStringHandler.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
337
Projects/Server/Buffers/STArrayPool.cs
Normal file
337
Projects/Server/Buffers/STArrayPool.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
27
Projects/Server/Serialization/Attributes/EmbeddedSerializableAttribute.cs → Projects/Server/Buffers/ValueStringBuilderExtensions.cs
Executable file → Normal file
27
Projects/Server/Serialization/Attributes/EmbeddedSerializableAttribute.cs → Projects/Server/Buffers/ValueStringBuilderExtensions.cs
Executable file → Normal 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;
|
||||
}
|
||||
}
|
||||
262
Projects/Server/Client/ClientVersion.cs
Normal file
262
Projects/Server/Client/ClientVersion.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
135
Projects/Server/Client/UOClient.cs
Normal file
135
Projects/Server/Client/UOClient.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
1419
Projects/Server/Collections/BitArray.cs
Normal file
1419
Projects/Server/Collections/BitArray.cs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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
1154
Projects/Server/Collections/PooledRefList.cs
Normal file
1154
Projects/Server/Collections/PooledRefList.cs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
220
Projects/Server/Configuration/ServerConfigurationPrompts.cs
Normal file
220
Projects/Server/Configuration/ServerConfigurationPrompts.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
75
Projects/Server/GarbageCollection/Gen2GcCallback.cs
Normal file
75
Projects/Server/GarbageCollection/Gen2GcCallback.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue