Files
LuaScript/src/main/java/org/openautonomousconnection/luascript/runtime/FxLuaScriptEngine.java

127 lines
4.1 KiB
Java
Raw Normal View History

2026-02-10 19:24:38 +01:00
package org.openautonomousconnection.luascript.runtime;
import javafx.concurrent.Worker;
import javafx.scene.web.WebEngine;
import org.luaj.vm2.Globals;
import org.openautonomousconnection.luascript.fx.FxDomHost;
import org.openautonomousconnection.luascript.fx.FxEventHost;
import org.openautonomousconnection.luascript.fx.FxUiHost;
import org.openautonomousconnection.luascript.fx.FxWebViewResourceHost;
2026-02-28 16:00:29 +01:00
import org.openautonomousconnection.luascript.hosts.AudioHost;
2026-02-10 19:24:38 +01:00
import org.openautonomousconnection.luascript.hosts.HostServices;
import org.openautonomousconnection.luascript.security.LuaExecutionPolicy;
import org.openautonomousconnection.luascript.utils.LuaGlobalsFactory;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* JavaFX WebView integration entry point for LuaScript (no JavaScript).
*
* <p>Hard rule: every HTML script tag is treated as Lua.</p>
*/
public final class FxLuaScriptEngine implements AutoCloseable {
private final WebEngine engine;
private final LuaExecutionPolicy policy;
private final AtomicBoolean bootstrapped = new AtomicBoolean(false);
private LuaRuntime runtime;
/**
* Creates an integration engine with default UI execution policy.
*
* @param engine web engine
*/
public FxLuaScriptEngine(WebEngine engine) {
this(engine, LuaExecutionPolicy.uiDefault());
}
/**
* Creates an integration engine with a custom execution policy.
*
* @param engine web engine
* @param policy execution policy
*/
public FxLuaScriptEngine(WebEngine engine, LuaExecutionPolicy policy) {
this.engine = Objects.requireNonNull(engine, "engine");
this.policy = Objects.requireNonNull(policy, "policy");
}
/**
* Installs a load hook that bootstraps Lua when a page finished loading.
*/
public void install() {
engine.getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> {
if (newState == Worker.State.SUCCEEDED) {
bootstrapped.set(false);
bootstrap();
} else if (newState == Worker.State.CANCELLED || newState == Worker.State.FAILED) {
bootstrapped.set(false);
closeRuntimeQuietly();
}
});
}
/**
* Bootstraps Lua for the currently loaded document.
*/
public void bootstrap() {
if (!bootstrapped.compareAndSet(false, true)) return;
closeRuntimeQuietly();
// DOM host must exist before event/UI tables, and must ensure stable ids.
FxDomHost dom = new FxDomHost(engine);
dom.ensureAllElementsHaveId();
// Create per-page globals; harden sandbox in production.
Globals globals = LuaGlobalsFactory.create(
new LuaGlobalsFactory.Options()
.enableDebug(false)
.sandbox(true)
);
HostServices.StdoutConsole console = new HostServices.StdoutConsole("[lua] ");
FxUiHost uiHost = new FxUiHost(engine, dom);
FxWebViewResourceHost resourceHost = new FxWebViewResourceHost(engine);
2026-02-27 23:54:33 +01:00
FxEventHost eventHost = new FxEventHost(dom);
2026-02-28 16:00:29 +01:00
// TODO: Default implementation or parameter for "audioHost"
HostServices services = new HostServices.Default(uiHost, dom, eventHost, resourceHost, console, audioHost);
2026-02-27 23:54:33 +01:00
LuaRuntime rt = new LuaRuntime(globals, services, policy);
eventHost.setRouter(rt.eventRouter());
2026-02-10 19:24:38 +01:00
rt.installStdTables(true);
rt.bootstrapFromDom();
this.runtime = rt;
}
/**
* Returns active runtime or null if not bootstrapped.
*
* @return runtime or null
*/
public LuaRuntime runtimeOrNull() {
return runtime;
}
@Override
public void close() {
closeRuntimeQuietly();
}
private void closeRuntimeQuietly() {
LuaRuntime rt = this.runtime;
this.runtime = null;
if (rt != null) {
try {
rt.close();
} catch (Exception ignored) {
// Best-effort shutdown.
}
}
}
2026-02-27 23:54:33 +01:00
}