Skip to content

Commit

Permalink
Add the bulk of the work for returning task data from events.
Browse files Browse the repository at this point in the history
Credit: kiip1 for suggestion and example.
  • Loading branch information
Moderocky committed Jul 8, 2022
1 parent 4597af5 commit d881b28
Show file tree
Hide file tree
Showing 6 changed files with 221 additions and 28 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>org.byteskript</groupId>
<artifactId>byteskript</artifactId>
<version>1.0.34</version>
<version>1.0.35</version>
<name>ByteSkript</name>
<description>A compiled JVM implementation of the Skript language.</description>

Expand Down
21 changes: 12 additions & 9 deletions src/main/java/org/byteskript/skript/runtime/Skript.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.byteskript.skript.runtime.internal.*;
import org.byteskript.skript.runtime.threading.*;
import org.byteskript.skript.runtime.type.Converter;
import org.byteskript.skript.runtime.type.EventData;
import org.byteskript.skript.runtime.type.OperatorFunction;

import java.io.*;
Expand Down Expand Up @@ -463,15 +464,15 @@ public Future<?> runScript(final ScriptRunner runner, final Event event) {
final Runnable runnable = () -> {
final ScriptThread thread = (ScriptThread) Thread.currentThread();
future.thread = thread;
thread.variables.clear();
thread.variables.clear(); // Some threads get regurgitated and will have shadow variables from their previous run.
thread.initiator = runner.owner();
thread.event = event;
try {
runner.run();
future.value(runner.result());
} catch (ThreadDeath ignore) {
// This is likely from an exit the current process effect, we don't want to make noise
// This is likely from an exit the current process effect, we don't want to make noise.
} finally {
future.value(runner.result());
future.finish();
}
};
Expand All @@ -485,15 +486,16 @@ public Future<?> runScript(final ScriptRunner runner, final Event event) {
This will trigger only the given script.
""")
@GenerateExample
public boolean runEvent(final Event event, final Script script) {
public EventData<?> runEvent(final Event event, final Script script) {
boolean run = false;
final List<ScriptFinishFuture> futures = new ArrayList<>();
for (Map.Entry<Class<? extends Event>, EventHandler> entry : events.entrySet()) {
final Class<? extends Event> key = entry.getKey();
if (!key.isAssignableFrom(event.getClass())) continue;
run = true;
entry.getValue().run(this, event, script);
futures.addAll(Arrays.asList(entry.getValue().run(this, event, script)));
}
return run;
return new EventData<>(run, event, futures.toArray(new ScriptFinishFuture[0]));
}

@Description("""
Expand Down Expand Up @@ -777,15 +779,16 @@ public void unloadScript(Script script) {
Each handler will spawn its own process.
""")
@GenerateExample
public boolean runEvent(final Event event) {
public EventData<?> runEvent(final Event event) {
boolean run = false;
final List<ScriptFinishFuture> futures = new ArrayList<>();
for (Map.Entry<Class<? extends Event>, EventHandler> entry : events.entrySet()) {
final Class<? extends Event> key = entry.getKey();
if (!key.isAssignableFrom(event.getClass())) continue;
run = true;
entry.getValue().run(this, event);
futures.addAll(Arrays.asList(entry.getValue().run(this, event)));
}
return run;
return new EventData<>(run, event, futures.toArray(new ScriptFinishFuture[0]));
}

@Description("""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
import org.byteskript.skript.api.Event;
import org.byteskript.skript.runtime.Script;
import org.byteskript.skript.runtime.Skript;
import org.byteskript.skript.runtime.threading.ScriptFinishFuture;
import org.byteskript.skript.runtime.threading.ScriptRunner;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;

@Ignore
public class EventHandler {
Expand All @@ -24,21 +26,38 @@ public List<ScriptRunner> getTriggers() {
return triggers;
}

public void run(final Skript skript, final Event event) {
for (final ScriptRunner trigger : triggers) {
skript.runScript(trigger, event);
public ScriptFinishFuture[] run(final Skript skript, final Event event) {
final ScriptFinishFuture[] futures = new ScriptFinishFuture[triggers.size()];
int count = 0;
synchronized (triggers) { // Reduce the chance of comodification.
for (final ScriptRunner trigger : triggers) {
futures[count] = (ScriptFinishFuture) skript.runScript(trigger, event);
assert futures[count] != null: "Script trigger didn't produce a task.";
count++;
}
}
return futures;
}

public void run(final Skript skript, final Event event, final Script script) {
for (final ScriptRunner trigger : triggers) {
if (trigger.owner() == script.mainClass())
skript.runScript(trigger, event);
public ScriptFinishFuture[] run(final Skript skript, final Event event, final Script script) {
int count = 0;
synchronized (triggers) {
final List<ScriptRunner> triggers = new ArrayList<>(this.triggers);
triggers.removeIf(trigger -> trigger.owner() != script.mainClass());
final ScriptFinishFuture[] futures = new ScriptFinishFuture[triggers.size()];
for (final ScriptRunner trigger : triggers) {
futures[count] = (ScriptFinishFuture) skript.runScript(trigger, event);
assert futures[count] != null: "Script trigger didn't produce a task.";
count++;
}
return futures;
}
}

public void add(final ScriptRunner runner) {
this.triggers.add(runner);
synchronized (triggers) {
this.triggers.add(runner);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,31 @@
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Supplier;

public class ScriptFinishFuture implements Future<Object> {
public class ScriptFinishFuture implements Supplier<Object>, Future<Object> {

private final Skript skript;
private final Object lock = new Object();
private boolean done;
public ScriptThread thread;
protected Object value;

public ScriptFinishFuture(Skript skript) {
this.skript = skript;
}

public void value(Object object) {
synchronized (this) {
this.value = object;
}
public synchronized void value(Object object) {
this.value = object;
}

public void finish() {
synchronized (lock) {
lock.notify();
}
synchronized (this) {
this.done = true;
}
}

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

@Override
public boolean isCancelled() {
return thread != null && !thread.isAlive();
return thread == null || thread.isAlive();
}

@Override
public boolean isDone() {
return thread != null && !thread.isAlive();
public synchronized boolean isDone() {
return value != null;
}

@Override
public Object get() throws InterruptedException, ExecutionException {
public Object get() {
synchronized (this) {
if (this.done) return value;
}
synchronized (lock) {
lock.wait();
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
synchronized (this) {
return value;
Expand All @@ -67,6 +77,9 @@ public Object get() throws InterruptedException, ExecutionException {

@Override
public Object get(long timeout, @NotNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
synchronized (this) {
if (value != null) return value;
}
synchronized (lock) {
lock.wait(unit.toMillis(timeout));
}
Expand Down
87 changes: 87 additions & 0 deletions src/main/java/org/byteskript/skript/runtime/type/EventData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright (c) 2022 ByteSkript org (Moderocky)
* View the full licence information and permissions:
* https://github.com/Moderocky/ByteSkript/blob/master/LICENSE
*/

package org.byteskript.skript.runtime.type;

import org.byteskript.skript.api.Event;
import org.byteskript.skript.runtime.threading.ScriptFinishFuture;

import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;

public final class EventData<Type extends Event> {
private final boolean run;
private final Type event;
private final ScriptFinishFuture[] tasks;
private CompletableFuture<?> all, any;
private CompletableFuture<?>[] futures;

public EventData(boolean run, Type event, ScriptFinishFuture[] tasks) {
this.run = run;
this.event = event;
this.tasks = tasks;
}

private void prepareFutures() {
this.futures = new CompletableFuture[tasks.length];
for (int i = 0; i < tasks.length; i++) futures[i] = CompletableFuture.supplyAsync(tasks[i]);
}

public CompletableFuture<?> all() {
if (all != null) return all;
if (this.futures == null) this.prepareFutures();
return all = CompletableFuture.allOf(futures);
}

public CompletableFuture<?> any() {
if (any != null) return any;
if (this.futures == null) this.prepareFutures();
return any = CompletableFuture.anyOf(futures);
}

@SuppressWarnings("unchecked")
public Class<Type> getType() {
return (Class<Type>) event.getClass();
}

public boolean run() {
return run;
}

public Type event() {
return event;
}

public ScriptFinishFuture[] tasks() {
return tasks;
}

@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
var that = (EventData) obj;
return this.run == that.run &&
Objects.equals(this.event, that.event) &&
Objects.equals(this.tasks, that.tasks);
}

@Override
public int hashCode() {
return Objects.hash(run, event, tasks);
}

@Override
public String toString() {
return "EventData[" +
"run=" + run + ", " +
"event=" + event + ", " +
"tasks=" + tasks + ']';
}


}
71 changes: 71 additions & 0 deletions src/test/java/org/byteskript/skript/test/ThreadingTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright (c) 2022 ByteSkript org (Moderocky)
* View the full licence information and permissions:
* https://github.com/Moderocky/ByteSkript/blob/master/LICENSE
*/

package org.byteskript.skript.test;

import mx.kenzie.foundation.language.PostCompileClass;
import org.byteskript.skript.api.Event;
import org.byteskript.skript.api.ModifiableLibrary;
import org.byteskript.skript.api.note.EventValue;
import org.byteskript.skript.runtime.Skript;
import org.byteskript.skript.runtime.config.ConfigEntry;
import org.byteskript.skript.runtime.config.ConfigMap;
import org.junit.BeforeClass;
import org.junit.Test;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

public class ThreadingTest extends SkriptTest {

private static final Skript skript
= new Skript();
// = new Skript(new DebugSkriptCompiler(Stream.controller(System.out)));

private final String test = """
on test:
trigger:
print "Start"
wait 1 second
print "Finish"
""";
private static PostCompileClass cls;

@BeforeClass
public static void setup() {
skript.registerLibrary(new ModifiableLibrary("test") {{ // Credit: kiip1 for suggestion.
generateSyntaxFrom(TestEvent.class);
}});
cls = skript.compileScript(
"""
on test:
trigger:
wait 1 millisecond
set event-thing to 6
""", "skript.events");
skript.loadScript(cls);
}

@Test
public void eventSynchronized() throws Throwable {
final TestEvent event;
skript.runEvent(event = new TestEvent())
.all()
.get();
assert event.number == 6;
}

@org.byteskript.skript.api.note.Event("on test")
public static final class TestEvent extends Event {
public int number = 1;
@EventValue("thing")
public void setThing(Number number) {
this.number = (int) number;
}
}
}

0 comments on commit d881b28

Please sign in to comment.