All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.nukkit.plugin.js.JSWorker Maven / Gradle / Ivy

There is a newer version: 1.20.40-r1
Show newest version
package cn.nukkit.plugin.js;

import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.HostAccess;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.proxy.ProxyExecutable;

import java.io.IOException;
import java.io.Reader;
import java.nio.file.Path;
import java.util.concurrent.CompletableFuture;

import static cn.nukkit.plugin.js.JSConcurrentManager.PROMISE_FAILED;
import static cn.nukkit.plugin.js.JSConcurrentManager.wrapPromise;

public final class JSWorker implements AutoCloseable {
    private static final Value NULL = Value.asValue(null);

    private final Context sourceContext;
    private final ESMFileSystem fileSystem;
    private final String workerSourcePath;
    private Context workerContext;
    private Reader sourceReader;
    private Path sourcePath;
    private Thread workerThread;

    private Value sourceReceiveCallback;
    private Value workerReceiveCallback;

    public JSWorker(Context sourceContext, ESMFileSystem fileSystem, String workerSourcePath) {
        this.sourceContext = sourceContext;
        this.fileSystem = fileSystem;
        this.workerSourcePath = workerSourcePath;
    }

    public void init() throws IOException {
        this.workerContext = Context.newBuilder("js")
                .fileSystem(fileSystem)
                .allowAllAccess(true)
                .allowHostAccess(HostAccess.newBuilder(HostAccess.ALL).targetTypeMapping(Double.class, Float.class, null, Double::floatValue).build())
                .allowHostClassLoading(true)
                .allowHostClassLookup(className -> true)
                .allowIO(true)
                .allowExperimentalOptions(true)
                .option("js.esm-eval-returns-exports", "true")
                .option("js.shared-array-buffer", "true")
                .option("js.foreign-object-prototype", "true")
                .option("js.nashorn-compat", "true")
                .option("js.ecmascript-version", "13")
                .build();
        JSIInitiator.init(workerContext);
        var workerGlobal = workerContext.getBindings("js");
        workerGlobal.putMember("postMessage", (ProxyExecutable) arguments -> {
            synchronized (sourceContext) {
                if (sourceReceiveCallback != null)
                    return sourceReceiveCallback.execute((Object[]) arguments);
                else return NULL;
            }
        });
        workerGlobal.putMember("postMessageAsync", (ProxyExecutable) arguments -> JSConcurrentManager.wrapPromise(workerContext,
                CompletableFuture.supplyAsync(() -> {
                    synchronized (sourceContext) {
                        if (sourceReceiveCallback != null)
                            return sourceReceiveCallback.execute((Object[]) arguments);
                        else return PROMISE_FAILED;
                    }
                })));
        this.sourcePath = fileSystem.parsePath(workerSourcePath);
        this.sourceReader = fileSystem.newReader(sourcePath);
    }

    public void start() {
        if (this.workerThread != null && workerThread.isAlive()) {
            workerContext.close(true);
            workerThread.interrupt();
        }
        this.workerThread = new Thread(() -> {
            try {
                var exports = workerContext.eval(Source.newBuilder("js", sourceReader,
                                fileSystem.baseDir.getName() + "/worker-" + sourcePath.getFileName() + "-" + workerThread.getId())
                        .mimeType("application/javascript+module").build());
                this.setWorkerReceiveCallback(exports.getMember("onmessage"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        this.workerThread.start();
    }

    public void setSourceReceiveCallback(Value value) {
        if (value != null && value.canExecute()) {
            this.sourceReceiveCallback = value;
        }
    }

    public void setWorkerReceiveCallback(Value value) {
        if (value != null && value.canExecute()) {
            this.workerReceiveCallback = value;
        }
    }

    public Value getSourceReceiveCallback() {
        return sourceReceiveCallback;
    }

    public Value getWorkerReceiveCallback() {
        return workerReceiveCallback;
    }

    @SuppressWarnings("SynchronizeOnNonFinalField")
    public Value postMessage(Value... arguments) {
        synchronized (workerContext) {
            if (workerReceiveCallback != null) {
                return workerReceiveCallback.execute((Object[]) arguments);
            } else {
                return NULL;
            }
        }
    }

    @SuppressWarnings("SynchronizeOnNonFinalField")
    public JSConcurrentManager.JPromise postMessageAsync(Value... arguments) {
        return wrapPromise(sourceContext, CompletableFuture.supplyAsync(() -> {
            synchronized (workerContext) {
                if (workerReceiveCallback != null) {
                    return workerReceiveCallback.execute((Object[]) arguments);
                } else {
                    return PROMISE_FAILED;
                }
            }
        }));
    }

    @SuppressWarnings("SynchronizeOnNonFinalField")
    @Override
    public void close() throws Exception {
        synchronized (workerContext) {
            workerContext.close(true);
        }
        this.workerThread.interrupt();
        this.workerThread = null;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy