perf: cache the resolved speed entry per creature
Serialization consults the speed table four times per mob per save (the Should* flag checks) and again through the Default* methods on elided loads - each a SpeedClass/type dictionary walk. The resolved SpeedClassEntry is now cached on the creature (one reference; the table is immutable after Configure), so those become a null-check and field reads. GetSpeeds/GetMoveSpeeds stay the virtual override point, so stubs and forks that override them still steer elision; only their default implementations read the cache. NPCSpeeds' per-call lookups collapse into FindEntry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
f46780d554
commit
f2f8313b42
2 changed files with 22 additions and 22 deletions
|
|
@ -5078,14 +5078,29 @@ namespace Server.Mobiles
|
|||
// If this needs to be serialized, recommend creating a hash or registry id. Don't serialize strings.
|
||||
public virtual SpeedLevel SpeedClass => SpeedLevel.None;
|
||||
|
||||
// Resolved once per creature; serialization consults the table four times per mob
|
||||
// per save (and again on elided loads), so the dictionary walk must not repeat.
|
||||
private NPCSpeeds.SpeedClassEntry _speedEntry;
|
||||
|
||||
private NPCSpeeds.SpeedClassEntry SpeedEntry => _speedEntry ??= NPCSpeeds.FindEntry(this);
|
||||
|
||||
public virtual void GetSpeeds(out double activeSpeed, out double passiveSpeed)
|
||||
{
|
||||
NPCSpeeds.GetSpeeds(this, out activeSpeed, out passiveSpeed);
|
||||
var entry = SpeedEntry ?? throw new InvalidOperationException(
|
||||
$"{GetType()} has no speed entry - is {"Data/npc-speeds.json"} missing?"
|
||||
);
|
||||
|
||||
activeSpeed = entry.ActiveSpeed;
|
||||
passiveSpeed = entry.PassiveSpeed;
|
||||
}
|
||||
|
||||
// Move speeds are optional (0 = inherit), so this tolerates an unloaded table.
|
||||
public virtual void GetMoveSpeeds(out double activeMoveSpeed, out double passiveMoveSpeed)
|
||||
{
|
||||
NPCSpeeds.GetMoveSpeeds(this, out activeMoveSpeed, out passiveMoveSpeed);
|
||||
var entry = SpeedEntry;
|
||||
|
||||
activeMoveSpeed = entry?.ActiveMoveSpeed ?? 0;
|
||||
passiveMoveSpeed = entry?.PassiveMoveSpeed ?? 0;
|
||||
}
|
||||
|
||||
// Pre-v22 saves carry no movement clock. Think speeds matching today's GetSpeeds
|
||||
|
|
|
|||
|
|
@ -26,32 +26,17 @@ public static class NPCSpeeds
|
|||
public static int MinIdleSeconds { get; private set; }
|
||||
public static int MaxIdleSeconds { get; private set; }
|
||||
|
||||
public static void GetSpeeds(BaseCreature bc, out double activeSpeed, out double passiveSpeed)
|
||||
// Null when the table is unloaded (test fixtures). Creatures cache the result — the
|
||||
// table is immutable after Configure.
|
||||
public static SpeedClassEntry FindEntry(BaseCreature bc)
|
||||
{
|
||||
if ((bc.SpeedClass == SpeedLevel.None || !_speedsByLevel.TryGetValue(bc.SpeedClass, out var sp)) &&
|
||||
!_speedsByType.TryGetValue(bc.GetType(), out sp))
|
||||
{
|
||||
sp = _speedsByLevel[SpeedLevel.Medium];
|
||||
_speedsByLevel.TryGetValue(SpeedLevel.Medium, out sp);
|
||||
}
|
||||
|
||||
activeSpeed = sp.ActiveSpeed;
|
||||
passiveSpeed = sp.PassiveSpeed;
|
||||
}
|
||||
|
||||
// Move speeds are optional (0 = inherit), so this tolerates a missing entry or table.
|
||||
public static void GetMoveSpeeds(BaseCreature bc, out double activeMoveSpeed, out double passiveMoveSpeed)
|
||||
{
|
||||
if ((bc.SpeedClass == SpeedLevel.None || !_speedsByLevel.TryGetValue(bc.SpeedClass, out var sp)) &&
|
||||
!_speedsByType.TryGetValue(bc.GetType(), out sp) &&
|
||||
!_speedsByLevel.TryGetValue(SpeedLevel.Medium, out sp))
|
||||
{
|
||||
activeMoveSpeed = 0;
|
||||
passiveMoveSpeed = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
activeMoveSpeed = sp.ActiveMoveSpeed;
|
||||
passiveMoveSpeed = sp.PassiveMoveSpeed;
|
||||
return sp;
|
||||
}
|
||||
|
||||
public static void RegisterSpeed(SpeedClassEntry entry)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue