Skip to content

Commit d881b28

Browse files
committed
Add the bulk of the work for returning task data from events.
Credit: kiip1 for suggestion and example.
1 parent 4597af5 commit d881b28

File tree

6 files changed

+221
-28
lines changed

6 files changed

+221
-28
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>org.byteskript</groupId>
88
<artifactId>byteskript</artifactId>
9-
<version>1.0.34</version>
9+
<version>1.0.35</version>
1010
<name>ByteSkript</name>
1111
<description>A compiled JVM implementation of the Skript language.</description>
1212

src/main/java/org/byteskript/skript/runtime/Skript.java

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.byteskript.skript.runtime.internal.*;
2525
import org.byteskript.skript.runtime.threading.*;
2626
import org.byteskript.skript.runtime.type.Converter;
27+
import org.byteskript.skript.runtime.type.EventData;
2728
import org.byteskript.skript.runtime.type.OperatorFunction;
2829

2930
import java.io.*;
@@ -463,15 +464,15 @@ public Future<?> runScript(final ScriptRunner runner, final Event event) {
463464
final Runnable runnable = () -> {
464465
final ScriptThread thread = (ScriptThread) Thread.currentThread();
465466
future.thread = thread;
466-
thread.variables.clear();
467+
thread.variables.clear(); // Some threads get regurgitated and will have shadow variables from their previous run.
467468
thread.initiator = runner.owner();
468469
thread.event = event;
469470
try {
470471
runner.run();
471-
future.value(runner.result());
472472
} catch (ThreadDeath ignore) {
473-
// This is likely from an exit the current process effect, we don't want to make noise
473+
// This is likely from an exit the current process effect, we don't want to make noise.
474474
} finally {
475+
future.value(runner.result());
475476
future.finish();
476477
}
477478
};
@@ -485,15 +486,16 @@ public Future<?> runScript(final ScriptRunner runner, final Event event) {
485486
This will trigger only the given script.
486487
""")
487488
@GenerateExample
488-
public boolean runEvent(final Event event, final Script script) {
489+
public EventData<?> runEvent(final Event event, final Script script) {
489490
boolean run = false;
491+
final List<ScriptFinishFuture> futures = new ArrayList<>();
490492
for (Map.Entry<Class<? extends Event>, EventHandler> entry : events.entrySet()) {
491493
final Class<? extends Event> key = entry.getKey();
492494
if (!key.isAssignableFrom(event.getClass())) continue;
493495
run = true;
494-
entry.getValue().run(this, event, script);
496+
futures.addAll(Arrays.asList(entry.getValue().run(this, event, script)));
495497
}
496-
return run;
498+
return new EventData<>(run, event, futures.toArray(new ScriptFinishFuture[0]));
497499
}
498500

499501
@Description("""
@@ -777,15 +779,16 @@ public void unloadScript(Script script) {
777779
Each handler will spawn its own process.
778780
""")
779781
@GenerateExample
780-
public boolean runEvent(final Event event) {
782+
public EventData<?> runEvent(final Event event) {
781783
boolean run = false;
784+
final List<ScriptFinishFuture> futures = new ArrayList<>();
782785
for (Map.Entry<Class<? extends Event>, EventHandler> entry : events.entrySet()) {
783786
final Class<? extends Event> key = entry.getKey();
784787
if (!key.isAssignableFrom(event.getClass())) continue;
785788
run = true;
786-
entry.getValue().run(this, event);
789+
futures.addAll(Arrays.asList(entry.getValue().run(this, event)));
787790
}
788-
return run;
791+
return new EventData<>(run, event, futures.toArray(new ScriptFinishFuture[0]));
789792
}
790793

791794
@Description("""

src/main/java/org/byteskript/skript/runtime/internal/EventHandler.java

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010
import org.byteskript.skript.api.Event;
1111
import org.byteskript.skript.runtime.Script;
1212
import org.byteskript.skript.runtime.Skript;
13+
import org.byteskript.skript.runtime.threading.ScriptFinishFuture;
1314
import org.byteskript.skript.runtime.threading.ScriptRunner;
1415

1516
import java.util.ArrayList;
1617
import java.util.List;
18+
import java.util.concurrent.Future;
1719

1820
@Ignore
1921
public class EventHandler {
@@ -24,21 +26,38 @@ public List<ScriptRunner> getTriggers() {
2426
return triggers;
2527
}
2628

27-
public void run(final Skript skript, final Event event) {
28-
for (final ScriptRunner trigger : triggers) {
29-
skript.runScript(trigger, event);
29+
public ScriptFinishFuture[] run(final Skript skript, final Event event) {
30+
final ScriptFinishFuture[] futures = new ScriptFinishFuture[triggers.size()];
31+
int count = 0;
32+
synchronized (triggers) { // Reduce the chance of comodification.
33+
for (final ScriptRunner trigger : triggers) {
34+
futures[count] = (ScriptFinishFuture) skript.runScript(trigger, event);
35+
assert futures[count] != null: "Script trigger didn't produce a task.";
36+
count++;
37+
}
3038
}
39+
return futures;
3140
}
3241

33-
public void run(final Skript skript, final Event event, final Script script) {
34-
for (final ScriptRunner trigger : triggers) {
35-
if (trigger.owner() == script.mainClass())
36-
skript.runScript(trigger, event);
42+
public ScriptFinishFuture[] run(final Skript skript, final Event event, final Script script) {
43+
int count = 0;
44+
synchronized (triggers) {
45+
final List<ScriptRunner> triggers = new ArrayList<>(this.triggers);
46+
triggers.removeIf(trigger -> trigger.owner() != script.mainClass());
47+
final ScriptFinishFuture[] futures = new ScriptFinishFuture[triggers.size()];
48+
for (final ScriptRunner trigger : triggers) {
49+
futures[count] = (ScriptFinishFuture) skript.runScript(trigger, event);
50+
assert futures[count] != null: "Script trigger didn't produce a task.";
51+
count++;
52+
}
53+
return futures;
3754
}
3855
}
3956

4057
public void add(final ScriptRunner runner) {
41-
this.triggers.add(runner);
58+
synchronized (triggers) {
59+
this.triggers.add(runner);
60+
}
4261
}
4362

4463
}

src/main/java/org/byteskript/skript/runtime/threading/ScriptFinishFuture.java

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,31 @@
1313
import java.util.concurrent.Future;
1414
import java.util.concurrent.TimeUnit;
1515
import java.util.concurrent.TimeoutException;
16+
import java.util.function.Supplier;
1617

17-
public class ScriptFinishFuture implements Future<Object> {
18+
public class ScriptFinishFuture implements Supplier<Object>, Future<Object> {
1819

1920
private final Skript skript;
2021
private final Object lock = new Object();
22+
private boolean done;
2123
public ScriptThread thread;
2224
protected Object value;
2325

2426
public ScriptFinishFuture(Skript skript) {
2527
this.skript = skript;
2628
}
2729

28-
public void value(Object object) {
29-
synchronized (this) {
30-
this.value = object;
31-
}
30+
public synchronized void value(Object object) {
31+
this.value = object;
3232
}
3333

3434
public void finish() {
3535
synchronized (lock) {
3636
lock.notify();
3737
}
38+
synchronized (this) {
39+
this.done = true;
40+
}
3841
}
3942

4043
@Override
@@ -47,18 +50,25 @@ public boolean cancel(boolean mayInterruptIfRunning) {
4750

4851
@Override
4952
public boolean isCancelled() {
50-
return thread != null && !thread.isAlive();
53+
return thread == null || thread.isAlive();
5154
}
5255

5356
@Override
54-
public boolean isDone() {
55-
return thread != null && !thread.isAlive();
57+
public synchronized boolean isDone() {
58+
return value != null;
5659
}
5760

5861
@Override
59-
public Object get() throws InterruptedException, ExecutionException {
62+
public Object get() {
63+
synchronized (this) {
64+
if (this.done) return value;
65+
}
6066
synchronized (lock) {
61-
lock.wait();
67+
try {
68+
lock.wait();
69+
} catch (InterruptedException e) {
70+
throw new RuntimeException(e);
71+
}
6272
}
6373
synchronized (this) {
6474
return value;
@@ -67,6 +77,9 @@ public Object get() throws InterruptedException, ExecutionException {
6777

6878
@Override
6979
public Object get(long timeout, @NotNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
80+
synchronized (this) {
81+
if (value != null) return value;
82+
}
7083
synchronized (lock) {
7184
lock.wait(unit.toMillis(timeout));
7285
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright (c) 2022 ByteSkript org (Moderocky)
3+
* View the full licence information and permissions:
4+
* https://github.com/Moderocky/ByteSkript/blob/master/LICENSE
5+
*/
6+
7+
package org.byteskript.skript.runtime.type;
8+
9+
import org.byteskript.skript.api.Event;
10+
import org.byteskript.skript.runtime.threading.ScriptFinishFuture;
11+
12+
import java.util.Objects;
13+
import java.util.concurrent.CompletableFuture;
14+
import java.util.concurrent.Future;
15+
16+
public final class EventData<Type extends Event> {
17+
private final boolean run;
18+
private final Type event;
19+
private final ScriptFinishFuture[] tasks;
20+
private CompletableFuture<?> all, any;
21+
private CompletableFuture<?>[] futures;
22+
23+
public EventData(boolean run, Type event, ScriptFinishFuture[] tasks) {
24+
this.run = run;
25+
this.event = event;
26+
this.tasks = tasks;
27+
}
28+
29+
private void prepareFutures() {
30+
this.futures = new CompletableFuture[tasks.length];
31+
for (int i = 0; i < tasks.length; i++) futures[i] = CompletableFuture.supplyAsync(tasks[i]);
32+
}
33+
34+
public CompletableFuture<?> all() {
35+
if (all != null) return all;
36+
if (this.futures == null) this.prepareFutures();
37+
return all = CompletableFuture.allOf(futures);
38+
}
39+
40+
public CompletableFuture<?> any() {
41+
if (any != null) return any;
42+
if (this.futures == null) this.prepareFutures();
43+
return any = CompletableFuture.anyOf(futures);
44+
}
45+
46+
@SuppressWarnings("unchecked")
47+
public Class<Type> getType() {
48+
return (Class<Type>) event.getClass();
49+
}
50+
51+
public boolean run() {
52+
return run;
53+
}
54+
55+
public Type event() {
56+
return event;
57+
}
58+
59+
public ScriptFinishFuture[] tasks() {
60+
return tasks;
61+
}
62+
63+
@Override
64+
public boolean equals(Object obj) {
65+
if (obj == this) return true;
66+
if (obj == null || obj.getClass() != this.getClass()) return false;
67+
var that = (EventData) obj;
68+
return this.run == that.run &&
69+
Objects.equals(this.event, that.event) &&
70+
Objects.equals(this.tasks, that.tasks);
71+
}
72+
73+
@Override
74+
public int hashCode() {
75+
return Objects.hash(run, event, tasks);
76+
}
77+
78+
@Override
79+
public String toString() {
80+
return "EventData[" +
81+
"run=" + run + ", " +
82+
"event=" + event + ", " +
83+
"tasks=" + tasks + ']';
84+
}
85+
86+
87+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright (c) 2022 ByteSkript org (Moderocky)
3+
* View the full licence information and permissions:
4+
* https://github.com/Moderocky/ByteSkript/blob/master/LICENSE
5+
*/
6+
7+
package org.byteskript.skript.test;
8+
9+
import mx.kenzie.foundation.language.PostCompileClass;
10+
import org.byteskript.skript.api.Event;
11+
import org.byteskript.skript.api.ModifiableLibrary;
12+
import org.byteskript.skript.api.note.EventValue;
13+
import org.byteskript.skript.runtime.Skript;
14+
import org.byteskript.skript.runtime.config.ConfigEntry;
15+
import org.byteskript.skript.runtime.config.ConfigMap;
16+
import org.junit.BeforeClass;
17+
import org.junit.Test;
18+
19+
import java.io.ByteArrayInputStream;
20+
import java.io.InputStream;
21+
import java.nio.charset.StandardCharsets;
22+
23+
public class ThreadingTest extends SkriptTest {
24+
25+
private static final Skript skript
26+
= new Skript();
27+
// = new Skript(new DebugSkriptCompiler(Stream.controller(System.out)));
28+
29+
private final String test = """
30+
on test:
31+
trigger:
32+
print "Start"
33+
wait 1 second
34+
print "Finish"
35+
36+
""";
37+
private static PostCompileClass cls;
38+
39+
@BeforeClass
40+
public static void setup() {
41+
skript.registerLibrary(new ModifiableLibrary("test") {{ // Credit: kiip1 for suggestion.
42+
generateSyntaxFrom(TestEvent.class);
43+
}});
44+
cls = skript.compileScript(
45+
"""
46+
on test:
47+
trigger:
48+
wait 1 millisecond
49+
set event-thing to 6
50+
""", "skript.events");
51+
skript.loadScript(cls);
52+
}
53+
54+
@Test
55+
public void eventSynchronized() throws Throwable {
56+
final TestEvent event;
57+
skript.runEvent(event = new TestEvent())
58+
.all()
59+
.get();
60+
assert event.number == 6;
61+
}
62+
63+
@org.byteskript.skript.api.note.Event("on test")
64+
public static final class TestEvent extends Event {
65+
public int number = 1;
66+
@EventValue("thing")
67+
public void setThing(Number number) {
68+
this.number = (int) number;
69+
}
70+
}
71+
}

0 commit comments

Comments
 (0)