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(