Skip to content

Commit 2fe41fc

Browse files
committed
Replaced multiple per-client polling workers with a single, sequential
player-owned work queue. - Unified XP, ability, buff, and debuff events under one worker loop - Removed 50ms polling per-queue; now wakes immediately on signal - Preserved existing event structs and execution semantics - Ensured deterministic ordering for all player-related mutations - Reduced background task count and scheduling overhead per client This keeps all player state mutations serialized while improving responsiveness and scalability.
1 parent fea6309 commit 2fe41fc

File tree

10 files changed

+146
-152
lines changed

10 files changed

+146
-152
lines changed
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
using Darkages.Sprites.Entity;
1+
using Darkages.Network.Client;
2+
using Darkages.Network.Client.Abstractions;
3+
using Darkages.Sprites.Entity;
24

35
namespace Darkages.Events;
46

5-
public readonly struct AbilityEvent(Aisling player, int exp, bool hunting)
7+
public readonly struct AbilityEvent(Aisling player, int exp, bool hunting) : IClientWork
68
{
79
public Aisling Player { get; } = player;
810
public int Exp { get; } = exp;
911
public bool Hunting { get; } = hunting;
12+
13+
public void Execute(WorldClient client) => client.ClientWorkApEvent(Player, Exp, Hunting);
1014
}

Zolian.Server.Base/Events/BuffEvent.cs

