From 6f0e1a22f971fae9f6074d71f50ceffd7e0706f2 Mon Sep 17 00:00:00 2001
From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com>
Date: Mon, 25 Jan 2021 21:06:46 -0800
Subject: [PATCH] fix(core): Core cleanup, adds event loop synchronization, and
fixes gumps (#429)
- [X] Cleans up gump packets
- [X] Adds event loop task synchronization
- [X] Moves timer pause and cleans up the delay task timer class
- [X] Cleans up the conserve cpu
- [X] Renames variables to make them consistent with the new style
- [X] Removes process delta recursion checking.
- [X] Properly diposes net states. This fixes edge cases that may cause hanging connections to stay open longer than they should.
- [X] Fixes an issue with gump items not compiling properly
Users can now utilize `Timer.Pause()` since the code will be executed on the proper thread.
```cs
public void async void Talk()
{
_canTalk = false;
await Timer.Pause(Utility.RandomMinMax(5000, 8000)); // Talk after 5-8 seconds
DoTalk();
await Timer.Pause(Utility.RandomMinMax(12000, 25000)); // Reset ability to talk after 12-25 seconds
_canTalk = true;
}
```
---
Projects/Server.Tests/Server.Tests.csproj | 2 +-
.../Tests/Buffers/ValueStringBuilderTests.cs | 13 ++
Projects/Server/Buffers/ValueStringBuilder.cs | 152 ++++++++++----
Projects/Server/EventLoopTasks.cs | 78 ++++++++
Projects/Server/Gumps/GumpItem.cs | 1 +
Projects/Server/Items/Item.cs | 90 ++++-----
Projects/Server/Main.cs | 163 +++++++--------
Projects/Server/Mobiles/Mobile.cs | 70 ++++---
Projects/Server/Network/ISocket.cs | 2 +
.../NetState/NetState.ClientVersion.cs | 8 +-
Projects/Server/Network/NetState/NetState.cs | 185 ++++++++++--------
Projects/Server/Network/NetworkSocket.cs | 38 +++-
Projects/Server/Network/NetworkState.cs | 4 +-
.../Network/Packets/IncomingAccountPackets.cs | 15 +-
.../Server/Network/Packets/IncomingPackets.cs | 2 +-
.../Network/Packets/OutgoingGumpPackets.cs | 152 +++++++-------
Projects/Server/Network/TcpServer.cs | 8 +-
...{TimerDelayCalls.cs => Timer.DelayCall.cs} | 4 +-
Projects/Server/Timer/Timer.Pause.cs | 53 +++++
Projects/Server/Timer/Timer.cs | 49 ++---
Projects/Server/Utilities/Utility.cs | 40 ++++
.../Skills/Tracking/TrackingGumpTests.cs | 33 ++++
.../UOContent.Tests/UOContent.Tests.csproj | 2 +-
Projects/UOContent/Gumps/AdminGump.cs | 48 ++---
24 files changed, 763 insertions(+), 449 deletions(-)
create mode 100644 Projects/Server/EventLoopTasks.cs
rename Projects/Server/Timer/{TimerDelayCalls.cs => Timer.DelayCall.cs} (98%)
create mode 100644 Projects/Server/Timer/Timer.Pause.cs
create mode 100644 Projects/UOContent.Tests/Tests/Skills/Tracking/TrackingGumpTests.cs
diff --git a/Projects/Server.Tests/Server.Tests.csproj b/Projects/Server.Tests/Server.Tests.csproj
index acb2da06f..af438a866 100644
--- a/Projects/Server.Tests/Server.Tests.csproj
+++ b/Projects/Server.Tests/Server.Tests.csproj
@@ -3,7 +3,7 @@
false
-
+
diff --git a/Projects/Server.Tests/Tests/Buffers/ValueStringBuilderTests.cs b/Projects/Server.Tests/Tests/Buffers/ValueStringBuilderTests.cs
index ccd9ced9e..462b318e1 100644
--- a/Projects/Server.Tests/Tests/Buffers/ValueStringBuilderTests.cs
+++ b/Projects/Server.Tests/Tests/Buffers/ValueStringBuilderTests.cs
@@ -17,5 +17,18 @@ namespace Server.Tests.Buffers
Assert.Equal(removed, sb.ToString());
}
+
+ [Theory]
+ [InlineData(8)]
+ [InlineData(9876)]
+ [InlineData(-5)]
+ [InlineData(-130984209)]
+ public void TestAppendInt32(int value)
+ {
+ using var sb = new ValueStringBuilder(stackalloc char[64]);
+ sb.Append(value);
+
+ Assert.Equal(value.ToString(), sb.ToString());
+ }
}
}
diff --git a/Projects/Server/Buffers/ValueStringBuilder.cs b/Projects/Server/Buffers/ValueStringBuilder.cs
index 45fc9f1ec..f9dc56bfa 100644
--- a/Projects/Server/Buffers/ValueStringBuilder.cs
+++ b/Projects/Server/Buffers/ValueStringBuilder.cs
@@ -3,6 +3,7 @@
using System;
using System.Buffers;
+using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Server.Network;
@@ -13,6 +14,7 @@ namespace Server.Buffers
{
private char[] _arrayToReturnToPool;
private Span _chars;
+ private int _length;
// If this ctor is used, you cannot pass in stackalloc ROS for append/replace.
public ValueStringBuilder(ReadOnlySpan initialString) : this(initialString.Length)
@@ -29,7 +31,7 @@ namespace Server.Buffers
{
_arrayToReturnToPool = null;
_chars = initialBuffer;
- Length = 0;
+ _length = 0;
}
// If this ctor is used, you cannot pass in stackalloc ROS for append/replace.
@@ -37,13 +39,20 @@ namespace Server.Buffers
{
_arrayToReturnToPool = ArrayPool.Shared.Rent(initialCapacity);
_chars = _arrayToReturnToPool;
- Length = 0;
+ _length = 0;
}
- public int Length { get; set; }
+ public int Length => _length;
public int Capacity => _chars.Length;
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Reset()
+ {
+ _length = 0;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void EnsureCapacity(int capacity)
{
if (capacity > _chars.Length)
@@ -68,15 +77,16 @@ namespace Server.Buffers
{
if (terminate)
{
- EnsureCapacity(Length + 1);
- _chars[Length] = '\0';
+ EnsureCapacity(_length + 1);
+ _chars[_length] = '\0';
}
return ref MemoryMarshal.GetReference(_chars);
}
public ref char this[int index] => ref _chars[index];
- public override string ToString() => _chars.SliceToLength(Length).ToString();
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override string ToString() => _chars.SliceToLength(_length).ToString();
/// Returns the underlying storage of the builder.
public Span RawChars => _chars;
@@ -89,21 +99,27 @@ namespace Server.Buffers
{
if (terminate)
{
- EnsureCapacity(Length + 1);
- _chars[Length] = '\0';
+ EnsureCapacity(_length + 1);
+ _chars[_length] = '\0';
}
- return _chars.SliceToLength(Length);
+ return _chars.SliceToLength(_length);
}
- public ReadOnlySpan AsSpan() => _chars.SliceToLength(Length);
- public ReadOnlySpan AsSpan(int start) => _chars.Slice(start, Length - start);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ReadOnlySpan AsSpan() => _chars.SliceToLength(_length);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ReadOnlySpan AsSpan(int start) => _chars.Slice(start, _length - start);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan AsSpan(int start, int length) => _chars.Slice(start, length);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryCopyTo(Span destination, out int charsWritten)
{
- if (_chars.SliceToLength(Length).TryCopyTo(destination))
+ if (_chars.SliceToLength(_length).TryCopyTo(destination))
{
- charsWritten = Length;
+ charsWritten = _length;
return true;
}
@@ -111,19 +127,21 @@ namespace Server.Buffers
return false;
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Insert(int index, char value, int count)
{
- if (Length > _chars.Length - count)
+ if (_length > _chars.Length - count)
{
Grow(count);
}
- int remaining = Length - index;
+ int remaining = _length - index;
_chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count));
_chars.Slice(index, count).Fill(value);
- Length += count;
+ _length += count;
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Insert(int index, string s)
{
if (s == null)
@@ -133,25 +151,25 @@ namespace Server.Buffers
int count = s.Length;
- if (Length > _chars.Length - count)
+ if (_length > _chars.Length - count)
{
Grow(count);
}
- int remaining = Length - index;
+ int remaining = _length - index;
_chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count));
s.AsSpan().CopyTo(_chars.Slice(index));
- Length += count;
+ _length += count;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Append(char c)
{
- int pos = Length;
+ int pos = _length;
if ((uint)pos < (uint)_chars.Length)
{
_chars[pos] = c;
- Length = pos + 1;
+ _length = pos + 1;
}
else
{
@@ -159,6 +177,50 @@ namespace Server.Buffers
}
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Append(int value, NumberFormatInfo info = null)
+ {
+ if (value >= 0)
+ {
+ Append((uint)value);
+ return;
+ }
+
+ Append((info ?? NumberFormatInfo.CurrentInfo).NegativeSign);
+ Append((uint)-value);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void Append(uint value)
+ {
+ int bufferLength = Utility.CountDigits(value);
+
+ int pos = _length;
+ if ((uint)pos + (uint)bufferLength >= _chars.Length)
+ {
+ Grow(bufferLength);
+ }
+
+ if (bufferLength == 1)
+ {
+ _chars[pos] = (char)(value + '0');
+ _length = pos + 1;
+ return;
+ }
+
+ fixed (char* buffer = _chars.Slice(pos))
+ {
+ char* p = buffer + bufferLength;
+ do
+ {
+ value = Utility.DivRem(value, 10, out uint remainder);
+ *--p = (char)(remainder + '0');
+ } while (value != 0);
+ }
+
+ _length = pos + bufferLength;
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Append(string s)
{
@@ -167,11 +229,11 @@ namespace Server.Buffers
return;
}
- int pos = Length;
+ int pos = _length;
if (s.Length == 1 && (uint)pos < (uint)_chars.Length) // very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc.
{
_chars[pos] = s[0];
- Length = pos + 1;
+ _length = pos + 1;
}
else
{
@@ -200,71 +262,75 @@ namespace Server.Buffers
Append(Environment.NewLine);
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void AppendSlow(string s)
{
- int pos = Length;
+ int pos = _length;
if (pos > _chars.Length - s.Length)
{
Grow(s.Length);
}
s.AsSpan().CopyTo(_chars.Slice(pos));
- Length += s.Length;
+ _length += s.Length;
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Append(char c, int count)
{
- if (Length > _chars.Length - count)
+ if (_length > _chars.Length - count)
{
Grow(count);
}
- Span dst = _chars.Slice(Length, count);
+ Span dst = _chars.Slice(_length, count);
for (int i = 0; i < dst.Length; i++)
{
dst[i] = c;
}
- Length += count;
+ _length += count;
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void Append(char* value, int length)
{
- int pos = Length;
+ int pos = _length;
if (pos > _chars.Length - length)
{
Grow(length);
}
- Span dst = _chars.Slice(Length, length);
+ Span dst = _chars.Slice(_length, length);
for (int i = 0; i < dst.Length; i++)
{
dst[i] = *value++;
}
- Length += length;
+ _length += length;
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Append(ReadOnlySpan value)
{
- int pos = Length;
+ int pos = _length;
if (pos > _chars.Length - value.Length)
{
Grow(value.Length);
}
- value.CopyTo(_chars.Slice(Length));
- Length += value.Length;
+ value.CopyTo(_chars.Slice(_length));
+ _length += value.Length;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span AppendSpan(int length)
{
- int origPos = Length;
+ int origPos = _length;
if (origPos > _chars.Length - length)
{
Grow(length);
}
- Length = origPos + length;
+ _length = origPos + length;
return _chars.Slice(origPos, length);
}
@@ -287,9 +353,9 @@ namespace Server.Buffers
[MethodImpl(MethodImplOptions.NoInlining)]
private void Grow(int additionalCapacityBeyondPos)
{
- char[] poolArray = ArrayPool.Shared.Rent(Math.Max(Length + additionalCapacityBeyondPos, _chars.Length * 2));
+ char[] poolArray = ArrayPool.Shared.Rent(Math.Max(_length + additionalCapacityBeyondPos, _chars.Length * 2));
- _chars.SliceToLength(Length).CopyTo(poolArray);
+ _chars.SliceToLength(_length).CopyTo(poolArray);
char[] toReturn = _arrayToReturnToPool;
_chars = _arrayToReturnToPool = poolArray;
@@ -314,7 +380,7 @@ namespace Server.Buffers
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReplaceAny(ReadOnlySpan oldChars, ReadOnlySpan newChars, int startIndex, int count)
{
- int currentLength = Length;
+ int currentLength = _length;
if ((uint)startIndex > (uint)currentLength)
{
throw new ArgumentOutOfRangeException(nameof(startIndex));
@@ -345,7 +411,7 @@ namespace Server.Buffers
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Replace(char oldChar, char newChar, int startIndex, int count)
{
- int currentLength = Length;
+ int currentLength = _length;
if ((uint)startIndex > (uint)currentLength)
{
throw new ArgumentOutOfRangeException(nameof(startIndex));
@@ -384,7 +450,7 @@ namespace Server.Buffers
throw new ArgumentOutOfRangeException(nameof(startIndex));
}
- if (length > Length - startIndex)
+ if (length > _length - startIndex)
{
throw new ArgumentOutOfRangeException(nameof(length));
}
@@ -393,7 +459,7 @@ namespace Server.Buffers
{
_chars = _chars.Slice(length);
}
- else if (startIndex + length == Length)
+ else if (startIndex + length == _length)
{
_chars = _chars.SliceToLength(startIndex);
}
@@ -403,7 +469,7 @@ namespace Server.Buffers
_chars.Slice(startIndex + length).CopyTo(_chars.Slice(startIndex));
}
- Length -= length;
+ _length -= length;
}
}
}
diff --git a/Projects/Server/EventLoopTasks.cs b/Projects/Server/EventLoopTasks.cs
new file mode 100644
index 000000000..a96d7aa53
--- /dev/null
+++ b/Projects/Server/EventLoopTasks.cs
@@ -0,0 +1,78 @@
+/*************************************************************************
+ * ModernUO *
+ * Copyright (C) 2019-2021 - ModernUO Development Team *
+ * Email: hi@modernuo.com *
+ * File: EventLoopTasks.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 . *
+ *************************************************************************/
+
+using System;
+using System.Collections.Concurrent;
+using System.Threading;
+
+namespace Server
+{
+ public sealed class EventLoopContext : SynchronizationContext
+ {
+ private readonly ConcurrentQueue _queue;
+ private readonly Thread _mainThread;
+
+ public EventLoopContext()
+ {
+ _queue = new ConcurrentQueue();
+ _mainThread = Thread.CurrentThread;
+ }
+
+ public override SynchronizationContext CreateCopy() => new EventLoopContext();
+
+ public override void Post(SendOrPostCallback d, object state) => _queue.Enqueue(() => d(state));
+
+ public override void Send(SendOrPostCallback d, object state)
+ {
+ if (Thread.CurrentThread == _mainThread)
+ {
+ d(state);
+ return;
+ }
+
+ AutoResetEvent evt = new AutoResetEvent(false);
+
+ _queue.Enqueue(() =>
+ {
+ d(state);
+ evt.Set();
+ });
+
+ evt.WaitOne();
+ }
+
+ public int ExecuteTasks()
+ {
+ if (Thread.CurrentThread != _mainThread)
+ {
+ throw new Exception("Called EventLoop.ExecuteTasks on incorrect thread!");
+ }
+
+ var count = _queue.Count;
+
+ for (int i = 0; i < count; i++)
+ {
+ if (!_queue.TryDequeue(out var a))
+ {
+ return count;
+ }
+
+ a();
+ }
+
+ return count;
+ }
+ }
+}
diff --git a/Projects/Server/Gumps/GumpItem.cs b/Projects/Server/Gumps/GumpItem.cs
index ff3389b27..4562f6e43 100644
--- a/Projects/Server/Gumps/GumpItem.cs
+++ b/Projects/Server/Gumps/GumpItem.cs
@@ -44,6 +44,7 @@ namespace Server.Gumps
public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches)
{
+ writer.Write((ushort)0x7B20); // "{ "
writer.Write(Hue == 0 ? LayoutName : LayoutNameHue);
writer.WriteAscii(' ');
writer.WriteAscii(X.ToString());
diff --git a/Projects/Server/Items/Item.cs b/Projects/Server/Items/Item.cs
index 96022c548..78fdb1a68 100644
--- a/Projects/Server/Items/Item.cs
+++ b/Projects/Server/Items/Item.cs
@@ -195,10 +195,7 @@ namespace Server
{
public const int QuestItemHue = 0x4EA; // Hmmmm... "for EA"?
public static readonly List- EmptyItems = new();
-
- private static readonly List
- m_DeltaQueue = new();
-
- private static bool _processing;
+ private static readonly Queue
- m_DeltaQueue = new();
private static int m_OpenSlots;
private int m_Amount;
@@ -2498,6 +2495,7 @@ namespace Server
}
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool GetSaveFlag(SaveFlag flags, SaveFlag toGet) => (flags & toGet) != 0;
public IPooledEnumerable GetObjectsInRange(int range)
@@ -3243,69 +3241,55 @@ namespace Server
if (!GetFlag(ImplFlag.InQueue))
{
SetFlag(ImplFlag.InQueue, true);
-
- if (_processing)
- {
- try
- {
- using var op = new StreamWriter("delta-recursion.log", true);
- op.WriteLine("# {0}", DateTime.UtcNow);
- op.WriteLine(new StackTrace());
- op.WriteLine();
- }
- catch
- {
- // ignored
- }
- }
- else
- {
- m_DeltaQueue.Add(this);
- }
+ m_DeltaQueue.Enqueue(this);
}
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RemDelta(ItemDelta flags)
{
m_DeltaFlags &= ~flags;
-
- if (GetFlag(ImplFlag.InQueue) && m_DeltaFlags == ItemDelta.None)
- {
- SetFlag(ImplFlag.InQueue, false);
-
- if (_processing)
- {
- try
- {
- using var op = new StreamWriter("delta-recursion.log", true);
- op.WriteLine("# {0}", DateTime.UtcNow);
- op.WriteLine(new StackTrace());
- op.WriteLine();
- }
- catch
- {
- // ignored
- }
- }
- else
- {
- m_DeltaQueue.Remove(this);
- }
- }
}
- public static void ProcessDeltaQueue()
+ public static int ProcessDeltaQueue()
{
- _processing = true;
+ int count = 0;
- for (var i = 0; i < m_DeltaQueue.Count; i++)
+ var limit = m_DeltaQueue.Count;
+
+ while (m_DeltaQueue.Count > 0 && --limit >= 0)
{
- m_DeltaQueue[i].ProcessDelta();
+ var item = m_DeltaQueue.Dequeue();
+
+ if (item == null)
+ {
+ continue;
+ }
+
+ count++;
+
+ item.SetFlag(ImplFlag.InQueue, false);
+
+ try
+ {
+ item.ProcessDelta();
+ }
+ catch (Exception ex)
+ {
+#if DEBUG
+ Console.WriteLine("Process Delta Queue for {0} failed: {1}", item, ex);
+#endif
+ }
}
- m_DeltaQueue.Clear();
+ if (m_DeltaQueue.Count > 0)
+ {
+ Utility.PushColor(ConsoleColor.DarkYellow);
+ Console.WriteLine("Warning: {0} items left in delta queue after processing.", m_DeltaQueue.Count);
+ Utility.PopColor();
+ }
- _processing = false;
+ return count;
}
public virtual void OnDelete()
diff --git a/Projects/Server/Main.cs b/Projects/Server/Main.cs
index 90c56339e..ae35e91dd 100644
--- a/Projects/Server/Main.cs
+++ b/Projects/Server/Main.cs
@@ -19,6 +19,7 @@ using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
@@ -30,26 +31,22 @@ namespace Server
{
public static class Core
{
- private static bool m_Crashed;
- private static Thread timerThread;
- private static string m_BaseDirectory;
+ private static bool _crashed;
+ private static Thread _timerThread;
+ private static string _baseDirectory;
- private static bool m_Profiling;
- private static DateTime m_ProfileStart;
- private static TimeSpan m_ProfileTime;
+ private static bool _profiling;
+ private static DateTime _profileStart;
+ private static TimeSpan _profileTime;
#nullable enable
private static bool? _isRunningFromXUnit;
#nullable disable
- private static long m_CycleIndex;
- private static readonly float[] m_CyclesPerSecond = new float[127]; // Divisible by long.MaxValue
+ private static int _itemCount;
+ private static int _mobileCount;
+ private static EventLoopContext _eventLoopContext;
- // private static readonly AutoResetEvent m_Signal = new AutoResetEvent(true);
-
- private static int m_ItemCount;
- private static int m_MobileCount;
-
- private static readonly Type[] m_SerialTypeArray = { typeof(Serial) };
+ private static readonly Type[] _serialTypeArray = { typeof(Serial) };
public static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
public static readonly bool IsDarwin = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
@@ -57,7 +54,7 @@ namespace Server
public static readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || IsFreeBSD;
public static readonly bool Unix = IsDarwin || IsFreeBSD || IsLinux;
- private const string assembliesConfiguration = "Data/assemblies.json";
+ private const string AssembliesConfiguration = "Data/assemblies.json";
#nullable enable
// TODO: Find a way to get rid of this
@@ -87,29 +84,29 @@ namespace Server
public static bool Profiling
{
- get => m_Profiling;
+ get => _profiling;
set
{
- if (m_Profiling == value)
+ if (_profiling == value)
{
return;
}
- m_Profiling = value;
+ _profiling = value;
- if (m_ProfileStart > DateTime.MinValue)
+ if (_profileStart > DateTime.MinValue)
{
- m_ProfileTime += DateTime.UtcNow - m_ProfileStart;
+ _profileTime += DateTime.UtcNow - _profileStart;
}
- m_ProfileStart = m_Profiling ? DateTime.UtcNow : DateTime.MinValue;
+ _profileStart = _profiling ? DateTime.UtcNow : DateTime.MinValue;
}
}
public static TimeSpan ProfileTime =>
- m_ProfileStart > DateTime.MinValue
- ? m_ProfileTime + (DateTime.UtcNow - m_ProfileStart)
- : m_ProfileTime;
+ _profileStart > DateTime.MinValue
+ ? _profileTime + (DateTime.UtcNow - _profileStart)
+ : _profileTime;
public static Assembly Assembly { get; set; }
@@ -120,8 +117,16 @@ namespace Server
public static Thread Thread { get; private set; }
- // Milliseconds
- public static long TickCount => Stopwatch.GetTimestamp() * 1000L / Stopwatch.Frequency;
+ [ThreadStatic] private static long _tickCount;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static long GetTicks() => 1000L * Stopwatch.GetTimestamp() / Stopwatch.Frequency;
+
+ public static long TickCount
+ {
+ get => _tickCount == 0 ? GetTicks() : _tickCount;
+ set => _tickCount = value;
+ }
public static bool MultiProcessor { get; private set; }
@@ -131,24 +136,24 @@ namespace Server
{
get
{
- if (m_BaseDirectory == null)
+ if (_baseDirectory == null)
{
try
{
- m_BaseDirectory = Assembly.Location;
+ _baseDirectory = Assembly.Location;
- if (m_BaseDirectory.Length > 0)
+ if (_baseDirectory.Length > 0)
{
- m_BaseDirectory = Path.GetDirectoryName(m_BaseDirectory);
+ _baseDirectory = Path.GetDirectoryName(_baseDirectory);
}
}
catch
{
- m_BaseDirectory = "";
+ _baseDirectory = "";
}
}
- return m_BaseDirectory;
+ return _baseDirectory;
}
}
@@ -156,33 +161,13 @@ namespace Server
public static bool Closing => ClosingTokenSource.IsCancellationRequested;
- public static float CyclesPerSecond => m_CyclesPerSecond[m_CycleIndex % m_CyclesPerSecond.Length];
-
- public static float AverageCPS
- {
- get
- {
- var total = 0.0f;
- var count = Math.Min(m_CycleIndex + 1, m_CyclesPerSecond.Length);
-
- for (int i = 0; i < count; i++)
- {
- total += m_CyclesPerSecond[i];
- }
-
- return total / count;
- }
- }
-
- public static bool ConserveCPU { get; private set; }
-
public static string Arguments
{
get
{
var sb = new StringBuilder();
- if (m_Profiling)
+ if (_profiling)
{
Utility.Separate(sb, "-profile", " ");
}
@@ -195,8 +180,8 @@ namespace Server
public static int GlobalMaxUpdateRange { get; set; } = 24;
- public static int ScriptItems => m_ItemCount;
- public static int ScriptMobiles => m_MobileCount;
+ public static int ScriptItems => _itemCount;
+ public static int ScriptMobiles => _mobileCount;
public static Expansion Expansion { get; set; }
@@ -222,11 +207,6 @@ namespace Server
public static bool EJ => Expansion >= Expansion.EJ;
- public static void Configure()
- {
- ConserveCPU = ServerConfiguration.GetOrUpdateSetting("core.conserveCpu", true);
- }
-
public static string FindDataFile(string path, bool throwNotFound = true, bool warnNotFound = false)
{
string fullPath = null;
@@ -265,7 +245,7 @@ namespace Server
if (e.IsTerminating)
{
- m_Crashed = true;
+ _crashed = true;
var close = false;
@@ -355,7 +335,7 @@ namespace Server
World.WaitForWriteCompletion();
- if (!m_Crashed)
+ if (!_crashed)
{
EventSink.InvokeShutdown();
}
@@ -370,6 +350,10 @@ namespace Server
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit;
+ _eventLoopContext = new EventLoopContext();
+
+ SynchronizationContext.SetSynchronizationContext(_eventLoopContext);
+
foreach (var a in args)
{
if (a.InsensitiveEquals("-profile"))
@@ -420,7 +404,7 @@ namespace Server
Console.WriteLine("Core: Running on {0}", RuntimeInformation.FrameworkDescription);
var ttObj = new Timer.TimerThread();
- timerThread = new Thread(ttObj.TimerMain)
+ _timerThread = new Thread(ttObj.TimerMain)
{
Name = "Timer Thread"
};
@@ -458,7 +442,7 @@ namespace Server
ServerConfiguration.Load();
- var assemblyPath = Path.Join(BaseDirectory, assembliesConfiguration);
+ var assemblyPath = Path.Join(BaseDirectory, AssembliesConfiguration);
// Load UOContent.dll
var assemblyFiles = JsonConfig.Deserialize
>(assemblyPath).ToArray();
@@ -480,7 +464,7 @@ namespace Server
AssemblyHandler.Invoke("Initialize");
- timerThread.Start();
+ _timerThread.Start();
TcpServer.Start();
EventSink.InvokeServerStarted();
@@ -491,39 +475,34 @@ namespace Server
{
try
{
- long last = TickCount;
-
- const int sampleInterval = 100;
- const float ticksPerSecond = 1000.0f * sampleInterval;
-
- long sample = 0;
+ const int interval = 100;
+ int idleCount = 0;
while (!Closing)
{
- Mobile.ProcessDeltaQueue();
- Item.ProcessDeltaQueue();
+ _tickCount = TickCount;
- Timer.Slice();
+ var events = Mobile.ProcessDeltaQueue();
+ events += Item.ProcessDeltaQueue();
+ events += Timer.Slice();
// Handle networking
- TcpServer.Slice();
- NetState.HandleAllReceives();
- NetState.FlushAll();
- NetState.ProcessDisposedQueue();
+ events += TcpServer.Slice();
+ events += NetState.HandleAllReceives();
+ events += NetState.FlushAll();
- // Execute other stuff
- if (sample++ % sampleInterval != 0)
+ // Execute captured post-await methods (like Timer.Pause)
+ events += _eventLoopContext.ExecuteTasks();
+
+ _tickCount = 0;
+
+ if (events > 0)
{
+ idleCount = 0;
continue;
}
- var now = TickCount;
- var cyclesPerSecond = ticksPerSecond / (now - last);
- m_CyclesPerSecond[m_CycleIndex % m_CyclesPerSecond.Length] = cyclesPerSecond;
- m_CycleIndex = Math.Max(unchecked(m_CycleIndex + 1), 0);
- last = now;
-
- if (ConserveCPU && cyclesPerSecond >= 100)
+ if (++idleCount > interval)
{
Thread.Sleep(1);
}
@@ -537,8 +516,8 @@ namespace Server
public static void VerifySerialization()
{
- m_ItemCount = 0;
- m_MobileCount = 0;
+ _itemCount = 0;
+ _mobileCount = 0;
var callingAssembly = Assembly.GetCallingAssembly();
@@ -564,18 +543,18 @@ namespace Server
if (isItem)
{
- Interlocked.Increment(ref m_ItemCount);
+ Interlocked.Increment(ref _itemCount);
}
else
{
- Interlocked.Increment(ref m_MobileCount);
+ Interlocked.Increment(ref _mobileCount);
}
StringBuilder warningSb = null;
try
{
- if (type.GetConstructor(m_SerialTypeArray) == null)
+ if (type.GetConstructor(_serialTypeArray) == null)
{
warningSb = new StringBuilder();
warningSb.AppendLine(" - No serialization constructor");
diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs
index ed38663eb..438512eb8 100644
--- a/Projects/Server/Mobiles/Mobile.cs
+++ b/Projects/Server/Mobiles/Mobile.cs
@@ -471,9 +471,6 @@ namespace Server
};
private static readonly Queue m_DeltaQueue = new();
- private static readonly Queue m_DeltaQueueR = new();
-
- private static bool _processing;
private static readonly string[] m_GuildTypes =
{
@@ -7957,50 +7954,49 @@ namespace Server
if (!m_InDeltaQueue)
{
m_InDeltaQueue = true;
-
- if (_processing)
- {
- lock (m_DeltaQueueR)
- {
- m_DeltaQueueR.Enqueue(this);
-
- try
- {
- using (var op = new StreamWriter("delta-recursion.log", true))
- {
- op.WriteLine("# {0}", DateTime.UtcNow);
- op.WriteLine(new StackTrace());
- op.WriteLine();
- }
- }
- catch
- {
- // ignored
- }
- }
- }
- else
- {
- m_DeltaQueue.Enqueue(this);
- }
+ m_DeltaQueue.Enqueue(this);
}
}
- public static void ProcessDeltaQueue()
+ public static int ProcessDeltaQueue()
{
- _processing = true;
+ int count = 0;
- while (m_DeltaQueue.TryDequeue(out var m))
+ var limit = m_DeltaQueue.Count;
+
+ while (m_DeltaQueue.Count > 0 && --limit >= 0)
{
- m.ProcessDelta();
+ var mob = m_DeltaQueue.Dequeue();
+
+ if (mob == null)
+ {
+ continue;
+ }
+
+ count++;
+
+ mob.m_InDeltaQueue = false;
+
+ try
+ {
+ mob.ProcessDelta();
+ }
+ catch (Exception ex)
+ {
+#if DEBUG
+ Console.WriteLine("Process Delta Queue for {0} failed: {1}", mob, ex);
+#endif
+ }
}
- _processing = false;
-
- while (m_DeltaQueueR.TryDequeue(out var m))
+ if (m_DeltaQueue.Count > 0)
{
- m.ProcessDelta();
+ Utility.PushColor(ConsoleColor.DarkYellow);
+ Console.WriteLine("Warning: {0} mobiles left in delta queue after processing.", m_DeltaQueue.Count);
+ Utility.PopColor();
}
+
+ return count;
}
public virtual void OnKillsChange(int oldValue)
diff --git a/Projects/Server/Network/ISocket.cs b/Projects/Server/Network/ISocket.cs
index c8a459a6b..866e546c6 100644
--- a/Projects/Server/Network/ISocket.cs
+++ b/Projects/Server/Network/ISocket.cs
@@ -32,5 +32,7 @@ namespace Server.Network
public Task ReceiveAsync(IList> buffer, SocketFlags flags);
public void Shutdown(SocketShutdown how);
+
+ public void Close();
}
}
diff --git a/Projects/Server/Network/NetState/NetState.ClientVersion.cs b/Projects/Server/Network/NetState/NetState.ClientVersion.cs
index b53f924a7..090accb3f 100644
--- a/Projects/Server/Network/NetState/NetState.ClientVersion.cs
+++ b/Projects/Server/Network/NetState/NetState.ClientVersion.cs
@@ -41,8 +41,8 @@ namespace Server.Network
public ClientVersion Version
{
- get => m_Version;
- set => ProtocolChanges = ProtocolChangesByVersion(m_Version = value);
+ get => _version;
+ set => ProtocolChanges = ProtocolChangesByVersion(_version = value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -87,9 +87,9 @@ namespace Server.Network
public bool NewSecureTrading => HasProtocolChanges(ProtocolChanges.NewSecureTrading);
public bool IsUOTDClient =>
- (Flags & ClientFlags.UOTD) != 0 || m_Version?.Type == ClientType.UOTD;
+ (Flags & ClientFlags.UOTD) != 0 || _version?.Type == ClientType.UOTD;
- public bool IsSAClient => m_Version?.Type == ClientType.SA;
+ public bool IsSAClient => _version?.Type == ClientType.SA;
private ExpansionInfo m_Expansion;
diff --git a/Projects/Server/Network/NetState/NetState.cs b/Projects/Server/Network/NetState/NetState.cs
index 9d1bd0cd7..ec6ad7c76 100644
--- a/Projects/Server/Network/NetState/NetState.cs
+++ b/Projects/Server/Network/NetState/NetState.cs
@@ -43,23 +43,25 @@ namespace Server.Network
private static int HuePickerCap = 512;
private static int MenuCap = 512;
- private static readonly ConcurrentQueue m_Disposed = new();
- private static NetworkState m_NetworkState = NetworkState.ResumeState;
+ private static readonly Queue FlushPending = new(2048);
+ private static readonly ConcurrentQueue Disposed = new();
+ private static NetworkState NetworkState = NetworkState.ResumeState;
public static NetStateCreatedCallback CreatedCallback { get; set; }
- private readonly string m_ToString;
- private int m_Disposing;
- private ClientVersion m_Version;
+ private readonly string _toString;
+ private int _disposing;
+ private ClientVersion _version;
private byte[] _recvBuffer;
private byte[] _sendBuffer;
- private long m_NextCheckActivity;
- private volatile bool m_Running;
+ private long _nextActivityCheck;
+ private volatile bool _running;
private volatile DecodePacket _packetDecoder;
private volatile EncodePacket _packetEncoder;
+ private bool _flushQueued = false;
- internal int m_AuthID;
- internal int m_Seed;
+ internal int _authId;
+ internal int _seed;
public static void Configure()
{
@@ -78,7 +80,7 @@ namespace Server.Network
public NetState(ISocket connection)
{
- m_Running = false;
+ _running = false;
Connection = connection;
Seeded = false;
Gumps = new List();
@@ -89,18 +91,18 @@ namespace Server.Network
RecvPipe = new Pipe(_recvBuffer);
_sendBuffer = GC.AllocateUninitializedArray(SendPipeSize);
SendPipe = new Pipe(_sendBuffer);
- m_NextCheckActivity = Core.TickCount + 30000;
+ _nextActivityCheck = Core.TickCount + 30000;
try
{
Address = Utility.Intern((Connection?.RemoteEndPoint as IPEndPoint)?.Address);
- m_ToString = Address?.ToString() ?? "(error)";
+ _toString = Address?.ToString() ?? "(error)";
}
catch (Exception ex)
{
TraceException(ex);
Address = IPAddress.None;
- m_ToString = "(error)";
+ _toString = "(error)";
}
ConnectedOn = DateTime.UtcNow;
@@ -134,7 +136,7 @@ namespace Server.Network
public List Trades { get; }
- public bool Running => m_Running;
+ public bool Running => _running;
public bool Seeded { get; set; }
@@ -162,9 +164,9 @@ namespace Server.Network
public IAccount Account { get; set; }
- public bool IsDisposing => m_Disposing != 0;
+ public bool IsDisposing => _disposing != 0;
- public int CompareTo(NetState other) => string.CompareOrdinal(m_ToString, other?.m_ToString);
+ public int CompareTo(NetState other) => string.CompareOrdinal(_toString, other?._toString);
public void ValidateAllTrades()
{
@@ -358,16 +360,16 @@ namespace Server.Network
this.SendLaunchBrowser(url);
}
- public override string ToString() => m_ToString;
+ public override string ToString() => _toString;
public static void Pause()
{
- NetworkState.Pause(ref m_NetworkState);
+ NetworkState.Pause(ref NetworkState);
}
public static void Resume()
{
- NetworkState.Resume(ref m_NetworkState);
+ NetworkState.Resume(ref NetworkState);
}
public bool GetSendBuffer(out CircularBuffer cBuffer)
@@ -403,6 +405,12 @@ namespace Server.Network
}
SendPipe.Writer.Advance((uint)length);
+
+ if (!_flushQueued)
+ {
+ FlushPending.Enqueue(this);
+ _flushQueued = true;
+ }
}
catch (Exception ex)
{
@@ -442,7 +450,11 @@ namespace Server.Network
result.CopyFrom(buffer.AsSpan(0, length));
writer.Advance((uint)length);
- // Flush at the end of the game loop
+ if (!_flushQueued)
+ {
+ FlushPending.Enqueue(this);
+ _flushQueued = true;
+ }
}
else
{
@@ -471,12 +483,12 @@ namespace Server.Network
internal void Start()
{
- if (Connection == null || m_Running)
+ if (Connection == null || _running)
{
return;
}
- m_Running = true;
+ _running = true;
ThreadPool.UnsafeQueueUserWorkItem(RecvTask, null);
ThreadPool.UnsafeQueueUserWorkItem(SendTask, null);
@@ -488,7 +500,7 @@ namespace Server.Network
try
{
- while (m_Running)
+ while (_running)
{
var result = await reader.Read();
if (result.IsClosed)
@@ -505,7 +517,7 @@ namespace Server.Network
if (bytesWritten > 0)
{
- m_NextCheckActivity = Core.TickCount + 90000;
+ _nextActivityCheck = Core.TickCount + 90000;
reader.Advance((uint)bytesWritten);
}
}
@@ -536,9 +548,9 @@ namespace Server.Network
try
{
- while (m_Running)
+ while (_running)
{
- if (m_NetworkState == NetworkState.PauseState)
+ if (NetworkState == NetworkState.PauseState)
{
continue;
}
@@ -564,7 +576,7 @@ namespace Server.Network
DecodePacket(result.Buffer, ref bytesWritten);
writer.Advance((uint)bytesWritten);
- m_NextCheckActivity = Core.TickCount + 90000;
+ _nextActivityCheck = Core.TickCount + 90000;
// No need to flush
}
@@ -582,19 +594,26 @@ namespace Server.Network
}
}
- public static void HandleAllReceives()
+ public static int HandleAllReceives()
{
+ int count = 0;
+
foreach (var ns in TcpServer.Instances)
{
- ns.HandleReceive();
+ if (ns.HandleReceive())
+ {
+ count++;
+ }
}
+
+ return count;
}
- public void HandleReceive()
+ public bool HandleReceive()
{
if (Connection == null)
{
- return;
+ return false;
}
try
@@ -608,7 +627,7 @@ namespace Server.Network
if (result.IsClosed || result.Length <= 0)
{
- return;
+ return false;
}
var bytesProcessed = this.ProcessPacket(result.Buffer);
@@ -620,9 +639,10 @@ namespace Server.Network
if (bytesProcessed < 0)
{
Dispose();
+ return false;
}
- return;
+ return true;
}
reader.Advance((uint)bytesProcessed);
@@ -635,6 +655,7 @@ namespace Server.Network
TraceException(ex);
#endif
Dispose();
+ return false;
}
}
@@ -644,19 +665,32 @@ namespace Server.Network
{
SendPipe.Writer.Flush();
}
+
+ _flushQueued = false;
}
- public static void FlushAll()
+ public static int FlushAll()
{
- foreach (var ns in TcpServer.Instances)
+ int count = 0;
+ while (FlushPending.TryDequeue(out var ns))
{
ns.Flush();
+ count++;
}
+
+ while (Disposed.TryDequeue(out var ns))
+ {
+ ns.Disconnect();
+ TcpServer.Instances.Remove(ns);
+ count++;
+ }
+
+ return count;
}
public void CheckAlive(long curTicks)
{
- if (Connection != null && m_NextCheckActivity - curTicks < 0)
+ if (Connection != null && _nextActivityCheck - curTicks < 0)
{
WriteConsole("Disconnecting due to inactivity...");
Dispose();
@@ -717,7 +751,7 @@ namespace Server.Network
public virtual void Dispose()
{
- if (Connection == null || Interlocked.Exchange(ref m_Disposing, 1) != 0)
+ if (Connection == null || Interlocked.CompareExchange(ref _disposing, 1, 0) == 1)
{
return;
}
@@ -728,59 +762,56 @@ namespace Server.Network
{
Connection.Shutdown(SocketShutdown.Both);
}
- catch (Exception ex)
+ catch (SocketException ex)
{
TraceException(ex);
}
- finally
+
+ try
{
- m_Disposed.Enqueue(this);
+ Connection.Close();
}
+ catch (SocketException ex)
+ {
+ TraceException(ex);
+ }
+
+ Disposed.Enqueue(this);
}
- public static void ProcessDisposedQueue()
+ private void Disconnect()
{
- var breakout = 0;
+ _running = false;
+ Connection = null;
- while (breakout++ < 200)
+ RecvPipe.Writer.Flush();
+ RecvPipe.Writer.Close();
+
+ var m = Mobile;
+ var a = Account;
+
+ if (m != null)
{
- if (!m_Disposed.TryDequeue(out var ns))
- {
- break;
- }
+ m.NetState = null;
+ Mobile = null;
+ }
- var m = ns.Mobile;
- var a = ns.Account;
+ Gumps.Clear();
+ Menus.Clear();
+ HuePickers.Clear();
+ Account = null;
+ ServerInfo = null;
+ CityInfo = null;
- if (m != null)
- {
- m.NetState = null;
- ns.Mobile = null;
- }
+ var count = TcpServer.Instances.Count;
- ns.m_Running = false;
- ns.Connection = null;
- ns._recvBuffer = null;
- ns.RecvPipe = null;
- ns._sendBuffer = null;
- ns.SendPipe = null;
- ns.Gumps.Clear();
- ns.Menus.Clear();
- ns.HuePickers.Clear();
- ns.Account = null;
- ns.ServerInfo = null;
- ns.CityInfo = null;
-
- TcpServer.Instances.Remove(ns);
-
- if (a != null)
- {
- ns.WriteConsole("Disconnected. [{0} Online] [{1}]", TcpServer.Instances.Count, a);
- }
- else
- {
- ns.WriteConsole("Disconnected. [{0} Online]", TcpServer.Instances.Count);
- }
+ if (a != null)
+ {
+ WriteConsole("Disconnected. [{0} Online] [{1}]", count, a);
+ }
+ else
+ {
+ WriteConsole("Disconnected. [{0} Online]", count);
}
}
}
diff --git a/Projects/Server/Network/NetworkSocket.cs b/Projects/Server/Network/NetworkSocket.cs
index 9f2905727..8170cdc83 100644
--- a/Projects/Server/Network/NetworkSocket.cs
+++ b/Projects/Server/Network/NetworkSocket.cs
@@ -17,24 +17,48 @@ using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
+using System.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace Server.Network
{
public class NetworkSocket : ISocket
{
- public Socket Connection { get; }
- public EndPoint LocalEndPoint => Connection.LocalEndPoint;
- public EndPoint RemoteEndPoint => Connection.RemoteEndPoint;
+ private Socket _connection;
- public NetworkSocket(Socket connection) => Connection = connection;
+ public Socket Connection
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _connection;
+ }
+ public EndPoint LocalEndPoint
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _connection.LocalEndPoint;
+ }
+
+ public EndPoint RemoteEndPoint
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _connection.RemoteEndPoint;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public NetworkSocket(Socket connection) => _connection = connection;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public Task SendAsync(IList> buffers, SocketFlags flags) =>
- Connection.SendAsync(buffers, flags);
+ _connection.SendAsync(buffers, flags);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public Task ReceiveAsync(IList> buffers, SocketFlags flags) =>
- Connection.ReceiveAsync(buffers, flags);
+ _connection.ReceiveAsync(buffers, flags);
- public void Shutdown(SocketShutdown how) => Connection.Shutdown(how);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Shutdown(SocketShutdown how) => _connection.Shutdown(how);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Close() => _connection.Close();
}
}
diff --git a/Projects/Server/Network/NetworkState.cs b/Projects/Server/Network/NetworkState.cs
index 13acbb05c..8f86be4f4 100644
--- a/Projects/Server/Network/NetworkState.cs
+++ b/Projects/Server/Network/NetworkState.cs
@@ -29,10 +29,10 @@ namespace Server.Network
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Pause(ref NetworkState state) =>
- Interlocked.Exchange(ref state, PauseState)?.Paused != true;
+ Interlocked.CompareExchange(ref state, PauseState, ResumeState) != PauseState;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Resume(ref NetworkState state) =>
- Interlocked.Exchange(ref state, ResumeState)?.Paused == true;
+ Interlocked.CompareExchange(ref state, ResumeState, PauseState) != ResumeState;
}
}
diff --git a/Projects/Server/Network/Packets/IncomingAccountPackets.cs b/Projects/Server/Network/Packets/IncomingAccountPackets.cs
index 0034f832e..9229d9611 100644
--- a/Projects/Server/Network/Packets/IncomingAccountPackets.cs
+++ b/Projects/Server/Network/Packets/IncomingAccountPackets.cs
@@ -283,9 +283,6 @@ namespace Server.Network
m.NetState?.Dispose();
- // TODO: Make this wait one tick so we don't have to call it unnecessarily
- NetState.ProcessDisposedQueue();
-
state.SendClientVersionRequest();
state.BlockAllPackets = true;
@@ -395,8 +392,8 @@ namespace Server.Network
if (
!m_AuthIDWindow.TryGetValue(authID, out var ap) ||
- state.m_AuthID != 0 && authID != state.m_AuthID ||
- state.m_AuthID == 0 && authID != state.m_Seed
+ state._authId != 0 && authID != state._authId ||
+ state._authId == 0 && authID != state._seed
)
{
state.WriteConsole("Invalid client detected, disconnecting");
@@ -445,19 +442,19 @@ namespace Server.Network
{
var si = info[index];
- state.m_AuthID = GenerateAuthID(state);
+ state._authId = GenerateAuthID(state);
state.SentFirstPacket = false;
- state.SendPlayServerAck(si, state.m_AuthID);
+ state.SendPlayServerAck(si, state._authId);
}
}
public static void LoginServerSeed(NetState state, CircularBufferReader reader, ref int packetLength)
{
- state.m_Seed = reader.ReadInt32();
+ state._seed = reader.ReadInt32();
state.Seeded = true;
- if (state.m_Seed == 0)
+ if (state._seed == 0)
{
state.WriteConsole("Invalid client detected, disconnecting");
state.Dispose();
diff --git a/Projects/Server/Network/Packets/IncomingPackets.cs b/Projects/Server/Network/Packets/IncomingPackets.cs
index 8b5bc5e62..af8af3e4c 100644
--- a/Projects/Server/Network/Packets/IncomingPackets.cs
+++ b/Projects/Server/Network/Packets/IncomingPackets.cs
@@ -106,7 +106,7 @@ namespace Server.Network
return -1;
}
- ns.m_Seed = seed;
+ ns._seed = seed;
ns.Seeded = true;
return 4;
diff --git a/Projects/Server/Network/Packets/OutgoingGumpPackets.cs b/Projects/Server/Network/Packets/OutgoingGumpPackets.cs
index 699489c3f..c693a41b9 100644
--- a/Projects/Server/Network/Packets/OutgoingGumpPackets.cs
+++ b/Projects/Server/Network/Packets/OutgoingGumpPackets.cs
@@ -43,8 +43,12 @@ namespace Server.Network
ns.Send(writer.Span);
}
+ private static readonly byte[] _layoutBuffer = GC.AllocateUninitializedArray(0x20000);
+ private static readonly byte[] _stringsBuffer = GC.AllocateUninitializedArray(0x20000);
+ private static readonly byte[] _packBuffer = GC.AllocateUninitializedArray(0x20000);
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void WritePacked(ref SpanWriter writer, ReadOnlySpan span)
+ private static void WritePacked(ReadOnlySpan span, ref SpanWriter writer)
{
var length = span.Length;
@@ -54,21 +58,38 @@ namespace Server.Network
return;
}
- var wantLength = 1 + span.Length * 1024 / 1000;
+ var wantLength = Zlib.MaxPackSize(length);
+ var packBuffer = _packBuffer;
+ byte[] rentedBuffer = null;
- wantLength += 4095;
- wantLength &= ~4095;
+ if (wantLength > packBuffer.Length)
+ {
+ packBuffer = rentedBuffer = ArrayPool.Shared.Rent(wantLength);
+ }
- var packBuffer = ArrayPool.Shared.Rent(wantLength);
var packLength = wantLength;
- Zlib.Pack(packBuffer, ref packLength, span, length, ZlibQuality.Default);
+ var error = Zlib.Pack(packBuffer, ref packLength, span, length, ZlibQuality.Default);
+
+ if (error != ZlibError.Okay)
+ {
+ Utility.PushColor(ConsoleColor.Red);
+ Console.WriteLine("Core: Gump compression failed {0}", error);
+ Utility.PopColor();
+
+ writer.Write(4);
+ writer.Write(0);
+ return;
+ }
writer.Write(4 + packLength);
writer.Write(length);
writer.Write(packBuffer.AsSpan(0, packLength));
- ArrayPool.Shared.Return(packBuffer);
+ if (rentedBuffer != null)
+ {
+ ArrayPool.Shared.Return(rentedBuffer);
+ }
}
public static void SendDisplayGump(this NetState ns, Gump gump, out int switches, out int entries)
@@ -83,7 +104,57 @@ namespace Server.Network
var packed = ns.Unpack;
- var writer = new SpanWriter(512, true);
+ 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(11);
+
+ 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(stackalloc byte[maxLength]);
writer.Write((byte)(packed ? 0xDD : 0xB0)); // Packet ID
writer.Seek(2, SeekOrigin.Current);
@@ -92,77 +163,26 @@ namespace Server.Network
writer.Write(gump.X);
writer.Write(gump.Y);
- var spanWriter = new SpanWriter(512, true);
-
- if (!gump.Draggable)
- {
- spanWriter.Write(Gump.NoMove);
- }
-
- if (!gump.Closable)
- {
- spanWriter.Write(Gump.NoClose);
- }
-
- if (!gump.Disposable)
- {
- spanWriter.Write(Gump.NoDispose);
- }
-
- if (!gump.Resizable)
- {
- spanWriter.Write(Gump.NoResize);
- }
-
- var stringsList = new OrderedHashSet(11);
-
- foreach (var entry in gump.Entries)
- {
- entry.AppendTo(ref spanWriter, stringsList, ref entries, ref switches);
- }
-
if (packed)
{
- spanWriter.Write((byte)0); // Layout text terminator
- WritePacked(ref writer, spanWriter.Span);
- }
- else
- {
- writer.Write((ushort)spanWriter.BytesWritten);
- writer.Write(spanWriter.Span);
- }
+ layoutWriter.Write((byte)0); // Layout text terminator
+ WritePacked(layoutWriter.Span, ref writer);
- if (packed)
- {
writer.Write(stringsList.Count);
+ WritePacked(stringsWriter.Span, ref writer);
}
else
{
+ writer.Write((ushort)layoutWriter.BytesWritten);
+ writer.Write(layoutWriter.Span);
+
writer.Write((ushort)stringsList.Count);
- }
-
- spanWriter.Seek(0, SeekOrigin.Begin);
-
- foreach (var str in stringsList)
- {
- var s = str ?? "";
- spanWriter.Write((ushort)s.Length);
- spanWriter.WriteBigUni(s);
- }
-
- if (packed)
- {
- WritePacked(ref writer, spanWriter.Span);
- }
- else
- {
- writer.Write(spanWriter.Span);
+ writer.Write(stringsWriter.Span);
}
writer.WritePacketLength();
ns.Send(writer.Span);
- spanWriter.Dispose(); // Can't use using and refs, so we dispose manually
}
public static void SendDisplaySignGump(this NetState ns, Serial serial, int gumpId, string unknown, string caption)
diff --git a/Projects/Server/Network/TcpServer.cs b/Projects/Server/Network/TcpServer.cs
index 5a527631b..2ad87c4ff 100644
--- a/Projects/Server/Network/TcpServer.cs
+++ b/Projects/Server/Network/TcpServer.cs
@@ -151,20 +151,24 @@ namespace Server.Network
}
}
- public static void Slice()
+ public static int Slice()
{
int count = 0;
+ var limit = m_ConnectedQueue.Count;
- while (count++ < 250)
+ while (m_ConnectedQueue.Count > 0 && --limit >= 0)
{
if (!m_ConnectedQueue.TryDequeue(out var ns))
{
break;
}
+ count++;
Instances.Add(ns);
ns.WriteConsole("Connected. [{0} Online]", Instances.Count);
}
+
+ return count;
}
private static async void BeginAcceptingSockets(this TcpListener listener)
diff --git a/Projects/Server/Timer/TimerDelayCalls.cs b/Projects/Server/Timer/Timer.DelayCall.cs
similarity index 98%
rename from Projects/Server/Timer/TimerDelayCalls.cs
rename to Projects/Server/Timer/Timer.DelayCall.cs
index 4fd2d791e..5e3b46b4e 100644
--- a/Projects/Server/Timer/TimerDelayCalls.cs
+++ b/Projects/Server/Timer/Timer.DelayCall.cs
@@ -1,8 +1,8 @@
/*************************************************************************
* ModernUO *
- * Copyright 2019-2020 - ModernUO Development Team *
+ * Copyright (C) 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
- * File: TimerDelayCalls.cs *
+ * File: Timer.DelayCall.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 *
diff --git a/Projects/Server/Timer/Timer.Pause.cs b/Projects/Server/Timer/Timer.Pause.cs
new file mode 100644
index 000000000..d0854f7dd
--- /dev/null
+++ b/Projects/Server/Timer/Timer.Pause.cs
@@ -0,0 +1,53 @@
+/*************************************************************************
+ * ModernUO *
+ * Copyright (C) 2019-2021 - ModernUO Development Team *
+ * Email: hi@modernuo.com *
+ * File: Timer.Pause.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 . *
+ *************************************************************************/
+
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Server
+{
+ public partial class Timer
+ {
+ public class DelayTaskTimer : Timer, INotifyCompletion
+ {
+ private Action _continuation;
+ private bool _complete;
+
+ internal DelayTaskTimer(TimeSpan delay) : base(delay) => Start();
+
+ protected override void OnTick()
+ {
+ _complete = true;
+ _continuation?.Invoke();
+ }
+
+ public DelayTaskTimer GetAwaiter() => this;
+
+ public bool IsCompleted => _complete;
+
+ public void OnCompleted(Action continuation) => _continuation = continuation;
+
+ public void GetResult()
+ {
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static DelayTaskTimer Pause(TimeSpan ms) => new(ms);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static DelayTaskTimer Pause(int ms) => Pause(TimeSpan.FromMilliseconds(ms));
+ }
+}
diff --git a/Projects/Server/Timer/Timer.cs b/Projects/Server/Timer/Timer.cs
index 487aaa266..a9c23aab5 100644
--- a/Projects/Server/Timer/Timer.cs
+++ b/Projects/Server/Timer/Timer.cs
@@ -1,3 +1,18 @@
+/*************************************************************************
+ * ModernUO *
+ * Copyright (C) 2019-2021 - ModernUO Development Team *
+ * Email: hi@modernuo.com *
+ * File: Timer.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 . *
+ *************************************************************************/
+
using System;
using System.Collections.Generic;
using System.IO;
@@ -136,14 +151,14 @@ namespace Server
return TimerProfile.Acquire(name);
}
- public static void Slice()
+ public static int Slice()
{
+ var index = 0;
+
lock (m_Queue)
{
m_QueueCountAtSlice = m_Queue.Count;
- var index = 0;
-
while (index < BreakCount && m_Queue.Count != 0)
{
var t = m_Queue.Dequeue();
@@ -153,11 +168,13 @@ namespace Server
t.OnTick();
t.m_Queued = false;
- ++index;
+ index++;
prof?.Finish();
}
}
+
+ return index;
}
public void RegCreation()
@@ -243,15 +260,6 @@ namespace Server
{
}
- public static Task Pause(int ms) => Pause(TimeSpan.FromMilliseconds(ms));
-
- public static Task Pause(TimeSpan ms)
- {
- var t = new DelayTaskTimer(ms);
- t.Start();
- return t.Task;
- }
-
public class TimerThread
{
private static readonly Dictionary
@@ -495,20 +503,5 @@ namespace Server
}
}
}
-
- private class DelayTaskTimer : Timer
- {
- private readonly TaskCompletionSource m_TaskCompleter;
-
- public DelayTaskTimer(TimeSpan delay) : base(delay) =>
- m_TaskCompleter = new TaskCompletionSource();
-
- public Task Task => m_TaskCompleter.Task;
-
- protected override void OnTick()
- {
- m_TaskCompleter.SetResult(this);
- }
- }
}
}
diff --git a/Projects/Server/Utilities/Utility.cs b/Projects/Server/Utilities/Utility.cs
index 90901775e..962e4832b 100644
--- a/Projects/Server/Utilities/Utility.cs
+++ b/Projects/Server/Utilities/Utility.cs
@@ -1391,6 +1391,46 @@ namespace Server
return (int)(unchecked(((i + (i >> 4)) & 0xF0F0F0F0F0F0F0FUL) * 0x101010101010101UL) >> 56);
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int CountDigits(uint value)
+ {
+ int digits = 1;
+ if (value >= 100000)
+ {
+ value /= 100000;
+ digits += 5;
+ }
+ if (value < 10)
+ {
+ // no-op
+ }
+ else if (value < 100)
+ {
+ digits++;
+ }
+ else if (value < 1000)
+ {
+ digits += 2;
+ }
+ else if (value < 10000)
+ {
+ digits += 3;
+ }
+ else
+ {
+ digits += 4;
+ }
+
+ return digits;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint DivRem(uint a, uint b, out uint result)
+ {
+ uint div = a / b;
+ result = a - div * b;
+ return div;
+ }
}
}
diff --git a/Projects/UOContent.Tests/Tests/Skills/Tracking/TrackingGumpTests.cs b/Projects/UOContent.Tests/Tests/Skills/Tracking/TrackingGumpTests.cs
new file mode 100644
index 000000000..99ce2daf7
--- /dev/null
+++ b/Projects/UOContent.Tests/Tests/Skills/Tracking/TrackingGumpTests.cs
@@ -0,0 +1,33 @@
+using System;
+using Server.Mobiles;
+using Server.Network;
+using Server.SkillHandlers;
+using Server.Tests;
+using Server.Tests.Network;
+using Xunit;
+
+namespace UOContent.Tests
+{
+ public class TrackingGumpTests : IClassFixture
+ {
+ [Theory]
+ [InlineData(ProtocolChanges.None)]
+ [InlineData(ProtocolChanges.Unpack)]
+ // Regression test used to identify an issue with AddItem compilation of the packet
+ public void TestTrackingGump(ProtocolChanges changes)
+ {
+ var pm = new PlayerMobile();
+ pm.Skills.Tracking.BaseFixedPoint = 1000;
+ var g = new TrackWhatGump(pm);
+
+ using var ns = PacketTestUtilities.CreateTestNetState();
+ ns.ProtocolChanges = changes;
+
+ var expected = g.Compile(ns).Compile();
+ ns.SendDisplayGump(g, out var switches, out var entries);
+
+ var result = ns.SendPipe.Reader.TryRead();
+ AssertThat.Equal(result.Buffer[0].AsSpan(0), expected);
+ }
+ }
+}
diff --git a/Projects/UOContent.Tests/UOContent.Tests.csproj b/Projects/UOContent.Tests/UOContent.Tests.csproj
index 63d7b7c89..05672ef81 100644
--- a/Projects/UOContent.Tests/UOContent.Tests.csproj
+++ b/Projects/UOContent.Tests/UOContent.Tests.csproj
@@ -3,7 +3,7 @@
false
-
+
diff --git a/Projects/UOContent/Gumps/AdminGump.cs b/Projects/UOContent/Gumps/AdminGump.cs
index 442f87f2e..63acbb309 100644
--- a/Projects/UOContent/Gumps/AdminGump.cs
+++ b/Projects/UOContent/Gumps/AdminGump.cs
@@ -2,9 +2,9 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
-using System.Text;
using System.Threading;
using Server.Accounting;
+using Server.Buffers;
using Server.Commands;
using Server.Items;
using Server.Misc;
@@ -222,11 +222,7 @@ namespace Server.Gumps
}
case AdminGumpPage.Information_Perf:
{
- AddLabel(20, 130, LabelHue, "Cycles Per Second:");
- AddLabel(40, 150, LabelHue, $"Current: {Core.CyclesPerSecond:N2}");
- AddLabel(40, 170, LabelHue, $"Average: {Core.AverageCPS:N2}");
-
- var sb = new StringBuilder();
+ using var sb = new ValueStringBuilder();
ThreadPool.GetAvailableThreads(out var curUser, out var curIOCP);
ThreadPool.GetMaxThreads(out var maxUser, out var maxIOCP);
@@ -646,7 +642,7 @@ namespace Server.Gumps
AddLabel(12, 140, LabelHue, "There are no accounts to display.");
}
- var sb = new StringBuilder();
+ using var sb = new ValueStringBuilder();
for (int i = 0, index = listPage * 12;
i < 12 && index >= 0 && index < sharedAccounts.Count;
@@ -662,10 +658,7 @@ namespace Server.Gumps
AddLabelCropped(12, offset, 60, 20, LabelHue, accts.Count.ToString());
AddLabelCropped(72, offset, 120, 20, LabelHue, ipAddr.ToString());
- if (sb.Length > 0)
- {
- sb.Length = 0;
- }
+ sb.Reset();
for (var j = 0; j < accts.Count; ++j)
{
@@ -1199,7 +1192,7 @@ namespace Server.Gumps
AddButtonLabeled(20, 150, GetButtonID(5, 4), "Add Comment");
- var sb = new StringBuilder();
+ var sb = new ValueStringBuilder();
if (a.Comments.Count == 0)
{
@@ -1214,12 +1207,17 @@ namespace Server.Gumps
}
var c = a.Comments[i];
-
- sb.AppendFormat("[{0} on {1}]
{2}", c.AddedBy, c.LastModified, c.Content);
+ sb.Append('[');
+ sb.Append(c.AddedBy);
+ sb.Append(" on ");
+ sb.Append(c.LastModified.ToString());
+ sb.Append("]
");
+ sb.Append(c.Content);
}
AddHtml(20, 180, 380, 190, sb.ToString(), true, true);
+ sb.Dispose(); // Due to an analyzer bug, we can't use a using
goto case AdminGumpPage.AccountDetails;
}
case AdminGumpPage.AccountDetails_Tags:
@@ -1233,7 +1231,7 @@ namespace Server.Gumps
AddButtonLabeled(20, 150, GetButtonID(5, 5), "Add Tag");
- var sb = new StringBuilder();
+ var sb = new ValueStringBuilder();
if (a.Tags.Count == 0)
{
@@ -1249,11 +1247,14 @@ namespace Server.Gumps
var tag = a.Tags[i];
- sb.AppendFormat("{0} = {1}", tag.Name, tag.Value);
+ sb.Append(tag.Name);
+ sb.Append(" = ");
+ sb.Append(tag.Value);
}
AddHtml(20, 180, 380, 190, sb.ToString(), true, true);
+ sb.Dispose();
goto case AdminGumpPage.AccountDetails;
}
case AdminGumpPage.Firewall:
@@ -2917,17 +2918,16 @@ namespace Server.Gumps
if (list.Count > 0)
{
- var sb = new StringBuilder();
-
- sb.AppendFormat(
- "You are about to ban {0} account{1}. Do you wish to continue?",
- list.Count,
- list.Count != 1 ? "s" : ""
- );
+ using var sb = new ValueStringBuilder();
+ sb.Append("You are about to ban ");
+ sb.Append(list.Count);
+ sb.Append(list.Count != 1 ? "accounts." : "account.");
+ sb.Append(" Do you wish to continue?");
for (var i = 0; i < list.Count; ++i)
{
- sb.AppendFormat("
- {0}", list[i].Username);
+ sb.Append("
- ");
+ sb.Append(list[i].Username);
}
from.SendGump(