Skip to content

Commit a29df9a

Browse files
author
Dan Oak
committed
Improve state management and concurrency
- Fix WorldHostedService to use async void with proper error handling - Replace ConcurrentBag with ConcurrentDictionary for Players and NPCs (O(1) lookups) - Make WorldState fields private with thread-safe accessors and helper methods - Add player disconnect cleanup for trade/warp sessions - Update all packet handlers to use .Values for dictionary collections
1 parent 0691793 commit a29df9a

16 files changed

Lines changed: 111 additions & 55 deletions

src/Acorn/Net/NewConnectionHostedService.cs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using Microsoft.Extensions.Hosting;
1111
using Microsoft.Extensions.Logging;
1212
using Microsoft.Extensions.Options;
13+
using Moffat.EndlessOnline.SDK.Protocol.Net.Server;
1314

1415
namespace Acorn.Net;
1516

@@ -117,7 +118,7 @@ await HandleWebSocketConnection(ws.Result, cancellationToken),
117118
var playerState = playerStateFactory.CreatePlayerState(communicator, sessionId,
118119
async player => await OnClientDisposed(player, sessionId));
119120

120-
var added = worldState.Players.TryAdd(sessionId, playerState);
121+
var added = worldState.TryAddPlayer(sessionId, playerState);
121122
logger.LogInformation("Connection accepted. {PlayersConnected} players connected",
122123
worldState.Players.Count);
123124
UpdateConnectedCount();
@@ -168,13 +169,33 @@ private void UpdateConnectedCount()
168169

169170
private async Task OnClientDisposed(PlayerState player, int sessionId)
170171
{
172+
// Cancel any pending warp
173+
player.WarpSession = null;
174+
175+
// Cancel any pending trade
176+
if (player.TradeSession != null)
177+
{
178+
var partner = player.CurrentMap?.Players.Values.FirstOrDefault(p =>
179+
p.SessionId == player.TradeSession?.PartnerId);
180+
181+
if (partner != null)
182+
{
183+
partner.TradeSession = null;
184+
partner.PendingTradeRequestFromPlayerId = null;
185+
await partner.Send(new TradeCloseServerPacket());
186+
}
187+
188+
player.TradeSession = null;
189+
player.PendingTradeRequestFromPlayerId = null;
190+
}
191+
171192
if (player.Character is not null && player.CurrentMap is not null)
172193
{
173194
await player.CurrentMap.NotifyLeave(player);
174195
await characterRepository.UpdateAsync(characterMapper.ToDatabase(player.Character));
175196
}
176197

177-
worldState.Players.TryRemove(sessionId, out _);
198+
worldState.TryRemovePlayer(sessionId, out _);
178199
logger.LogInformation("Player disconnected");
179200
UpdateConnectedCount();
180201
}

