diff --git a/src/engine/mobileAI/Threads/MobAIThread.java b/src/engine/mobileAI/Threads/MobAIThread.java index b3699879..9c1d0a66 100644 --- a/src/engine/mobileAI/Threads/MobAIThread.java +++ b/src/engine/mobileAI/Threads/MobAIThread.java @@ -14,6 +14,21 @@ public class MobAIThread implements Runnable{ public static int AI_PULSE_MOB_THRESHOLD = 200; public static int AI_PATROL_DIVISOR = 15; public static float AI_CAST_FREQUENCY; + + /** + * Fixed-rate pacing for one full AI pass, in milliseconds (default 10 passes + * per second). + * + * The pass used to run with no delay whatsoever - `while (true)` straight into + * a sweep of every zone and every mob - which pinned one core for the entire + * lifetime of the world server (~100% of a core even in an empty world, two + * cores together with MobRespawnThread). Individual actions inside + * MobAI.DetermineAction are already gated by timestamps (lastAttackTime, + * nextCastTime, patrol delays), so running the sweep at a fixed rate removes + * the busy spin without changing behaviour. Raise this value to trade AI + * reaction time for CPU. + */ + public static int AI_TICK_INTERVAL_MS = 100; // Thread constructor public MobAIThread() { @@ -27,6 +42,9 @@ public class MobAIThread implements Runnable{ AI_CAST_FREQUENCY = Float.parseFloat(ConfigManager.MB_AI_CAST_FREQUENCY.getValue()); AI_BASE_AGGRO_RANGE = (int)(60 * Float.parseFloat(ConfigManager.MB_AI_AGGRO_RANGE.getValue())); while (true) { + + long tickStart = System.currentTimeMillis(); + for (Zone zone : ZoneManager.getAllZones()) { for (Mob mob : zone.zoneMobSet) { @@ -40,6 +58,16 @@ public class MobAIThread implements Runnable{ } } } + + long sleep = AI_TICK_INTERVAL_MS - (System.currentTimeMillis() - tickStart); + + if (sleep > 0) { + try { + Thread.sleep(sleep); + } catch (InterruptedException ie) { + return; + } + } } } public static void startAIThread() { diff --git a/src/engine/mobileAI/Threads/MobRespawnThread.java b/src/engine/mobileAI/Threads/MobRespawnThread.java index 24e2f281..aa5d3334 100644 --- a/src/engine/mobileAI/Threads/MobRespawnThread.java +++ b/src/engine/mobileAI/Threads/MobRespawnThread.java @@ -25,6 +25,13 @@ import org.pmw.tinylog.Logger; public class MobRespawnThread implements Runnable { + /** + * Fixed-rate pacing for one respawn sweep, in milliseconds. The sweep used to + * run with no delay at all, pinning a core for the lifetime of the world server + * (the 100 ms check below only throttles how often a mob may actually respawn, + * it never paused the loop). Keep this at or below the 100 ms respawn throttle. + */ + public static int RESPAWN_TICK_INTERVAL_MS = 100; public MobRespawnThread() { Logger.info(" MobRespawnThread thread has started!"); @@ -36,6 +43,8 @@ public class MobRespawnThread implements Runnable { while (true) { + long tickStart = System.currentTimeMillis(); + try { for (Zone zone : ZoneManager.getAllZones()) { @@ -55,6 +64,15 @@ public class MobRespawnThread implements Runnable { Logger.error(e); } + long sleep = RESPAWN_TICK_INTERVAL_MS - (System.currentTimeMillis() - tickStart); + + if (sleep > 0) { + try { + Thread.sleep(sleep); + } catch (InterruptedException ie) { + return; + } + } } } public static void startRespawnThread() {