Skip to content

Commit 08221d4

Browse files
committed
Optimizations to Collections
1 parent bd66bb3 commit 08221d4

File tree

17 files changed

+422
-256
lines changed

17 files changed

+422
-256
lines changed

Zolian.Server.Base/Common/Extensions.cs

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
using Darkages.Enums;
2-
using Darkages.Sprites;
3-
using Darkages.Sprites.Entity;
4-
1+
using System.Collections.Concurrent;
52
using System.Numerics;
63
using System.Text;
74
using System.Text.RegularExpressions;
85

6+
using Darkages.Enums;
7+
using Darkages.Sprites;
8+
using Darkages.Sprites.Entity;
9+
910
namespace Darkages.Common;
1011

1112
public static class Extensions
@@ -180,4 +181,71 @@ public static T NextLowest<T>(this IEnumerable<T> enumerable, T seed) where T :
180181

181182
return current;
182183
}
184+
185+
/// <summary>
186+
/// Retrieves the first script instance from the specified dictionary, or null if the dictionary is null or empty.
187+
/// </summary>
188+
/// <typeparam name="TScript">The type of script to retrieve. Must be a reference type.</typeparam>
189+
/// <param name="dict">A thread-safe dictionary containing script instances, keyed by string. Can be null.</param>
190+
/// <returns>The first script instance found in the dictionary, or null if the dictionary is null or contains no elements.</returns>
191+
public static TScript? GetFirstScript<TScript>(ConcurrentDictionary<string, TScript>? dict) where TScript : class
192+
{
193+
if (dict == null) return null;
194+
foreach (var kvp in dict)
195+
return kvp.Value;
196+
197+
return null;
198+
}
199+
200+
/// <summary>
201+
/// Attempts to retrieve the element at the specified index in the array.
202+
/// </summary>
203+
/// <remarks>This method does not throw an exception if the index is outside the bounds of the array.
204+
/// Instead, it returns <see langword="false"/> and sets <paramref name="value"/> to the default value for the
205+
/// type.</remarks>
206+
/// <typeparam name="T">The type of elements in the array.</typeparam>
207+
/// <param name="array">The array from which to retrieve the value. Cannot be null.</param>
208+
/// <param name="index">The zero-based index of the element to retrieve.</param>
209+
/// <param name="value">When this method returns, contains the element at the specified index if the index is within the bounds of the
210+
/// array; otherwise, the default value for the type of the array elements.</param>
211+
/// <returns><see langword="true"/> if the element at the specified index was found; otherwise, <see langword="false"/>.</returns>
212+
public static bool TryGetValue<T>(this T[] array, int index, out T value)
213+
{
214+
if ((uint)index < (uint)array.Length)
215+
{
216+
value = array[index];
217+
return true;
218+
}
219+
220+
value = default!;
221+
return false;
222+
}
223+
224+
/// <summary>
225+
/// Attempts to find the first element in the array that matches the specified predicate.
226+
/// </summary>
227+
/// <remarks>If multiple elements match the predicate, only the first occurrence is returned. This method
228+
/// does not throw an exception if no match is found; instead, it returns false and sets the out parameter to the
229+
/// default value of the type.</remarks>
230+
/// <typeparam name="T">The type of elements in the array.</typeparam>
231+
/// <param name="array">The array to search for a matching element. Cannot be null.</param>
232+
/// <param name="predicate">A function that defines the conditions of the element to search for. Cannot be null.</param>
233+
/// <param name="value">When this method returns, contains the first element that matches the predicate, if found; otherwise, the
234+
/// default value for the type of the element.</param>
235+
/// <returns>true if a matching element is found; otherwise, false.</returns>
236+
public static bool TryGetValue<T>(this T[] array, Func<T, bool> predicate, out T value)
237+
{
238+
for (int i = 0; i < array.Length; i++)
239+
{
240+
var item = array[i];
241+
if (predicate(item))
242+
{
243+
value = item;
244+
return true;
245+
}
246+
}
247+
248+
value = default!;
249+
return false;
250+
}
183251
}

Zolian.Server.Base/Database/DatabaseLoad.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,8 @@ private static void Monsters(string conn)
471471

472472
ServerSetup.Instance.GlobalMonsterTemplateCache = ServerSetup.Instance.TempGlobalMonsterTemplateCache.ToFrozenDictionary();
473473
ServerSetup.Instance.TempGlobalMonsterTemplateCache.Clear();
474+
ServerSetup.Instance.MonsterTemplateByMapCache = ServerSetup.Instance.TempMonsterTemplateByMapCache.ToFrozenDictionary(kvp => kvp.Key, kvp => kvp.Value.ToArray());
475+
ServerSetup.Instance.TempMonsterTemplateByMapCache.Clear();
474476
ServerSetup.EventsLogger($"Monster Templates: {ServerSetup.Instance.GlobalMonsterTemplateCache.Count}");
475477
}
476478

Zolian.Server.Base/GameScripts/Affects/DebuffAffects.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1614,7 +1614,7 @@ public override void OnEnded(Sprite affected, Debuff debuff)
16141614
}
16151615

16161616
if (affected is not Monster monster) return;
1617-
monster.Scripts?.Values.FirstOrDefault(_ => monster.IsAlive)?.OnDeath();
1617+
monster.AIScript?.OnDeath();
16181618
}
16191619
}
16201620

Zolian.Server.Base/GameScripts/Areas/ShadowTower/ShadowGames.cs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
using Darkages.Enums;
1+
using System.Numerics;
2+
3+
using Darkages.Common;
4+
using Darkages.Enums;
25
using Darkages.Network.Client;
3-
using Darkages.ScriptingBase;
4-
using Darkages.Types;
5-
using System.Numerics;
6-
using Darkages.Object;
76
using Darkages.Network.Server;
7+
using Darkages.Object;
8+
using Darkages.ScriptingBase;
89
using Darkages.Sprites.Entity;
10+
using Darkages.Types;
911

1012
namespace Darkages.GameScripts.Areas.ShadowTower;
1113

