From 8119515705e6f80bb36d5aa4c9b0ab0f1693d8b6 Mon Sep 17 00:00:00 2001 From: Kam Date: Thu, 12 Mar 2026 15:47:56 -0400 Subject: [PATCH] Add Script Optimizations / Caching --- src/main/java/noppes/npcs/EventHooks.java | 19 ++++++++++-- .../npcs/controllers/ScriptContainer.java | 31 +++++++++++++------ .../npcs/controllers/data/DataScript.java | 11 +++++-- .../controllers/data/ForgeDataScript.java | 18 +++++++++++ .../npcs/controllers/data/IScriptUnit.java | 7 +++++ .../java/noppes/npcs/scripted/NpcAPI.java | 3 ++ 6 files changed, 74 insertions(+), 15 deletions(-) diff --git a/src/main/java/noppes/npcs/EventHooks.java b/src/main/java/noppes/npcs/EventHooks.java index 92475f8b3..e07facff0 100644 --- a/src/main/java/noppes/npcs/EventHooks.java +++ b/src/main/java/noppes/npcs/EventHooks.java @@ -92,6 +92,7 @@ import noppes.npcs.scripted.item.ScriptItemStack; import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; +import java.util.HashMap; public class EventHooks { public EventHooks() { @@ -1053,12 +1054,24 @@ public class EventHooks { NpcAPI.EVENT_BUS.post(event); } + private static final HashMap, String> forgeEventNameCache = new HashMap<>(); + + private static String getForgeEventName(Event event) { + Class clazz = event.getClass(); + String cached = forgeEventNameCache.get(clazz); + if (cached != null) + return cached; + String eventName = clazz.getName(); + int i = eventName.lastIndexOf("."); + eventName = StringUtils.uncapitalize(eventName.substring(i + 1).replace("$", "")); + forgeEventNameCache.put(clazz, eventName); + return eventName; + } + public static void onForgeEvent(ForgeEvent ev, Event event) { ForgeDataScript handler = ScriptController.Instance.forgeScripts; if (handler.isEnabled()) { - String eventName = event.getClass().getName(); - int i = eventName.lastIndexOf("."); - eventName = StringUtils.uncapitalize(eventName.substring(i + 1).replace("$", "")); + String eventName = getForgeEventName(event); if (event.isCancelable()) { ev.setCanceled(event.isCanceled()); } diff --git a/src/main/java/noppes/npcs/controllers/ScriptContainer.java b/src/main/java/noppes/npcs/controllers/ScriptContainer.java index 57c0a0ca1..77f25efd0 100644 --- a/src/main/java/noppes/npcs/controllers/ScriptContainer.java +++ b/src/main/java/noppes/npcs/controllers/ScriptContainer.java @@ -46,6 +46,7 @@ public class ScriptContainer implements IScriptUnit { private static Method luaCall; private CompiledScript compScript = null; private final HashMap cachedFunctions = new HashMap<>(); + private long lastGlobalsVersion = -1; /** * Persisted per-container language. Null means inherit from handler default. @@ -224,6 +225,7 @@ public class ScriptContainer implements IScriptUnit { if (ScriptController.Instance.lastLoaded > this.lastCreated) { this.lastCreated = ScriptController.Instance.lastLoaded; evaluated = false; + lastGlobalsVersion = -1; } synchronized (lock) { @@ -236,18 +238,24 @@ public class ScriptContainer implements IScriptUnit { engine.getContext().setWriter(pw); engine.getContext().setErrorWriter(pw); - // Populate bindings with API objects + // Populate bindings with API objects (skip if globals unchanged) + long currentGlobalsVersion = NpcAPI.engineObjectsVersion; + boolean globalsChanged = lastGlobalsVersion != currentGlobalsVersion; if (instanceBindings != null) { - instanceBindings.put("API", NpcAPI.Instance()); - HashMap engineEntries = new HashMap<>(NpcAPI.engineObjects); - for (Map.Entry objectEntry : engineEntries.entrySet()) { - instanceBindings.put(objectEntry.getKey(), objectEntry.getValue()); + if (globalsChanged) { + instanceBindings.put("API", NpcAPI.Instance()); + for (Map.Entry objectEntry : NpcAPI.engineObjects.entrySet()) { + instanceBindings.put(objectEntry.getKey(), objectEntry.getValue()); + } + lastGlobalsVersion = currentGlobalsVersion; } } else { - engine.put("API", NpcAPI.Instance()); - HashMap engineEntries = new HashMap<>(NpcAPI.engineObjects); - for (Map.Entry objectEntry : engineEntries.entrySet()) { - engine.put(objectEntry.getKey(), objectEntry.getValue()); + if (globalsChanged) { + engine.put("API", NpcAPI.Instance()); + for (Map.Entry objectEntry : NpcAPI.engineObjects.entrySet()) { + engine.put(objectEntry.getKey(), objectEntry.getValue()); + } + lastGlobalsVersion = currentGlobalsVersion; } } @@ -328,6 +336,11 @@ public class ScriptContainer implements IScriptUnit { return !this.getFullCode().isEmpty(); } + @Override + public boolean isUnknownFunction(String type) { + return unknownFunctions.contains(type); + } + public void setEngine(String scriptLanguage) { if (!Objects.equals(scriptLanguage, this.currentScriptLanguage)) { // Instance scopes share the template's engine — never create a new one diff --git a/src/main/java/noppes/npcs/controllers/data/DataScript.java b/src/main/java/noppes/npcs/controllers/data/DataScript.java index 7d9fb5c3a..1d98933ef 100644 --- a/src/main/java/noppes/npcs/controllers/data/DataScript.java +++ b/src/main/java/noppes/npcs/controllers/data/DataScript.java @@ -45,6 +45,7 @@ public class DataScript implements IScriptHandlerPacket { public String scriptLanguage = "ECMAScript"; private EntityNPCInterface npc; public boolean enabled = false; + private long lastGlobalsVersion = -1; public ICustomNpc dummyNpc; public IWorld dummyWorld; @@ -271,9 +272,13 @@ public class DataScript implements IScriptHandlerPacket { engine.put(entry.getKey(), entry.getValue().value); } - engine.put("API", NpcAPI.Instance()); - for (Map.Entry engineObjects : NpcAPI.engineObjects.entrySet()) { - engine.put(engineObjects.getKey(), engineObjects.getValue()); + long currentGlobalsVersion = NpcAPI.engineObjectsVersion; + if (lastGlobalsVersion != currentGlobalsVersion) { + engine.put("API", NpcAPI.Instance()); + for (Map.Entry engineObjects : NpcAPI.engineObjects.entrySet()) { + engine.put(engineObjects.getKey(), engineObjects.getValue()); + } + lastGlobalsVersion = currentGlobalsVersion; } } diff --git a/src/main/java/noppes/npcs/controllers/data/ForgeDataScript.java b/src/main/java/noppes/npcs/controllers/data/ForgeDataScript.java index 26685aee8..067474a0f 100644 --- a/src/main/java/noppes/npcs/controllers/data/ForgeDataScript.java +++ b/src/main/java/noppes/npcs/controllers/data/ForgeDataScript.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; public class ForgeDataScript extends MultiScriptHandler { @@ -32,6 +33,7 @@ public class ForgeDataScript extends MultiScriptHandler { private static List cachedHooks; private long lastForgeUpdate = -1L; + private final HashSet globalUnknownEvents = new HashSet<>(); public ForgeDataScript() { } @@ -59,6 +61,7 @@ public class ForgeDataScript extends MultiScriptHandler { protected void reInitScripts() { lastInited = ScriptController.Instance.lastLoaded; lastForgeUpdate = ScriptController.Instance.lastForgeUpdate; + globalUnknownEvents.clear(); for (IScriptUnit script : this.scripts) { if (script instanceof ScriptContainer) @@ -79,10 +82,25 @@ public class ForgeDataScript extends MultiScriptHandler { } } + // Skip events that no script tab handles + if (globalUnknownEvents.contains(type)) { + return; + } + + boolean anyHandled = false; for (IScriptUnit script : this.scripts) { if (script == null || script.hasErrored() || !script.hasCode()) continue; + boolean wasUnknown = script.isUnknownFunction(type); script.run(type, event); + if (!wasUnknown || !script.isUnknownFunction(type)) { + anyHandled = true; + } + } + + // If no script tab handled this event type, cache it globally + if (!anyHandled) { + globalUnknownEvents.add(type); } } diff --git a/src/main/java/noppes/npcs/controllers/data/IScriptUnit.java b/src/main/java/noppes/npcs/controllers/data/IScriptUnit.java index 2e4b25bad..5cdd58e04 100644 --- a/src/main/java/noppes/npcs/controllers/data/IScriptUnit.java +++ b/src/main/java/noppes/npcs/controllers/data/IScriptUnit.java @@ -168,6 +168,13 @@ public interface IScriptUnit { */ IScriptUnit createInstanceScope(IScriptHandler instanceHandler); + /** + * Check if the given function name is known to be absent from this script unit. + */ + default boolean isUnknownFunction(String type) { + return false; + } + // ==================== EXECUTION ==================== /** diff --git a/src/main/java/noppes/npcs/scripted/NpcAPI.java b/src/main/java/noppes/npcs/scripted/NpcAPI.java index 1b0d392b2..1a0b7edf1 100644 --- a/src/main/java/noppes/npcs/scripted/NpcAPI.java +++ b/src/main/java/noppes/npcs/scripted/NpcAPI.java @@ -160,6 +160,7 @@ public class NpcAPI extends AbstractNpcAPI { private static final Map worldCache = new LRUHashMap<>(10); private static final CacheHashMap> scriptItemCache = new CacheHashMap<>(60 * 1000); public static final HashMap engineObjects = new HashMap<>(); + public static volatile long engineObjectsVersion = 0; public static final EventBus EVENT_BUS = new EventBus(); private static AbstractNpcAPI instance = null; @@ -255,10 +256,12 @@ public class NpcAPI extends AbstractNpcAPI { public void addGlobalObject(String key, Object obj) { NpcAPI.engineObjects.put(key, obj); + NpcAPI.engineObjectsVersion++; } public void removeGlobalObject(String key) { NpcAPI.engineObjects.remove(key); + NpcAPI.engineObjectsVersion++; } public boolean hasGlobalObject(String key) {