feat: Adds new decay system and SkipSerialization (#2311)
## Summary - Replaces scan-based decay checking during world saves with an event-driven timer wheel scheduler - Removes virtual property checks from serialization hot path, achieving 6-8x faster world saves ## Changes ### DecayScheduler (new): - Timer wheel with 12 HashSet buckets (5-min intervals) + PriorityQueue for active processing - O(1) register/unregister vs O(n) scan of all items - Auto start/stop when items exist/empty - Configurable tick interval with jitter to prevent system synchronization ### Item.cs: - Added ScheduledDecayTime computed property - Added UpdateDecayRegistration() called from SetLastMoved(), property setters, AddItem()/RemoveItem() - Hooks in Visible, Movable, Spawner setters and Delete() ### World.cs: - Removed _decayQueue, EnqueueForDecay(), ProcessDecay() - ItemPersistence.Serialize() now tight loop without virtual calls ## Performance | Metric | Before | After | Improvement | |---------------|------------|-----------|-------------| | Items (230K) | 245K ticks | 34K ticks | 8x faster | | Mobiles (43K) | 56K ticks | 6K ticks | 9x faster | | Total | 302K ticks | 40K ticks | 7.5x faster | ## Configuration decay.maxItemsPerTick = 250 # Items processed per tick decay.tickInterval = 256ms # Base processing interval decay.bucketInterval = 5min # Timer wheel bucket size decay.jitterMaxMs = 25 # ±25ms tick jitter
This commit is contained in:
parent
c14f361de1
commit
ee2cd1d18d
27 changed files with 426 additions and 173 deletions
|
|
@ -47,7 +47,6 @@ public static class World
|
|||
private static int _threadId;
|
||||
internal static SerializationThreadWorker[] _threadWorkers;
|
||||
private static readonly ManualResetEvent _diskWriteHandle = new(true);
|
||||
private static readonly ConcurrentQueue<Item> _decayQueue = new();
|
||||
|
||||
private static string _tempSavePath; // Path to the temporary folder for the save
|
||||
|
||||
|
|
@ -109,18 +108,6 @@ public static class World
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void WaitForWriteCompletion() => _diskWriteHandle.WaitOne();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void EnqueueForDecay(Item item)
|
||||
{
|
||||
if (WorldState != WorldState.Saving)
|
||||
{
|
||||
logger.Warning("Attempting to queue {Item} for decay but the world is not saving", item);
|
||||
return;
|
||||
}
|
||||
|
||||
_decayQueue.Enqueue(item);
|
||||
}
|
||||
|
||||
public static void Broadcast(int hue, bool ascii, string text)
|
||||
{
|
||||
var length = OutgoingMessagePackets.GetMaxMessageLength(text);
|
||||
|
|
@ -216,20 +203,6 @@ public static class World
|
|||
}
|
||||
}
|
||||
|
||||
private static void ProcessDecay()
|
||||
{
|
||||
while (_decayQueue.TryDequeue(out var item))
|
||||
{
|
||||
if (item.OnDecay())
|
||||
{
|
||||
// TODO: Add Logging
|
||||
item.Delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static DateTime _serializationStart;
|
||||
|
||||
/**
|
||||
* Duplicates can be weeded out asynchronously while flushing
|
||||
* If performance becomes a problem, we need to build a dual mode concurrent array.
|
||||
|
|
@ -322,8 +295,6 @@ public static class World
|
|||
|
||||
try
|
||||
{
|
||||
_serializationStart = Core.Now;
|
||||
|
||||
if (string.IsNullOrEmpty(snapshotPath))
|
||||
{
|
||||
throw new ArgumentException("Snapshot path cannot be null or empty", nameof(snapshotPath));
|
||||
|
|
@ -543,28 +514,9 @@ public static class World
|
|||
}
|
||||
|
||||
item.ClearProperties();
|
||||
item.UpdateDecayRegistration();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Serialize()
|
||||
{
|
||||
foreach (var item in EntitiesBySerial.Values)
|
||||
{
|
||||
if (item.CanDecay() && item.LastMoved + item.DecayTime <= _serializationStart)
|
||||
{
|
||||
EnqueueForDecay(item);
|
||||
}
|
||||
|
||||
PushToCache(item);
|
||||
}
|
||||
}
|
||||
|
||||
public override void PostWorldSave()
|
||||
{
|
||||
ProcessDecay(); // Run this before the safety queue
|
||||
|
||||
base.PostWorldSave();
|
||||
}
|
||||
}
|
||||
|
||||
private class MobilePersistence : GenericEntityPersistence<Mobile>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue