diff --git a/pom.xml b/pom.xml
index 960e521..3326057 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
org.openautonomousconnection
LuaScript
- 1.0.0-BETA.1.1
+ 0.0.0-STABLE.1.3
Open Autonomous Connection
@@ -65,6 +65,10 @@
+
+ unlegitdqrk
+ https://repo.unlegitdqrk.dev/api/packages/UnlegitDqrk/maven
+
oac
https://repo.open-autonomous-connection.org/api/packages/open-autonomous-connection/maven
@@ -78,7 +82,7 @@
dev.unlegitdqrk
unlegitlibrary
- 1.8.1
+ 1.8.3
org.luaj
diff --git a/src/main/java/org/openautonomousconnection/luascript/fx/FxEventHost.java b/src/main/java/org/openautonomousconnection/luascript/fx/FxEventHost.java
index 5dbf39f..a9cc08b 100644
--- a/src/main/java/org/openautonomousconnection/luascript/fx/FxEventHost.java
+++ b/src/main/java/org/openautonomousconnection/luascript/fx/FxEventHost.java
@@ -11,6 +11,8 @@ import org.w3c.dom.events.EventTarget;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
/**
* EventHost implementation for JavaFX WebView using W3C DOM EventTarget listeners.
@@ -22,19 +24,31 @@ public final class FxEventHost implements EventHost {
private static final String GLOBAL_TARGET_ID = "__global__";
private final FxDomHost dom;
- private final LuaEventRouter router;
+ private volatile LuaEventRouter router;
private final ConcurrentHashMap elementListeners = new ConcurrentHashMap<>();
private final ConcurrentHashMap globalListeners = new ConcurrentHashMap<>();
+ private final ExecutorService dispatchExecutor = Executors.newSingleThreadExecutor(r -> {
+ Thread t = new Thread(r, "oac-fx-event-dispatch");
+ t.setDaemon(true);
+ return t;
+ });
/**
* Creates a new FxEventHost.
*
* @param dom fx dom host
+ */
+ public FxEventHost(FxDomHost dom) {
+ this.dom = Objects.requireNonNull(dom, "dom");
+ }
+
+ /**
+ * Sets the active Lua router for this host.
+ *
* @param router lua router
*/
- public FxEventHost(FxDomHost dom, LuaEventRouter router) {
- this.dom = Objects.requireNonNull(dom, "dom");
+ public void setRouter(LuaEventRouter router) {
this.router = Objects.requireNonNull(router, "router");
}
@@ -49,7 +63,7 @@ public final class FxEventHost implements EventHost {
elementListeners.computeIfAbsent(key, k -> {
EventListener listener = ev -> {
Map payload = FxEventPayloadExtractor.extract(ev);
- router.emit(elementId, evt, payload);
+ dispatchExecutor.execute(() -> emitSafely(elementId, evt, payload));
};
FxThreadBridge.runAndWait(() -> {
@@ -87,7 +101,7 @@ public final class FxEventHost implements EventHost {
globalListeners.computeIfAbsent(evt, k -> {
EventListener listener = ev -> {
Map payload = FxEventPayloadExtractor.extract(ev);
- router.emit(GLOBAL_TARGET_ID, evt, payload);
+ dispatchExecutor.execute(() -> emitSafely(GLOBAL_TARGET_ID, evt, payload));
};
FxThreadBridge.runAndWait(() -> {
@@ -113,4 +127,18 @@ public final class FxEventHost implements EventHost {
((EventTarget) doc).removeEventListener(evt, listener, false);
});
}
+
+ private void emitSafely(String elementId, String eventName, Map payload) {
+ try {
+ LuaEventRouter activeRouter = router;
+ if (activeRouter == null) {
+ System.err.println("[oac.event] Dropping '" + eventName + "' for '" + elementId + "': router not initialized");
+ return;
+ }
+ activeRouter.emit(elementId, eventName, payload);
+ } catch (RuntimeException ex) {
+ System.err.println("[oac.event] Failed to dispatch '" + eventName + "' for '" + elementId + "': " + ex.getMessage());
+ ex.printStackTrace(System.err);
+ }
+ }
}
diff --git a/src/main/java/org/openautonomousconnection/luascript/hosts/AudioHost.java b/src/main/java/org/openautonomousconnection/luascript/hosts/AudioHost.java
new file mode 100644
index 0000000..d40f52a
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/luascript/hosts/AudioHost.java
@@ -0,0 +1,9 @@
+package org.openautonomousconnection.luascript.hosts;
+
+import java.io.File;
+
+public interface AudioHost {
+
+ void play(File audioFile);
+
+}
diff --git a/src/main/java/org/openautonomousconnection/luascript/hosts/HostServices.java b/src/main/java/org/openautonomousconnection/luascript/hosts/HostServices.java
index c34137f..5e595c7 100644
--- a/src/main/java/org/openautonomousconnection/luascript/hosts/HostServices.java
+++ b/src/main/java/org/openautonomousconnection/luascript/hosts/HostServices.java
@@ -45,6 +45,8 @@ public interface HostServices {
*/
Optional console();
+ Optional audio();
+
/**
* Simple immutable implementation.
*/
@@ -54,6 +56,7 @@ public interface HostServices {
private final EventHost events;
private final ResourceHost resources;
private final ConsoleHost console;
+ private final AudioHost audioHost;
/**
* Creates a HostServices container.
@@ -64,12 +67,13 @@ public interface HostServices {
* @param resources resource host
* @param console console host
*/
- public Default(UiHost ui, DomHost dom, EventHost events, ResourceHost resources, ConsoleHost console) {
+ public Default(UiHost ui, DomHost dom, EventHost events, ResourceHost resources, ConsoleHost console, AudioHost audioHost) {
this.ui = ui;
this.dom = dom;
this.events = events;
this.resources = resources;
this.console = console;
+ this.audioHost = audioHost;
}
@Override
@@ -96,6 +100,11 @@ public interface HostServices {
public Optional console() {
return Optional.ofNullable(console);
}
+
+ @Override
+ public Optional audio() {
+ return Optional.ofNullable(audioHost);
+ }
}
/**
diff --git a/src/main/java/org/openautonomousconnection/luascript/runtime/FxLuaScriptEngine.java b/src/main/java/org/openautonomousconnection/luascript/runtime/FxLuaScriptEngine.java
index 29d1d69..b5439e8 100644
--- a/src/main/java/org/openautonomousconnection/luascript/runtime/FxLuaScriptEngine.java
+++ b/src/main/java/org/openautonomousconnection/luascript/runtime/FxLuaScriptEngine.java
@@ -7,6 +7,7 @@ import org.openautonomousconnection.luascript.fx.FxDomHost;
import org.openautonomousconnection.luascript.fx.FxEventHost;
import org.openautonomousconnection.luascript.fx.FxUiHost;
import org.openautonomousconnection.luascript.fx.FxWebViewResourceHost;
+import org.openautonomousconnection.luascript.hosts.AudioHost;
import org.openautonomousconnection.luascript.hosts.HostServices;
import org.openautonomousconnection.luascript.security.LuaExecutionPolicy;
import org.openautonomousconnection.luascript.utils.LuaGlobalsFactory;
@@ -82,23 +83,14 @@ public final class FxLuaScriptEngine implements AutoCloseable {
.sandbox(true)
);
- // Create runtime first (router lives inside it).
HostServices.StdoutConsole console = new HostServices.StdoutConsole("[lua] ");
FxUiHost uiHost = new FxUiHost(engine, dom);
FxWebViewResourceHost resourceHost = new FxWebViewResourceHost(engine);
-
- // runtime depends on services; events depends on runtime router.
- // We'll create eventHost after runtime, then build HostServices with it.
- LuaRuntime rt = new LuaRuntime(globals, new HostServices.Default(uiHost, dom, null, resourceHost, console), policy);
-
- FxEventHost eventHost = new FxEventHost(dom, rt.eventRouter());
-
- // Rebuild services including eventHost and reinstall tables.
- HostServices services = new HostServices.Default(uiHost, dom, eventHost, resourceHost, console);
-
- // Replace runtime with correct services (clean and deterministic).
- rt.close();
- rt = new LuaRuntime(globals, services, policy);
+ FxEventHost eventHost = new FxEventHost(dom);
+ // TODO: Default implementation or parameter for "audioHost"
+ HostServices services = new HostServices.Default(uiHost, dom, eventHost, resourceHost, console, audioHost);
+ LuaRuntime rt = new LuaRuntime(globals, services, policy);
+ eventHost.setRouter(rt.eventRouter());
rt.installStdTables(true);
rt.bootstrapFromDom();
@@ -131,4 +123,4 @@ public final class FxLuaScriptEngine implements AutoCloseable {
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/org/openautonomousconnection/luascript/security/LuaExecutionPolicy.java b/src/main/java/org/openautonomousconnection/luascript/security/LuaExecutionPolicy.java
index 7df2b46..90ab59d 100644
--- a/src/main/java/org/openautonomousconnection/luascript/security/LuaExecutionPolicy.java
+++ b/src/main/java/org/openautonomousconnection/luascript/security/LuaExecutionPolicy.java
@@ -24,6 +24,6 @@ public record LuaExecutionPolicy(Duration timeout, long instructionLimit, int ho
* @return policy
*/
public static LuaExecutionPolicy uiDefault() {
- return new LuaExecutionPolicy(Duration.ofMillis(50), 200_000, 5_000);
+ return new LuaExecutionPolicy(Duration.ofSeconds(5), 200_000, 5_000);
}
}
diff --git a/src/main/java/org/openautonomousconnection/luascript/security/LuaSecurityManager.java b/src/main/java/org/openautonomousconnection/luascript/security/LuaSecurityManager.java
index fa3abfe..fbb5c7e 100644
--- a/src/main/java/org/openautonomousconnection/luascript/security/LuaSecurityManager.java
+++ b/src/main/java/org/openautonomousconnection/luascript/security/LuaSecurityManager.java
@@ -1,8 +1,8 @@
package org.openautonomousconnection.luascript.security;
import org.luaj.vm2.*;
-import org.luaj.vm2.lib.DebugLib;
import org.luaj.vm2.lib.VarArgFunction;
+import org.openautonomousconnection.luascript.utils.LuaGlobalsFactory;
import java.util.Objects;
import java.util.concurrent.*;
@@ -16,6 +16,7 @@ import java.util.concurrent.atomic.AtomicLong;
public final class LuaSecurityManager implements AutoCloseable {
private final ExecutorService executor;
+ private final ConcurrentHashMap debugSetHooks = new ConcurrentHashMap<>();
/**
* Creates a new LuaSecurityManager that runs all Lua code on a single dedicated daemon thread.
@@ -28,8 +29,8 @@ public final class LuaSecurityManager implements AutoCloseable {
});
}
- private static Varargs callWithHook(Globals globals, LuaFunction function, Varargs args, LuaExecutionPolicy policy) {
- final LuaValue sethook = resolveAndHideDebugSetHook(globals);
+ private Varargs callWithHook(Globals globals, LuaFunction function, Varargs args, LuaExecutionPolicy policy) {
+ final LuaValue sethook = resolveDebugSetHook(globals);
final long deadlineMillis = System.currentTimeMillis() + policy.timeout().toMillis();
final AtomicLong ticks = new AtomicLong(0);
@@ -84,13 +85,19 @@ public final class LuaSecurityManager implements AutoCloseable {
return resumed;
}
- private static LuaValue resolveAndHideDebugSetHook(Globals globals) {
- // Ensure DebugLib exists for hooks.
- globals.load(new DebugLib());
+ private LuaValue resolveDebugSetHook(Globals globals) {
+ LuaValue cached = debugSetHooks.get(globals);
+ if (cached != null) return cached;
+
+ LuaValue internal = globals.get(LuaGlobalsFactory.INTERNAL_DEBUG_SETHOOK_KEY);
+ if (!internal.isnil() && internal.isfunction()) {
+ debugSetHooks.put(globals, internal);
+ return internal;
+ }
LuaValue debugTable = globals.get("debug");
if (debugTable.isnil() || !debugTable.istable()) {
- throw new IllegalStateException("Debug library not available (debug table missing)");
+ throw new IllegalStateException("Debug library not available (debug table missing and no internal sethook cached)");
}
LuaValue sethook = debugTable.get("sethook");
@@ -100,6 +107,7 @@ public final class LuaSecurityManager implements AutoCloseable {
// Hide debug from scripts.
globals.set("debug", LuaValue.NIL);
+ debugSetHooks.put(globals, sethook);
return sethook;
}
@@ -140,4 +148,4 @@ public final class LuaSecurityManager implements AutoCloseable {
public void close() {
executor.shutdownNow();
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/luascript/tables/AudioTable.java b/src/main/java/org/openautonomousconnection/luascript/tables/AudioTable.java
new file mode 100644
index 0000000..c0de21b
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/luascript/tables/AudioTable.java
@@ -0,0 +1,29 @@
+package org.openautonomousconnection.luascript.tables;
+
+import org.luaj.vm2.LuaValue;
+import org.luaj.vm2.lib.OneArgFunction;
+import org.openautonomousconnection.luascript.hosts.AudioHost;
+import org.openautonomousconnection.luascript.hosts.HostServices;
+import org.openautonomousconnection.luascript.utils.ScriptTable;
+
+import java.io.File;
+
+public class AudioTable extends ScriptTable {
+ protected AudioTable() {
+ super("audio");
+ }
+
+ @Override
+ protected void define(HostServices services) {
+ AudioHost audioHost = services.audio().orElseThrow(() -> new IllegalStateException("AudioHost not provided"));
+
+ table().set("play", new OneArgFunction() {
+ @Override
+ public LuaValue call(LuaValue arg) {
+ String fileName = arg.checkjstring();
+ audioHost.play(new File(fileName));
+ return LuaValue.NIL;
+ }
+ });
+ }
+}
diff --git a/src/main/java/org/openautonomousconnection/luascript/utils/LuaGlobalsFactory.java b/src/main/java/org/openautonomousconnection/luascript/utils/LuaGlobalsFactory.java
index cfcca18..2c44049 100644
--- a/src/main/java/org/openautonomousconnection/luascript/utils/LuaGlobalsFactory.java
+++ b/src/main/java/org/openautonomousconnection/luascript/utils/LuaGlobalsFactory.java
@@ -22,6 +22,8 @@ import java.util.function.Consumer;
*/
public final class LuaGlobalsFactory {
+ public static final String INTERNAL_DEBUG_SETHOOK_KEY = "__oac_internal_debug_sethook";
+
private LuaGlobalsFactory() {
}
@@ -35,12 +37,13 @@ public final class LuaGlobalsFactory {
Objects.requireNonNull(options, "options");
Globals g = JsePlatform.standardGlobals();
+ g.load(new DebugLib());
+ cacheInternalDebugSethook(g);
if (options.enableDebug) {
- // Ensure debug functions are available (depending on defaults).
- g.load(new DebugLib());
+ // Keep debug table visible to scripts.
} else {
- // If sandbox is not enabled but you still want no debug, explicitly remove it.
+ // Keep hook support internally, but do not expose debug to scripts.
g.set("debug", LuaValue.NIL);
}
@@ -55,6 +58,16 @@ public final class LuaGlobalsFactory {
return g;
}
+ private static void cacheInternalDebugSethook(Globals g) {
+ LuaValue debugTable = g.get("debug");
+ if (debugTable.isnil() || !debugTable.istable()) return;
+
+ LuaValue sethook = debugTable.get("sethook");
+ if (!sethook.isnil() && sethook.isfunction()) {
+ g.set(INTERNAL_DEBUG_SETHOOK_KEY, sethook);
+ }
+ }
+
/**
* Applies basic sandbox hardening to the given globals.
*