@@ -22,8 +24,10 @@ public override void OnPlayerWalk(WorldClient client, Position oldLocation, Posi
2224
var vectorMap = new Vector2(newLocation.X, newLocation.Y);
2325
if (client.Aisling.Pos != vectorMap) return;
2426

25-
ServerSetup.Instance.GlobalMonsterTemplateCache.TryGetValue("Rob33", out var boss1);
26-
ServerSetup.Instance.GlobalMonsterTemplateCache.TryGetValue("Rob32", out var boss2);
27+
// Per-map monster template cache
28+
if (!ServerSetup.Instance.MonsterTemplateByMapCache.TryGetValue(client.Aisling.Map.ID, out var templates) || templates.Length == 0) return;
29+
templates.TryGetValue(t => t.Name == "Rob32", out var boss2);
30+
templates.TryGetValue(t => t.Name == "Rob33", out var boss1);
2731

2832
switch (newLocation.X)
2933
{

Zolian.Server.Base/GameScripts/Areas/ShadowTower/ShadowTowerOmega.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Darkages.Enums;
1+
using Darkages.Common;
2+
using Darkages.Enums;
23
using Darkages.Network.Client;
34
using Darkages.ScriptingBase;
45
using Darkages.Types;
@@ -24,7 +25,10 @@ public override void OnPlayerWalk(WorldClient client, Position oldLocation, Posi
2425
var vectorMap = new Vector2(newLocation.X, newLocation.Y);
2526
if (client.Aisling.Pos != vectorMap) return;
2627

27-
ServerSetup.Instance.GlobalMonsterTemplateCache.TryGetValue("Rob34", out var boss);
28+
// Per-map monster template cache
29+
if (!ServerSetup.Instance.MonsterTemplateByMapCache.TryGetValue(client.Aisling.Map.ID, out var templates) || templates.Length == 0) return;
30+
templates.TryGetValue(t => t.Name == "Rob34", out var boss);
31+
2832
var mobsOnMap = client.Aisling.MonstersOnMap();
2933
if (!mobsOnMap.IsNullOrEmpty()) return;
3034
var bossCreate = Monster.Create(boss, client.Aisling.Map);

Zolian.Server.Base/GameScripts/Monsters/AdvancedMonsterAI.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2829,7 +2829,10 @@ public override void OnApproach(WorldClient client)
28292829
if (Monster.SwarmOnApproach) return;
28302830
Monster.SwarmOnApproach = true;
28312831
Task.Delay(500).Wait();
2832-
ServerSetup.Instance.GlobalMonsterTemplateCache.TryGetValue("SRat0", out var rat);
2832+
2833+
// Per-map monster template cache
2834+
if (!ServerSetup.Instance.MonsterTemplateByMapCache.TryGetValue(client.Aisling.Map.ID, out var templates) || templates.Length == 0) return;
2835+
templates.TryGetValue(t => t.Name == "SRat0", out var rat);
28332836

28342837
for (var i = 0; i < Random.Shared.Next(1, 2); i++)
28352838
{
@@ -3120,11 +3123,14 @@ public override void OnApproach(WorldClient client)
31203123
if (Monster.SwarmOnApproach) return;
31213124
Monster.SwarmOnApproach = true;
31223125
Task.Delay(500).Wait();
3123-
ServerSetup.Instance.GlobalMonsterTemplateCache.TryGetValue("SkSpS1", out var rat);
3126+
3127+
// Per-map monster template cache
3128+
if (!ServerSetup.Instance.MonsterTemplateByMapCache.TryGetValue(client.Aisling.Map.ID, out var templates) || templates.Length == 0) return;
3129+
templates.TryGetValue(t => t.Name == "SkSpS1", out var beetle);
31243130

31253131
for (var i = 0; i < Random.Shared.Next(1, 2); i++)
31263132
{
3127-
var summoned = Monster.Create(rat, Monster.Map);
3133+
var summoned = Monster.Create(beetle, Monster.Map);
31283134
if (summoned == null) return;
31293135
summoned.X = Monster.X + Random.Shared.Next(0, 4);
31303136
summoned.Y = Monster.Y + Random.Shared.Next(0, 4);

Zolian.Server.Base/GameScripts/Monsters/BossMonsterAI.cs

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
using Darkages.Common;
1+
using System.Security.Cryptography;
2+
3+
using Darkages.Common;
24
using Darkages.Enums;
35
using Darkages.Network.Client;
46
using Darkages.Network.Server;
57
using Darkages.ScriptingBase;
68
using Darkages.Sprites;
79
using Darkages.Sprites.Entity;
810
using Darkages.Types;
9-
using System.Security.Cryptography;
1011

1112
namespace Darkages.GameScripts.Monsters;
1213

@@ -662,9 +663,11 @@ private void UpdatePhases()
662663
if (Monster.CurrentHp <= Monster.MaximumHp * 0.75 && !_phaseOne)
663664
{
664665
Monster.SendTargetedClientMethod(PlayerScope.NearbyAislings, c => c.SendPublicMessage(Monster.Serial, PublicMessageType.Shout, $"{Monster.Name}: AHHHHH That Hurts! You made Yeti Mad!"));
665-
var foundA = ServerSetup.Instance.GlobalMonsterTemplateCache.TryGetValue("SnowmanA", out var templateA);
666-
var foundB = ServerSetup.Instance.GlobalMonsterTemplateCache.TryGetValue("SnowmanB", out var templateB);
667-
var foundC = ServerSetup.Instance.GlobalMonsterTemplateCache.TryGetValue("SnowmanC", out var templateC);
666+
// Per-map monster template cache
667+
if (!ServerSetup.Instance.MonsterTemplateByMapCache.TryGetValue(Monster.Map.ID, out var templates) || templates.Length == 0) return;
668+
var foundA = templates.TryGetValue(t => t.Name == "SnowmanA", out var templateA);
669+
var foundB = templates.TryGetValue(t => t.Name == "SnowmanB", out var templateB);
670+
var foundC = templates.TryGetValue(t => t.Name == "SnowmanC", out var templateC);
668671

669672
if (foundA) Monster.CreateFromTemplate(templateA, Monster.Map);
670673
if (foundB) Monster.CreateFromTemplate(templateB, Monster.Map);
@@ -676,11 +679,13 @@ private void UpdatePhases()
676679
if (Monster.CurrentHp <= Monster.MaximumHp * 0.50 && !_phaseTwo)
677680
{
678681
Monster.SendTargetedClientMethod(PlayerScope.NearbyAislings, c => c.SendPublicMessage(Monster.Serial, PublicMessageType.Shout, $"{Monster.Name}: AHHHHH That Hurts! You made Yeti Really Mad!"));
679-
var foundA = ServerSetup.Instance.GlobalMonsterTemplateCache.TryGetValue("SnowmanA", out var templateA);
680-
var foundB = ServerSetup.Instance.GlobalMonsterTemplateCache.TryGetValue("SnowmanB", out var templateB);
681-
var foundC = ServerSetup.Instance.GlobalMonsterTemplateCache.TryGetValue("SnowmanC", out var templateC);
682-
var foundD = ServerSetup.Instance.GlobalMonsterTemplateCache.TryGetValue("SnowmanD", out var templateD);
683-
var foundE = ServerSetup.Instance.GlobalMonsterTemplateCache.TryGetValue("SnowmanE", out var templateE);
682+
// Per-map monster template cache
683+
if (!ServerSetup.Instance.MonsterTemplateByMapCache.TryGetValue(Monster.Map.ID, out var templates) || templates.Length == 0) return;
684+
var foundA = templates.TryGetValue(t => t.Name == "SnowmanA", out var templateA);
685+
var foundB = templates.TryGetValue(t => t.Name == "SnowmanB", out var templateB);
686+
var foundC = templates.TryGetValue(t => t.Name == "SnowmanC", out var templateC);
687+
var foundD = templates.TryGetValue(t => t.Name == "SnowmanD", out var templateD);
688+
var foundE = templates.TryGetValue(t => t.Name == "SnowmanE", out var templateE);
684689

685690
if (foundA) Monster.CreateFromTemplate(templateA, Monster.Map);
686691
if (foundB) Monster.CreateFromTemplate(templateB, Monster.Map);
@@ -694,13 +699,15 @@ private void UpdatePhases()
694699
if (Monster.CurrentHp <= Monster.MaximumHp * 0.25 && !_phaseThree)
695700
{
696701
Monster.SendTargetedClientMethod(PlayerScope.NearbyAislings, c => c.SendPublicMessage(Monster.Serial, PublicMessageType.Shout, $"{Monster.Name}: AHHHHH That Hurts! Time to die!!"));
697-
var foundA = ServerSetup.Instance.GlobalMonsterTemplateCache.TryGetValue("SnowmanA", out var templateA);
698-
var foundB = ServerSetup.Instance.GlobalMonsterTemplateCache.TryGetValue("SnowmanB", out var templateB);
699-
var foundC = ServerSetup.Instance.GlobalMonsterTemplateCache.TryGetValue("SnowmanC", out var templateC);
700-
var foundD = ServerSetup.Instance.GlobalMonsterTemplateCache.TryGetValue("SnowmanD", out var templateD);
701-
var foundE = ServerSetup.Instance.GlobalMonsterTemplateCache.TryGetValue("SnowmanE", out var templateE);
702-
var foundF = ServerSetup.Instance.GlobalMonsterTemplateCache.TryGetValue("SnowmanF", out var templateF);
703-
var foundG = ServerSetup.Instance.GlobalMonsterTemplateCache.TryGetValue("SnowmanG", out var templateG);
702+
// Per-map monster template cache
703+
if (!ServerSetup.Instance.MonsterTemplateByMapCache.TryGetValue(Monster.Map.ID, out var templates) || templates.Length == 0) return;
704+
var foundA = templates.TryGetValue(t => t.Name == "SnowmanA", out var templateA);
705+
var foundB = templates.TryGetValue(t => t.Name == "SnowmanB", out var templateB);
706+
var foundC = templates.TryGetValue(t => t.Name == "SnowmanC", out var templateC);
707+
var foundD = templates.TryGetValue(t => t.Name == "SnowmanD", out var templateD);
708+
var foundE = templates.TryGetValue(t => t.Name == "SnowmanE", out var templateE);
709+
var foundF = templates.TryGetValue(t => t.Name == "SnowmanF", out var templateF);
710+
var foundG = templates.TryGetValue(t => t.Name == "SnowmanG", out var templateG);
704711

705712
if (foundA) Monster.CreateFromTemplate(templateA, Monster.Map);
706713
if (foundB) Monster.CreateFromTemplate(templateB, Monster.Map);

Zolian.Server.Base/Network/Components/MonolithComponent.cs

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Diagnostics;
22
using System.Numerics;
3+
34
using Darkages.Common;
45
using Darkages.Enums;
56
using Darkages.Network.Server;
@@ -53,24 +54,44 @@ private void ManageSpawns()
5354
PlaceNode(map);
5455
PlaceFlower(map);
5556

56-
// Monsters
57-
var monstersOnMap = ObjectManager
58-
.GetObjects<Monster>(map, m => m.IsAlive)
59-
.Values
60-
.ToList();
57+
// Templates for this map only
58+
if (!ServerSetup.Instance.MonsterTemplateByMapCache.TryGetValue(map.ID, out var templates) || templates.Length == 0) continue;
59+
60+
var monsters = ObjectManager.GetObjects<Monster>(map, m => m.IsAlive);
6161

6262
// Map based overload guard
6363
var maxMonsters = CalculateMaxMonsters(map);
64-
if (monstersOnMap.Count >= maxMonsters) continue;
64+
if (monsters.Count >= maxMonsters) continue;
65+
66+
var countsByName = new Dictionary<string, int>(StringComparer.Ordinal);
6567

66-
foreach (var template in ServerSetup.Instance.GlobalMonsterTemplateCache.Values)
68+
// Count existing monsters by template name
69+
foreach (var kvp in monsters)
6770
{
68-
if (template.AreaID != map.ID) continue;
69-
var count = monstersOnMap.Count(m => m.Template.Name == template.Name);
71+
var monster = kvp.Value;
72+
var name = monster?.Template?.Name;
73+
if (string.IsNullOrEmpty(name))
74+
continue;
75+
76+
countsByName.TryGetValue(name, out var c);
77+
countsByName[name] = c + 1;
78+
}
79+
80+
var remaining = maxMonsters - monsters.Count;
81+
82+
foreach (var template in templates)
83+
{
84+
if (remaining <= 0) break;
85+
86+
countsByName.TryGetValue(template.Name, out var count);
87+
7088
if (count >= template.SpawnMax) continue;
7189
if (!template.ReadyToSpawn()) continue;
7290

7391
CreateFromTemplate(template, map);
92+
93+
countsByName[template.Name] = count + 1;
94+
remaining--;
7495
}
7596
}
7697
}

Zolian.Server.Base/Network/Components/ObjectComponent.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ private static void RemoveObject(Aisling self, Sprite obj)
134134

135135
if (obj is Monster monster)
136136
{
137-
var script = monster.Scripts?.Values.FirstOrDefault();
137+
var script = monster.AIScript;
138138
script?.OnLeave(self.Client);
139139
}
140140

@@ -158,7 +158,7 @@ private static List<Sprite> AddObjects(List<Sprite> payload, Aisling self)
158158
switch (obj)
159159
{
160160
case Monster monster:
161-
monster.Scripts?.Values.FirstOrDefault()?.OnApproach(self.Client);
161+
monster.AIScript?.OnApproach(self.Client);
162162
break;
163163

164164
case Mundane npc:

Zolian.Server.Base/Network/Server/ServerSetup.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ public class ServerSetup : IServerContext
6666
public Dictionary<string, NationTemplate> TempGlobalNationTemplateCache { get; set; } = [];
6767
public FrozenDictionary<string, MonsterTemplate> GlobalMonsterTemplateCache { get; set; }
6868
public Dictionary<string, MonsterTemplate> TempGlobalMonsterTemplateCache { get; set; } = [];
69+
public FrozenDictionary<int, MonsterTemplate[]> MonsterTemplateByMapCache { get; set; }
70+
public Dictionary<int, List<MonsterTemplate>> TempMonsterTemplateByMapCache { get; set; } = [];
6971
public FrozenDictionary<string, MundaneTemplate> GlobalMundaneTemplateCache { get; set; }
7072
public Dictionary<string, MundaneTemplate> TempGlobalMundaneTemplateCache { get; set; } = [];
7173
public FrozenDictionary<uint, string> GlobalKnownGoodActorsCache { get; set; }

0 commit comments

Comments
 (0)