src/Acorn/Net/PacketHandlers/Chest/ChestAddClientPacketHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ await player.Send(new ChestReplyServerPacket
124124

125125
// Notify other players near the chest
126126
var agreePacket = new ChestAgreeServerPacket { Items = chestItems };
127-
foreach (var otherPlayer in player.CurrentMap.Players.Where(p => p != player))
127+
foreach (var otherPlayer in player.CurrentMap.Players.Values.Where(p => p != player))
128128
{
129129
if (otherPlayer.Character == null) continue;
130130

src/Acorn/Net/PacketHandlers/Chest/ChestTakeClientPacketHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ await player.Send(new ChestGetServerPacket
126126

127127
// Notify other players near the chest
128128
var agreePacket = new ChestAgreeServerPacket { Items = chestItems };
129-
foreach (var otherPlayer in player.CurrentMap.Players.Where(p => p != player))
129+
foreach (var otherPlayer in player.CurrentMap.Players.Values.Where(p => p != player))
130130
{
131131
if (otherPlayer.Character == null) continue;
132132

src/Acorn/Net/PacketHandlers/Player/AttackUseClientPacketHandler.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public async Task HandleAsync(PlayerState playerState, AttackUseClientPacket pac
5656
if (playerState.Character is not null)
5757
{
5858
var nextCoords = playerState.Character.NextCoords();
59-
var target = playerState.CurrentMap.Npcs.FirstOrDefault(x =>
59+
var target = playerState.CurrentMap.Npcs.Values.FirstOrDefault(x =>
6060
!x.IsDead &&
6161
x.X == nextCoords.X && x.Y == nextCoords.Y &&
6262
x.Data.Type is NpcType.Aggressive or NpcType.Passive);
@@ -76,7 +76,7 @@ public async Task HandleAsync(PlayerState playerState, AttackUseClientPacket pac
7676
target.AddOpponent(playerState.SessionId, damage);
7777
}
7878

79-
var npcIndex = playerState.CurrentMap.Npcs.ToList().IndexOf(target);
79+
var npcIndex = playerState.CurrentMap.Npcs.Values.ToList().IndexOf(target);
8080
var hpPercentage = (int)Math.Max((double)target.Hp / target.Data.Hp * 100, 0);
8181

8282
await playerState.CurrentMap.BroadcastPacket(new NpcReplyServerPacket

src/Acorn/Net/PacketHandlers/Player/FacePlayerClientPacketHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public async Task HandleAsync(PlayerState playerState,
2121
return;
2222
}
2323

24-
var broadcast = playerState.CurrentMap.Players.Select(player =>
24+
var broadcast = playerState.CurrentMap.Players.Values.Select(player =>
2525
player.Send(new FacePlayerServerPacket
2626
{
2727
Direction = packet.Direction,

src/Acorn/Net/PacketHandlers/Player/PlayerRangeRequestClientPacketHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ await playerState.Send(new PlayersListServerPacket
1919
{
2020
PlayersList = new PlayersList
2121
{
22-
Players = playerState.CurrentMap.Players.Select(x => x.Character?.AsOnlinePlayer()).ToList()
22+
Players = playerState.CurrentMap.Players.Values.Select(x => x.Character?.AsOnlinePlayer()).ToList()
2323
}
2424
});
2525
}

src/Acorn/Net/PacketHandlers/Player/Talk/SpawnNpcCommandHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ await _notifications.ServerAnnouncement(playerState,
110110
IsAdminSpawned = true
111111
};
112112

113-
playerState.CurrentMap.Npcs.Add(npc);
113+
playerState.CurrentMap.Npcs.TryAdd(npc.Id, npc);
114114
}
115115

116116
await _notifications.ServerAnnouncement(playerState,

src/Acorn/Net/PacketHandlers/Player/Talk/WiseManTalkHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public bool TryHandleMessage(PlayerState playerState, string message)
7777
// Find Wise Man NPC on the map
7878
var map = playerState.CurrentMap;
7979
var wiseManNpc =
80-
map.Npcs.FirstOrDefault(n => n.Data.Name.Contains("Wise Man", StringComparison.OrdinalIgnoreCase));
80+
map.Npcs.Values.FirstOrDefault(n => n.Data.Name.Contains("Wise Man", StringComparison.OrdinalIgnoreCase));
8181
if (wiseManNpc == null)
8282
{
8383
_logger.LogWarning("No Wise Man NPC found on map for player {Player}", playerState.Character.Name);

src/Acorn/Net/PacketHandlers/Trade/TradeAcceptClientPacketHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public async Task HandleAsync(PlayerState player, TradeAcceptClientPacket packet
3333
var partnerPlayerId = packet.PlayerId;
3434

3535
// Find partner player who should have requested to trade with us
36-
var partnerPlayer = player.CurrentMap.Players.FirstOrDefault(p => p.SessionId == partnerPlayerId);
36+
var partnerPlayer = player.CurrentMap.Players.Values.FirstOrDefault(p => p.SessionId == partnerPlayerId);
3737
if (partnerPlayer?.Character == null)
3838
{
3939
logger.LogDebug("Partner player {PartnerId} not found on map", partnerPlayerId);

src/Acorn/Net/PacketHandlers/Trade/TradeRequestClientPacketHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public async Task HandleAsync(PlayerState player, TradeRequestClientPacket packe
2929
var targetPlayerId = packet.PlayerId;
3030

3131
// Find target player on same map
32-
var targetPlayer = player.CurrentMap.Players.FirstOrDefault(p => p.SessionId == targetPlayerId);
32+
var targetPlayer = player.CurrentMap.Players.Values.FirstOrDefault(p => p.SessionId == targetPlayerId);
3333
if (targetPlayer?.Character == null)
3434
{
3535
logger.LogDebug("Target player {TargetId} not found on map", targetPlayerId);

0 commit comments

Comments
 (0)