Skip to content

Commit 5ea7436

Browse files
authored
Merge pull request #2 from bmstefanski/feat/javet-loader
feat: add Javet runtime
2 parents e9ece92 + cf0f10b commit 5ea7436

26 files changed

Lines changed: 2270 additions & 925 deletions

docs/content/docs/concurrency.mdx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,8 @@ for (let i = 0; i < 100; i++) {
181181

182182
![](./pool-stats.png)
183183

184+
**`/hytalejs runtime`** - Show runtime debug info (active runtime, pool size, multithreaded status)
185+
184186
**`/hytalejs tasks`** - Show operations waiting in queue
185187

186188
![](./queued-tasks.png)
@@ -220,15 +222,31 @@ Only Java objects passed through bindings (`Universe`, `HytaleServer`) are share
220222

221223
## Configuration
222224

223-
Pool size can be configured via `config.json` in your plugin's data directory. The file is created automatically on first run:
225+
Runtime options can be configured via `config.json` in your plugin's data directory. The file is created automatically on first run:
224226

225227
```json
226228
{
227-
"poolSize": 6
229+
"runtime": {
230+
"type": "graal",
231+
"poolSize": 6,
232+
"multithreaded": true
233+
}
234+
}
235+
```
236+
237+
Set `multithreaded: false` to force single-threaded execution (pool size becomes 1).
238+
239+
```json
240+
{
241+
"runtime": {
242+
"type": "javet",
243+
"poolSize": 1,
244+
"multithreaded": false
245+
}
228246
}
229247
```
230248

231-
Edit this value and restart the server to apply changes.
249+
Edit the config and restart the server to apply changes.
232250

233251
## References
234252

docs/content/docs/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"index",
55
"getting-started",
66
"concurrency",
7+
"runtimes",
78
"guides",
89
"api"
910
]

docs/content/docs/runtimes.mdx

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
---
2+
title: Runtimes
3+
description: Configure GraalJS and Javet runtimes for HytaleJS
4+
---
5+
6+
HytaleJS supports two JavaScript runtimes:
7+
8+
- **GraalJS** (default) — more tested and stable, but generally less performant.
9+
- **Javet (V8)** — faster in many workloads, uses V8 under the hood.
10+
11+
## Configuration
12+
13+
Runtime settings live in `config.json` inside your plugin data directory
14+
(`mods/bmstefanski_HytaleJS/config.json`).
15+
16+
```json
17+
{
18+
"runtime": {
19+
"type": "graal",
20+
"poolSize": 6,
21+
"multithreaded": true
22+
}
23+
}
24+
```
25+
26+
### Options
27+
28+
- `runtime.type`: `"graal"` or `"javet"` (default: `"graal"`).
29+
- `runtime.poolSize`: Number of runtime instances in the pool (e.g., Graal contexts or V8 runtimes) (default: `6`).
30+
- `runtime.multithreaded`: Enable/disable multi-context execution.
31+
- If `false`, the pool is forced to size `1` (single-threaded execution).
32+
33+
### Javet download settings
34+
35+
When using Javet, native libraries are downloaded at runtime by default.
36+
37+
```json
38+
{
39+
"runtime": {
40+
"type": "javet",
41+
"poolSize": 6,
42+
"multithreaded": true
43+
},
44+
"javet": {
45+
"download": true,
46+
"downloadBaseUrl": "https://repo1.maven.org/maven2"
47+
}
48+
}
49+
```
50+
51+
## Runtime Debug Command
52+
53+
Use `/hytalejs runtime` to print the active runtime, pool size, queue depth,
54+
and Javet download settings.
55+
56+
## Notes
57+
58+
- Switching runtimes requires a server restart.
59+
- If you run into runtime-specific issues, try GraalJS first since it’s the
60+
most tested option right now.

loader/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ dependencies {
2121
implementation 'org.graalvm.polyglot:polyglot:24.1.1'
2222
implementation 'org.graalvm.polyglot:js-community:24.1.1'
2323
implementation 'com.google.code.gson:gson:2.11.0'
24+
implementation 'com.caoccao.javet:javet:5.0.3'
2425
}
2526

2627
shadowJar {

loader/src/main/java/com/bmstefanski/hytalejs/ContextPool.java

Lines changed: 0 additions & 143 deletions
This file was deleted.
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package com.bmstefanski.hytalejs;
2+
3+
import org.graalvm.polyglot.Context;
4+
import org.graalvm.polyglot.Engine;
5+
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
import java.util.concurrent.BlockingQueue;
9+
import java.util.concurrent.ConcurrentHashMap;
10+
import java.util.concurrent.LinkedBlockingQueue;
11+
import java.util.function.Consumer;
12+
13+
public class GraalRuntimePool implements ScriptRuntimePool {
14+
private final Engine engine;
15+
private final List<ScriptRuntime> allRuntimes;
16+
private final BlockingQueue<ScriptRuntime> available;
17+
private final ConcurrentHashMap<Thread, PendingRequest> pendingRequests = new ConcurrentHashMap<>();
18+
19+
public GraalRuntimePool(int size, Consumer<ScriptRuntime> runtimeInitializer) {
20+
engine = Engine.newBuilder()
21+
.option("engine.WarnInterpreterOnly", "false")
22+
.build();
23+
allRuntimes = new ArrayList<>(size);
24+
available = new LinkedBlockingQueue<>(size);
25+
26+
for (int i = 0; i < size; i++) {
27+
Context ctx = Context.newBuilder("js")
28+
.engine(engine)
29+
.allowAllAccess(true)
30+
.allowHostClassLookup(className -> true)
31+
.build();
32+
ScriptRuntime runtime = new GraalScriptRuntime(ctx);
33+
runtimeInitializer.accept(runtime);
34+
allRuntimes.add(runtime);
35+
available.offer(runtime);
36+
}
37+
}
38+
39+
private ScriptRuntime acquire(String operation) {
40+
Thread currentThread = Thread.currentThread();
41+
pendingRequests.put(currentThread, new PendingRequest(operation, System.currentTimeMillis()));
42+
try {
43+
ScriptRuntime runtime = available.take();
44+
pendingRequests.remove(currentThread);
45+
try {
46+
runtime.enter();
47+
} catch (RuntimeException e) {
48+
available.offer(runtime);
49+
throw e;
50+
}
51+
return runtime;
52+
} catch (InterruptedException e) {
53+
pendingRequests.remove(currentThread);
54+
Thread.currentThread().interrupt();
55+
throw new RuntimeException("Interrupted while acquiring runtime", e);
56+
}
57+
}
58+
59+
private void release(ScriptRuntime runtime) {
60+
runtime.leave();
61+
available.offer(runtime);
62+
}
63+
64+
@Override
65+
public <T> T executeInRuntime(String operation, ScriptRuntimeTask<T> task) {
66+
ScriptRuntime runtime = acquire(operation);
67+
try {
68+
return task.execute(runtime);
69+
} finally {
70+
release(runtime);
71+
}
72+
}
73+
74+
@Override
75+
public void executeInRuntime(String operation, ScriptRuntimeRunnable task) {
76+
ScriptRuntime runtime = acquire(operation);
77+
try {
78+
task.execute(runtime);
79+
} finally {
80+
release(runtime);
81+
}
82+
}
83+
84+
@Override
85+
public List<ScriptRuntime> getAllRuntimes() {
86+
return allRuntimes;
87+
}
88+
89+
@Override
90+
public int getTotalSize() {
91+
return allRuntimes.size();
92+
}
93+
94+
@Override
95+
public int getAvailableCount() {
96+
return available.size();
97+
}
98+
99+
@Override
100+
public int getBusyCount() {
101+
return allRuntimes.size() - available.size();
102+
}
103+
104+
@Override
105+
public List<QueuedOperation> getQueuedOperations() {
106+
long now = System.currentTimeMillis();
107+
return pendingRequests.values().stream()
108+
.map(req -> new QueuedOperation(req.operation, now - req.startTime))
109+
.toList();
110+
}
111+
112+
@Override
113+
public void close() {
114+
for (ScriptRuntime runtime : allRuntimes) {
115+
if (runtime instanceof GraalScriptRuntime graalRuntime) {
116+
graalRuntime.getContext().close();
117+
}
118+
}
119+
engine.close();
120+
}
121+
}

0 commit comments

Comments
 (0)