Skip to content

Commit b3eda0c

Browse files
authored
fix(2053): don't reload relationships for last character (AscensionGameDev#2057)
* fix(2053): don't reload relationships for last character * fix: change fix to a lock model to prevent a multi-character variant of the bug when the server is under extreme load (which I can't easily reproduce locally but can theorize)
1 parent 942ffc4 commit b3eda0c

File tree

8 files changed

+120
-73
lines changed

8 files changed

+120
-73
lines changed

Intersect (Core)/GameObjects/ItemBase.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,13 @@ public static ItemBase[] GetCooldownGroup(string cooldownGroup)
381381
.ToArray();
382382
}
383383

384+
public string GetPaperdollForGender(Gender gender) =>
385+
gender switch
386+
{
387+
Gender.Male => MalePaperdoll,
388+
_ => FemalePaperdoll,
389+
};
390+
384391
private void Initialize()
385392
{
386393
Name = "New Item";

Intersect.Server.Core/Database/DbInterface.cs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -524,14 +524,7 @@ public static Player GetUserCharacter(User user, Guid playerId, bool explicitLoa
524524
try
525525
{
526526
using var playerContext = CreatePlayerContext(readOnly: true, explicitLoad: false);
527-
var playerEntry = playerContext.Players.Attach(player);
528-
playerEntry.Collection(p => p.Items).Query().Load();
529-
playerEntry.Collection(player => player.Bank).Load();
530-
playerEntry.Collection(player => player.Hotbar).Load();
531-
playerEntry.Collection(player => player.Items).Load();
532-
playerEntry.Collection(player => player.Quests).Load();
533-
playerEntry.Collection(player => player.Spells).Load();
534-
playerEntry.Collection(player => player.Variables).Load();
527+
player.LoadRelationships(playerContext);
535528
_ = Player.Validate(player);
536529
}
537530
catch (Exception exception)

Intersect.Server.Core/Database/IntersectDbContext.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ public override int SaveChanges(bool acceptAllChangesOnSuccess)
199199
var rowsChanged = base.SaveChanges(acceptAllChangesOnSuccess);
200200

201201
#if DEBUG
202-
Log.Debug($"DBOP-B SaveChanges({{acceptAllChangesOnSuccess}}) #{{currentExecutionId}}");
202+
Log.Debug($"DBOP-B SaveChanges({acceptAllChangesOnSuccess}) #{currentExecutionId}");
203203
#endif
204204

205205
return rowsChanged;
@@ -240,7 +240,7 @@ public override int SaveChanges(bool acceptAllChangesOnSuccess)
240240
Log.Error(concurrencyErrors.ToString());
241241

242242
#if DEBUG
243-
Log.Debug($"DBOP-C SaveChanges({{acceptAllChangesOnSuccess}}) #{{currentExecutionId}}");
243+
Log.Debug($"DBOP-C SaveChanges({acceptAllChangesOnSuccess}) #{currentExecutionId}");
244244
#endif
245245

246246
ServerContext.DispatchUnhandledException(

Intersect.Server.Core/Entities/Player.Database.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.ComponentModel.DataAnnotations.Schema;
4-
using System.Linq;
1+
using System.ComponentModel.DataAnnotations.Schema;
52
using Intersect.Logging;
63
using Intersect.Server.Database;
74
using Intersect.Server.Database.PlayerData;
@@ -20,6 +17,8 @@ namespace Intersect.Server.Entities
2017

2118
public partial class Player
2219
{
20+
private volatile bool _saving;
21+
private readonly object _savingLock = new();
2322

2423
#region Account
2524

@@ -151,6 +150,15 @@ public static bool PlayerExists(string name)
151150

152151
public void LoadRelationships(PlayerContext playerContext)
153152
{
153+
lock (_savingLock)
154+
{
155+
if (_saving)
156+
{
157+
Log.Warn($"Skipping loading relationships for player {Id} because it is being saved.");
158+
return;
159+
}
160+
}
161+
154162
var entityEntry = playerContext.Players.Attach(this);
155163
entityEntry.Collection(p => p.Bank).Load();
156164
entityEntry.Collection(p => p.Hotbar).Load();

Intersect.Server.Core/Entities/Player.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
1-
using System;
21
using System.Collections.Concurrent;
3-
using System.Collections.Generic;
42
using System.ComponentModel.DataAnnotations.Schema;
53
using System.Diagnostics;
6-
using System.Linq;
7-
using System.Threading;
8-
94
using Intersect.Enums;
105
using Intersect.GameObjects;
116
using Intersect.GameObjects.Crafting;
@@ -407,6 +402,11 @@ public void TryLogout(bool force = false, bool softLogout = false)
407402

408403
private void Logout(bool softLogout = false)
409404
{
405+
lock (_savingLock)
406+
{
407+
_saving = true;
408+
}
409+
410410
if (MapController.TryGetInstanceFromMap(MapId, MapInstanceId, out var instance))
411411
{
412412
instance.RemoveEntity(this);
@@ -511,6 +511,11 @@ public void CompleteLogout()
511511

512512
User?.Save();
513513

514+
lock (_savingLock)
515+
{
516+
_saving = false;
517+
}
518+
514519
Dispose();
515520

516521
#if DIAGNOSTIC

Intersect.Server.Core/Networking/PacketHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -654,7 +654,7 @@ public void HandlePacket(Client client, LogoutPacket packet)
654654
(Options.MaxCharacters > 1 || !Options.Instance.PlayerOpts.SkipCharacterSelect))
655655
{
656656
client.Entity?.TryLogout(false, true);
657-
client.Entity = null;
657+
client.Entity = default;
658658
PacketSender.SendPlayerCharacters(client);
659659
}
660660
else

Intersect.Server.Core/Networking/PacketSender.cs

Lines changed: 85 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1253,9 +1253,23 @@ public static void SendCreateCharacter(Client client)
12531253
//CharactersPacket
12541254
public static void SendPlayerCharacters(Client client)
12551255
{
1256-
var characters = new List<CharacterPacket>();
1257-
if (client.User == null)
1256+
if (client == default)
12581257
{
1258+
Log.Warn($"Tried to {nameof(SendPlayerCharacters)}() to a null client?");
1259+
return;
1260+
}
1261+
1262+
var user = client.User;
1263+
if (user == default)
1264+
{
1265+
Log.Warn($"Tried to {nameof(SendPlayerCharacters)}() to client with no user? ({client.Id})");
1266+
return;
1267+
}
1268+
1269+
var clientCharacters = client.Characters;
1270+
if (clientCharacters == default)
1271+
{
1272+
Log.Warn($"Tried to {nameof(SendPlayerCharacters)}() to client with no characters? (client {client.Id}/user {user.Id})");
12591273
return;
12601274
}
12611275

@@ -1267,73 +1281,92 @@ public static void SendPlayerCharacters(Client client)
12671281
}
12681282
}
12691283

1270-
var clientCharacters = client?.Characters;
1271-
if (clientCharacters == default)
1284+
var characterPackets = new List<CharacterPacket>();
1285+
1286+
var equipmentSlotsOptions = Options.EquipmentSlots;
1287+
var paperdollOrderOptions = Options.PaperdollOrder;
1288+
1289+
if (clientCharacters.Count < 1)
12721290
{
1273-
Log.Error($"PLEASE REPORT THIS WITH LOGS: About to crash because {(client == default ? nameof(client) : nameof(client.Characters))} is null.");
1291+
CharactersPacket emptyBulkCharactersPacket = new(
1292+
Array.Empty<CharacterPacket>(),
1293+
client.Characters.Count < Options.MaxCharacters
1294+
);
1295+
1296+
if (!client.Send(emptyBulkCharactersPacket))
1297+
{
1298+
Log.Error($"Failed to send empty bulk characters packet to {client.Id}");
1299+
}
1300+
1301+
return;
12741302
}
12751303

1276-
if (client.Characters.Count > 0) /* TODO: Fix NRE when logging out and back in */
1304+
foreach (var character in clientCharacters.OrderByDescending(p => p.LastOnline))
12771305
{
1278-
foreach (var character in client.Characters.OrderByDescending(p => p.LastOnline))
1279-
{
1280-
var equipmentArray = character.Equipment;
1281-
var equipment = new EquipmentFragment[Options.EquipmentSlots.Count + 1];
1306+
var equipmentArray = character.Equipment;
1307+
var equipment = new EquipmentFragment[equipmentSlotsOptions.Count + 1];
12821308

1283-
//Draw the equipment/paperdolls
1284-
for (var z = 0; z < Options.PaperdollOrder[1].Count; z++)
1309+
// Draw the equipment/paperdolls
1310+
var paperdollOrderOptionLayer1 = paperdollOrderOptions[1];
1311+
for (var z = 0; z < paperdollOrderOptionLayer1.Count; z++)
1312+
{
1313+
var indexOfPaperdoll = equipmentSlotsOptions.IndexOf(Options.PaperdollOrder[1][z]);
1314+
if (indexOfPaperdoll < 0)
12851315
{
1286-
if (Options.EquipmentSlots.IndexOf(Options.PaperdollOrder[1][z]) > -1)
1287-
{
1288-
if (equipmentArray[Options.EquipmentSlots.IndexOf(Options.PaperdollOrder[1][z])] > -1 &&
1289-
equipmentArray[Options.EquipmentSlots.IndexOf(Options.PaperdollOrder[1][z])] <
1290-
Options.MaxInvItems)
1291-
{
1292-
var paperdollOrder = Options.PaperdollOrder[1][z];
1293-
var equipmentSlot = Options.EquipmentSlots.IndexOf(paperdollOrder);
1294-
var itemIndex = equipmentArray[equipmentSlot];
1295-
1296-
var itemId = character
1297-
.Items[itemIndex]
1298-
.ItemId;
1299-
1300-
if (ItemBase.Get(itemId) != null)
1301-
{
1302-
var itemdata = ItemBase.Get(itemId);
1303-
equipment[z] = new EquipmentFragment
1304-
{
1305-
Name = character.Gender == 0 ? itemdata.MalePaperdoll : itemdata.FemalePaperdoll,
1306-
RenderColor = itemdata.Color,
1307-
};
1308-
}
1309-
}
1310-
}
1311-
else
1316+
const string equipmentFragmentNamePlayer = "Player";
1317+
if (Options.PaperdollOrder[1][z] == equipmentFragmentNamePlayer)
13121318
{
1313-
if (Options.PaperdollOrder[1][z] == "Player")
1314-
{
1315-
equipment[z] = new EquipmentFragment { Name = "Player" };
1316-
}
1319+
equipment[z] = new EquipmentFragment { Name = equipmentFragmentNamePlayer };
13171320
}
1321+
1322+
continue;
13181323
}
13191324

1320-
characters.Add(
1321-
new CharacterPacket(
1322-
character.Id, character.Name, character.Sprite, character.Face, character.Level,
1323-
ClassBase.GetName(character.ClassId), equipment
1324-
)
1325-
);
1325+
var inventoryIndexOfEquip = equipmentArray[indexOfPaperdoll];
1326+
if (inventoryIndexOfEquip <= -1 || inventoryIndexOfEquip >= Options.MaxInvItems)
1327+
{
1328+
continue;
1329+
}
1330+
1331+
var paperdollOrder = paperdollOrderOptionLayer1[z];
1332+
var equipmentSlot = equipmentSlotsOptions.IndexOf(paperdollOrder);
1333+
var itemIndex = equipmentArray[equipmentSlot];
1334+
1335+
var itemId = character.Items[itemIndex].ItemId;
1336+
1337+
if (!ItemBase.TryGet(itemId, out var itemDescriptor))
1338+
{
1339+
continue;
1340+
}
1341+
1342+
equipment[z] = new EquipmentFragment
1343+
{
1344+
Name = itemDescriptor.GetPaperdollForGender(character.Gender),
1345+
RenderColor = itemDescriptor.Color,
1346+
};
13261347
}
1348+
1349+
characterPackets.Add(
1350+
new CharacterPacket(
1351+
character.Id,
1352+
character.Name,
1353+
character.Sprite,
1354+
character.Face,
1355+
character.Level,
1356+
ClassBase.GetName(character.ClassId),
1357+
equipment
1358+
)
1359+
);
13271360
}
13281361

1329-
CharactersPacket packet = new(
1330-
characters.ToArray(),
1362+
CharactersPacket bulkCharactersPacket = new(
1363+
characterPackets.ToArray(),
13311364
client.Characters.Count < Options.MaxCharacters
13321365
);
13331366

1334-
if (!client.Send(packet))
1367+
if (!client.Send(bulkCharactersPacket))
13351368
{
1336-
Log.Error($"Failed to send {packet.Characters.Length} characters to {client.Id}");
1369+
Log.Error($"Failed to send {bulkCharactersPacket.Characters.Length} characters to {client.Id}");
13371370
}
13381371
}
13391372

Intersect.sln.DotSettings

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<s:Boolean x:Key="/Default/UserDictionary/Words/=cipherdata/@EntryIndexedValue">True</s:Boolean>
44
<s:Boolean x:Key="/Default/UserDictionary/Words/=Expiries/@EntryIndexedValue">True</s:Boolean>
55
<s:Boolean x:Key="/Default/UserDictionary/Words/=Migratable/@EntryIndexedValue">True</s:Boolean>
6+
<s:Boolean x:Key="/Default/UserDictionary/Words/=Paperdoll/@EntryIndexedValue">True</s:Boolean>
67
<s:Boolean x:Key="/Default/UserDictionary/Words/=plaindata/@EntryIndexedValue">True</s:Boolean>
78
<s:Boolean x:Key="/Default/UserDictionary/Words/=randomblob/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
8-
<s:Boolean x:Key="/Default/UserDictionary/Words/=singleplayer/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
9+

0 commit comments

Comments
 (0)