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;
}
```
This commit is contained in:
Kamron Batman 2021-01-25 21:06:46 -08:00 • committed by GitHub
parent 07751654b7
commit 6f0e1a22f9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 763 additions and 449 deletions

View file

@ -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<List<string>>(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");