Lines changed: 0 additions & 10 deletions
This file was deleted.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Darkages.Network.Client;
2+
using Darkages.Network.Client.Abstractions;
3+
using Darkages.Sprites;
4+
using Darkages.Types;
5+
6+
namespace Darkages.Events;
7+
8+
public readonly struct BuffOnAppliedEvent(Sprite affected, Buff buff) : IClientWork
9+
{
10+
public Sprite Affected { get; } = affected;
11+
public Buff Buff { get; } = buff;
12+
13+
public void Execute(WorldClient client) => client.ClientWorkBuffAppliedEvent(Affected, Buff);
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Darkages.Network.Client;
2+
using Darkages.Network.Client.Abstractions;
3+
using Darkages.Sprites;
4+
using Darkages.Types;
5+
6+
namespace Darkages.Events;
7+
8+
public readonly struct BuffOnUpdatedEvent(Sprite affected, Buff buff) : IClientWork
9+
{
10+
public Sprite Affected { get; } = affected;
11+
public Buff Buff { get; } = buff;
12+
13+
public void Execute(WorldClient client) => client.ClientWorkBuffUpdatedEvent(Affected, Buff);
14+
}

Zolian.Server.Base/Events/DebuffEvent.cs

Lines changed: 0 additions & 10 deletions
This file was deleted.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Darkages.Network.Client;
2+
using Darkages.Network.Client.Abstractions;
3+
using Darkages.Sprites;
4+
using Darkages.Types;
5+
6+
namespace Darkages.Events;
7+
8+
public readonly struct DebuffOnAppliedEvent(Sprite affected, Debuff debuff) : IClientWork
9+
{
10+
public Sprite Affected { get; } = affected;
11+
public Debuff Debuff { get; } = debuff;
12+
13+
public void Execute(WorldClient client) => client.ClientWorkDebuffAppliedEvent(Affected, Debuff);
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Darkages.Network.Client;
2+
using Darkages.Network.Client.Abstractions;
3+
using Darkages.Sprites;
4+
using Darkages.Types;
5+
6+
namespace Darkages.Events;
7+
8+
public readonly struct DebuffOnUpdatedEvent(Sprite affected, Debuff debuff) : IClientWork
9+
{
10+
public Sprite Affected { get; } = affected;
11+
public Debuff Debuff { get; } = debuff;
12+
13+
public void Execute(WorldClient client) => client.ClientWorkDebuffUpdatedEvent(Affected, Debuff);
14+
}
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
using Darkages.Sprites.Entity;
1+
using Darkages.Network.Client;
2+
using Darkages.Network.Client.Abstractions;
3+
using Darkages.Sprites.Entity;
24

35
namespace Darkages.Events;
46

5-
public readonly struct ExperienceEvent(Aisling player, long exp, bool hunting)
7+
public readonly struct ExperienceEvent(Aisling player, long exp, bool hunting) : IClientWork
68
{
79
public Aisling Player { get; } = player;
810
public long Exp { get; } = exp;
911
public bool Hunting { get; } = hunting;
12+
13+
public void Execute(WorldClient client) => client.ClientWorkExpEvent(Player, Exp, Hunting);
1014
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Darkages.Network.Client.Abstractions;
2+
3+
internal interface IClientWork
4+
{
5+
void Execute(WorldClient client);
6+
}

Zolian.Server.Base/Network/Client/WorldClient.cs

Lines changed: 72 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
using Chaos.Networking.Abstractions;
1515
using Chaos.Networking.Abstractions.Definitions;
1616
using Chaos.Networking.Entities.Server;
17+
using Chaos.NLog.Logging.Definitions;
18+
using Chaos.NLog.Logging.Extensions;
1719
using Chaos.Packets;
1820
using Chaos.Packets.Abstractions;
1921

@@ -28,6 +30,7 @@
2830
using Darkages.Managers;
2931
using Darkages.Meta;
3032
using Darkages.Models;
33+
using Darkages.Network.Client.Abstractions;
3134
using Darkages.Network.Client.Coalescer;
3235
using Darkages.Network.Server;
3336
using Darkages.Object;
@@ -156,12 +159,10 @@ public bool IsDayDreaming
156159
public WorldPortal PendingNode { get; set; }
157160
public uint EntryCheck { get; set; }
158161
private readonly Lock _warpCheckLock = new();
159-
private readonly ConcurrentQueue<ExperienceEvent> _expQueue = [];
160-
private readonly ConcurrentQueue<AbilityEvent> _apQueue = [];
161-
private readonly ConcurrentQueue<DebuffEvent> _debuffApplyQueue = [];
162-
private readonly ConcurrentQueue<BuffEvent> _buffApplyQueue = [];
163-
private readonly ConcurrentQueue<DebuffEvent> _debuffUpdateQueue = [];
164-
private readonly ConcurrentQueue<BuffEvent> _buffUpdateQueue = [];
162+
163+
// Client-owned work queue
164+
private readonly ConcurrentQueue<IClientWork> _clientWorkQueue = new();
165+
private readonly SemaphoreSlim _clientWorkSignal = new(0, int.MaxValue);
165166

166167
public WorldClient([NotNull] IWorldServer<IWorldClient> server, [NotNull] Socket socket,
167168
[NotNull] ICrypto crypto, [NotNull] IPacketSerializer packetSerializer,
@@ -171,14 +172,7 @@ public WorldClient([NotNull] IWorldServer<IWorldClient> server, [NotNull] Socket
171172
_soundCoalescer = new SoundCoalescer(SendSoundImmediate, 150, 24);
172173
_healthBarCoalescer = new HealthBarCoalescer(SendHealthBarCoalesced, 150, 24);
173174
_bodyAnimationCoalescer = new BodyAnimationCoalescer(SendBodyAnimationCoalesced, 150, 24);
174-
175-
// Event-Driven Tasks
176-
_ = Task.Factory.StartNew(ProcessExperienceEvents, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap();
177-
_ = Task.Factory.StartNew(ProcessAbilityEvents, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap();
178-
_ = Task.Factory.StartNew(ProcessApplyingBuffsEvents, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap();
179-
_ = Task.Factory.StartNew(ProcessApplyingDebuffsEvents, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap();
180-
_ = Task.Factory.StartNew(ProcessUpdatingBuffsEvents, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap();
181-
_ = Task.Factory.StartNew(ProcessUpdatingDebuffsEvents, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap();
175+
_ = Task.Run(ProcessPlayerWorkQueue);
182176
}
183177

184178
public Task Update()
@@ -199,113 +193,6 @@ public Task Update()
199193
return Task.CompletedTask;
200194
}
201195

202-
203-
#region Events
204-
205-
private async Task ProcessExperienceEvents()
206-
{
207-
while (ServerSetup.Instance.Running)
208-
{
209-
if (_expQueue.IsEmpty)
210-
{
211-
await Task.Delay(50).ConfigureAwait(false);
212-
continue;
213-
}
214-
215-
while (_expQueue.TryDequeue(out var expEvent))
216-
{
217-
HandleExp(expEvent.Player, expEvent.Exp, expEvent.Hunting);
218-
}
219-
}
220-
}
221-
222-
private async Task ProcessAbilityEvents()
223-
{
224-
while (ServerSetup.Instance.Running)
225-
{
226-
if (_apQueue.IsEmpty)
227-
{
228-
await Task.Delay(50).ConfigureAwait(false);
229-
continue;
230-
}
231-
232-
while (_apQueue.TryDequeue(out var apEvent))
233-
{
234-
HandleAp(apEvent.Player, apEvent.Exp, apEvent.Hunting);
235-
}
236-
}
237-
}
238-
239-
private async Task ProcessApplyingDebuffsEvents()
240-
{
241-
while (ServerSetup.Instance.Running)
242-
{
243-
if (_debuffApplyQueue.IsEmpty)
244-
{
245-
await Task.Delay(50).ConfigureAwait(false);
246-
continue;
247-
}
248-
249-
while (_debuffApplyQueue.TryDequeue(out var debuffEvent))
250-
{
251-
debuffEvent.Debuff.OnApplied(debuffEvent.Affected, debuffEvent.Debuff);
252-
}
253-
}
254-
}
255-
256-
private async Task ProcessApplyingBuffsEvents()
257-
{
258-
while (ServerSetup.Instance.Running)
259-
{
260-
if (_buffApplyQueue.IsEmpty)
261-
{
262-
await Task.Delay(50).ConfigureAwait(false);
263-
continue;
264-
}
265-
266-
while (_buffApplyQueue.TryDequeue(out var buffEvent))
267-
{
268-
buffEvent.Buff.OnApplied(buffEvent.Affected, buffEvent.Buff);
269-
}
270-
}
271-
}
272-
273-
private async Task ProcessUpdatingDebuffsEvents()
274-
{
275-
while (ServerSetup.Instance.Running)
276-
{
277-
if (_debuffUpdateQueue.IsEmpty)
278-
{
279-
await Task.Delay(50).ConfigureAwait(false);
280-
continue;
281-
}
282-
283-
while (_debuffUpdateQueue.TryDequeue(out var debuffEvent))
284-
{
285-
debuffEvent.Debuff.Update(debuffEvent.Affected);
286-
}
287-
}
288-
}
289-
290-
private async Task ProcessUpdatingBuffsEvents()
291-
{
292-
while (ServerSetup.Instance.Running)
293-
{
294-
if (_buffUpdateQueue.IsEmpty)
295-
{
296-
await Task.Delay(50).ConfigureAwait(false);
297-
continue;
298-
}
299-
300-
while (_buffUpdateQueue.TryDequeue(out var buffEvent))
301-
{
302-
buffEvent.Buff.Update(buffEvent.Affected);
303-
}
304-
}
305-
}
306-
307-
#endregion
308-
309196
private void CheckInvisible(Aisling player, Stopwatch sw)
310197
{
311198
if (player.IsInvisible) return;
@@ -4069,14 +3956,71 @@ public static void KillPlayer(Area map, string u)
40693956

40703957
#endregion
40713958

4072-
#region Events & Experience
3959+
#region Client Work Queue
3960+
3961+
private async Task ProcessPlayerWorkQueue()
3962+
{
3963+
while (ServerSetup.Instance.Running)
3964+
{
3965+
await _clientWorkSignal.WaitAsync(5000).ConfigureAwait(false);
3966+
3967+
while (_clientWorkQueue.TryDequeue(out var work))
3968+
{
3969+
try
3970+
{
3971+
work.Execute(this);
3972+
}
3973+
catch { }
3974+
}
3975+
}
3976+
}
3977+
3978+
public void EnqueueExperienceEvent(Aisling player, long exp, bool hunting)
3979+
{
3980+
_clientWorkQueue.Enqueue(new ExperienceEvent(player, exp, hunting));
3981+
_clientWorkSignal.Release();
3982+
}
3983+
3984+
public void EnqueueAbilityEvent(Aisling player, int exp, bool hunting)
3985+
{
3986+
_clientWorkQueue.Enqueue(new AbilityEvent(player, exp, hunting));
3987+
_clientWorkSignal.Release();
3988+
}
3989+
3990+
public void EnqueueDebuffAppliedEvent(Sprite affected, Debuff debuff)
3991+
{
3992+
_clientWorkQueue.Enqueue(new DebuffOnAppliedEvent(affected, debuff));
3993+
_clientWorkSignal.Release();
3994+
}
3995+
3996+
public void EnqueueDebuffUpdatedEvent(Sprite affected, Debuff debuff)
3997+
{
3998+
_clientWorkQueue.Enqueue(new DebuffOnUpdatedEvent(affected, debuff));
3999+
_clientWorkSignal.Release();
4000+
}
4001+
4002+
public void EnqueueBuffAppliedEvent(Sprite affected, Buff buff)
4003+
{
4004+
_clientWorkQueue.Enqueue(new BuffOnAppliedEvent(affected, buff));
4005+
_clientWorkSignal.Release();
4006+
}
40734007

4074-
public void EnqueueExperienceEvent(Aisling player, long exp, bool hunting) => _expQueue.Enqueue(new ExperienceEvent(player, exp, hunting));
4075-
public void EnqueueAbilityEvent(Aisling player, int exp, bool hunting) => _apQueue.Enqueue(new AbilityEvent(player, exp, hunting));
4076-
public void EnqueueDebuffAppliedEvent(Sprite affected, Debuff debuff) => _debuffApplyQueue.Enqueue(new DebuffEvent(affected, debuff));
4077-
public void EnqueueBuffAppliedEvent(Sprite affected, Buff buff) => _buffApplyQueue.Enqueue(new BuffEvent(affected, buff));
4078-
public void EnqueueDebuffUpdatedEvent(Sprite affected, Debuff debuff) => _debuffUpdateQueue.Enqueue(new DebuffEvent(affected, debuff));
4079-
public void EnqueueBuffUpdatedEvent(Sprite affected, Buff buff) => _buffUpdateQueue.Enqueue(new BuffEvent(affected, buff));
4008+
public void EnqueueBuffUpdatedEvent(Sprite affected, Buff buff)
4009+
{
4010+
_clientWorkQueue.Enqueue(new BuffOnUpdatedEvent(affected, buff));
4011+
_clientWorkSignal.Release();
4012+
}
4013+
4014+
internal void ClientWorkExpEvent(Aisling player, long exp, bool hunting) => HandleExp(player, exp, hunting);
4015+
internal void ClientWorkApEvent(Aisling player, int exp, bool hunting) => HandleAp(player, exp, hunting);
4016+
internal void ClientWorkDebuffAppliedEvent(Sprite affected, Debuff debuff) => debuff.OnApplied(affected, debuff);
4017+
internal void ClientWorkDebuffUpdatedEvent(Sprite affected, Debuff debuff) => debuff.Update(affected);
4018+
internal void ClientWorkBuffAppliedEvent(Sprite affected, Buff buff) => buff.OnApplied(affected, buff);
4019+
internal void ClientWorkBuffUpdatedEvent(Sprite affected, Buff buff) => buff.Update(affected);
4020+
4021+
#endregion
4022+
4023+
#region Events & Experience
40804024

40814025
public void GiveExp(long exp)
40824026
{

0 commit comments

Comments
 (0)