Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ExprSecRunnable #7389

Open
wants to merge 7 commits into
base: dev/feature
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Combine with EffRun
  • Loading branch information
TheAbsolutionism committed Jan 10, 2025
commit fec805f1923ef1b150557bfd94165080b78e1cd2
108 changes: 60 additions & 48 deletions src/main/java/ch/njol/skript/effects/EffRun.java
Original file line number Diff line number Diff line change
@@ -21,23 +21,37 @@
import org.skriptlang.skript.util.Executable;

@Name("Run (Experimental)")
@Description("Executes a task (a function). Any returned result is discarded.")
@Description({
"Executes a function (any result from the function is discarded) or a runnable.",
"Executing a runnable will allow any local variables from the current code to be usable within the runnable. "
+ "Including any modifications to be present outside the runnable.",
"Executing a runnable with a delay (e.g. after 1 second) will behave as the following:",
" - Any local variables defined before the runnable is executed will be usable, but any changes within will not take affect outside the runnable.",
" - Any local variables defined within the runnable will not be usable outside of it.",
" - Any local variables defined after the runnable is executed will not be accessible within the runnable."
})
@Examples({
"set {_function} to the function named \"myFunction\"",
"run {_function}",
"run {_function} with arguments {_things::*}",
"set {_function} to the function named \"myFunction\"",
"run {_function}",
"run {_function} with arguments {_things::*}",
"",
"execute a new runnable:",
"\tbroadcast \"Hi!\"",
"set {_runnable} to a new runnable:",
"\tbroadcast \"Bye!\"",
"execute {_runnable}",
"run {_runnable} after 2 seconds",
"after 1 second execute {_runnable}"
})
@Since("2.10")
@Since("2.10, INSERT VERSION (runnables)")
@Keywords({"run", "execute", "reflection", "function"})
@SuppressWarnings({"rawtypes", "unchecked"})
public class EffRun extends Effect {

static {
Skript.registerEffect(EffRun.class,
"(run|execute) %executable% [arguments:with arg[ument]s %-objects%] [after:after %-timespan%]",
"(run|execute) %skriptrunnable% [after:after %-timespan%]",
"after %timespan% (run|execute) %executable% [arguments:with arg[ument]s %-objects%]",
"after %timespan% (run|execute) %skriptrunnable%");
"(run|execute) %executable/skriptrunnable% [arguments:with arg[ument]s %-objects%] [after:after %-timespan%]",
"after %timespan% (run|execute) %executable/skriptrunnable% [arguments:with arg[ument]s %-objects%]");
}

// We don't bother with the generic type here because we have no way to verify it
@@ -50,35 +64,28 @@ public class EffRun extends Effect {

@Override
public boolean init(Expression<?>[] exprs, int pattern, Kleenean isDelayed, ParseResult result) {
if (pattern >= 2) {
timespan = (Expression<Timespan>) exprs[0];
task = exprs[1];
} else {
if (pattern == 0) {
task = exprs[0];
if (result.hasTag("after"))
timespan = (Expression<Timespan>) exprs[2];
} else {
timespan = (Expression<Timespan>) exprs[0];
task = exprs[1];
}
hasArguments = result.hasTag("arguments");
if (Executable.class.isAssignableFrom(task.getReturnType())) {
if (hasArguments) {
Expression<?> args = pattern == 0 ? exprs[1] : exprs[2];
this.arguments = LiteralUtils.defendExpression(args);
Expression<?>[] arguments;
if (this.arguments instanceof ExpressionList<?>) {
arguments = ((ExpressionList<?>) this.arguments).getExpressions();
} else {
arguments = new Expression[]{this.arguments};
}
this.input = new DynamicFunctionReference.Input(arguments);
return LiteralUtils.canInitSafely(this.arguments);
if (hasArguments) {
Expression<?> args = pattern == 0 ? exprs[1] : exprs[2];
this.arguments = LiteralUtils.defendExpression(args);
Expression<?>[] arguments;
if (this.arguments instanceof ExpressionList<?> expressionList) {
arguments = expressionList.getExpressions();
} else {
this.input = new DynamicFunctionReference.Input();
}
} else if (SkriptRunnable.class.isAssignableFrom(task.getReturnType())) {
if (hasArguments) {
Skript.error("Cannot provide arguments when running a runnable. Can only be used for functions.");
return false;
arguments = new Expression[]{this.arguments};
}
this.input = new DynamicFunctionReference.Input(arguments);
return LiteralUtils.canInitSafely(this.arguments);
} else {
this.input = new DynamicFunctionReference.Input();
}
return true;
}
@@ -87,39 +94,44 @@ public boolean init(Expression<?>[] exprs, int pattern, Kleenean isDelayed, Pars
protected void execute(Event event) {
Object object = task.getSingle(event);
Runnable toRun = null;
long ticks = 0;
if (timespan != null) {
Timespan timespan = this.timespan.getSingle(event);
if (timespan != null)
ticks = timespan.getAs(TimePeriod.TICK);
}
if (object == null) {
return;
} else if (object instanceof SkriptRunnable skriptRunnable) {
if (ticks == 0) {
skriptRunnable.run(event, null);
return;
}
Object locals = Variables.copyLocalVariables(event);
SkriptRunnableEvent runnableEvent = new SkriptRunnableEvent();
toRun = () -> skriptRunnable.run(runnableEvent, locals);
} else if (object instanceof Executable executable) {
Object[] arguments;
if (task instanceof DynamicFunctionReference<?> reference) {
if (executable instanceof DynamicFunctionReference<?> reference) {
assert input != null;
Expression<?> validated = reference.validate(input);
if (validated == null)
return;
arguments = validated.getArray(event);
} else if (hasArguments) {
} else if (this.arguments != null) {
arguments = this.arguments.getArray(event);
} else {
arguments = new Object[0];
}
if (ticks == 0) {
executable.execute(event, arguments);
return;
}
toRun = () -> executable.execute(event, arguments);
} else if (object instanceof SkriptRunnable skriptRunnable) {
Object locals = Variables.copyLocalVariables(event);
SkriptRunnableEvent runnableEvent = new SkriptRunnableEvent();
toRun = () -> skriptRunnable.run(runnableEvent, locals);
}
if (toRun == null)
return;
long ticks = 0;
if (timespan != null) {
Timespan timespan = this.timespan.getSingle(event);
if (timespan != null)
ticks = timespan.getAs(TimePeriod.TICK);
}
if (ticks == 0) {
toRun.run();
} else {
Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), toRun, ticks);
}
Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), toRun, ticks);
}

@Override
Original file line number Diff line number Diff line change
@@ -156,15 +156,29 @@ protected void setTriggerItems(List<TriggerItem> items) {
* {@link #loadCode(SectionNode)},
* {@link #loadOptionalCode(SectionNode)},
* or {@link #setTriggerItems(List)}.
* This will effectively utilize the local variables that are provided by the {@code event}
* allowing modification from within this section to be present outside of this section.
* @param event The event to pass as context.
* @return False if an exception occurred while executing the section.
*/
protected boolean runSection(Event event) {
return runSection(event, null);
}

protected boolean runSection(Event event, @Nullable Object locals) {
return this.section.runSection(event, locals);
/**
* Executes the code within the section associated with this expression.
* Before calling this method, the section must have been loaded through:
* {@link #loadCode(SectionNode)},
* {@link #loadOptionalCode(SectionNode)},
* or {@link #setTriggerItems(List)}.
* Providing {@code localVariables} will treat the {@code event} to be different than the current event of the current trigger.
* Allowing access to all local variables set before this method call and any modifications within to not take effect outside the event and section.
* @param event The event to pass as context.
* @param localVariables Local variables to be copied into the {@code event}
* @return False if an exception occurred while executing the section.
*/
protected boolean runSection(Event event, @Nullable Object localVariables) {
return this.section.runSection(event, localVariables);
}

}
14 changes: 9 additions & 5 deletions src/main/java/ch/njol/skript/lang/ExpressionSection.java
Original file line number Diff line number Diff line change
@@ -68,11 +68,15 @@ public void loadOptionalCode(SectionNode sectionNode) {
super.loadOptionalCode(sectionNode);
}

public boolean runSection(Event event, @Nullable Object locals) {
if (locals != null)
Variables.setLocalVariables(event, locals);
boolean status = TriggerItem.walk(this.first, event);
Variables.removeLocals(event);
public boolean runSection(Event event, @Nullable Object localVariables) {
boolean status;
if (localVariables != null) {
Variables.setLocalVariables(event, localVariables);
status = TriggerItem.walk(this.first, event);
Variables.removeLocals(event);
} else {
status = TriggerItem.walk(this.first, event);
}
return status;
}

49 changes: 46 additions & 3 deletions src/main/java/ch/njol/skript/sections/ExprSecRunnable.java
Original file line number Diff line number Diff line change
@@ -3,7 +3,10 @@
import ch.njol.skript.Skript;
import ch.njol.skript.classes.ClassInfo;
import ch.njol.skript.config.SectionNode;
import ch.njol.skript.doc.NoDoc;
import ch.njol.skript.doc.Description;
import ch.njol.skript.doc.Examples;
import ch.njol.skript.doc.Name;
import ch.njol.skript.doc.Since;
import ch.njol.skript.expressions.base.SectionExpression;
import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.ExpressionType;
@@ -19,7 +22,43 @@

import java.util.List;

@NoDoc
@Name("Runnable")
@Description({
"Create a runnable where the code within the section can be executed anywhere using 'EffRun'.",
"Executing a runnable after a delay will not halt the code that is defined afterwards.",
"Using a delay will make the runnable behave as the following:",
" - Any local variables defined before the runnable is executed will be usable, but any changes within will not take affect outside the runnable.",
" - Any local variables defined within the runnable will not be usable outside of it.",
" - Any local variables defined after the runnable is executed will not be accessible within the runnable."
})
@Examples({
"# Without a delay",
"set {_a} to 1",
"execute a new runnable:",
"\tadd 1 to {_a}",
"\tset {_b} to 3",
"if {_a} is 2:",
"\t# This will pass",
"if {_b} is 3:",
"\t# This will pass",
"",
"# With a delay",
"set {_a} to 1",
"after 2 seconds execute a new runnable:",
"\tadd 1 to {_a}",
"\tset {_b} to 3",
"if {_a} is 2:",
"\t# This will fail",
"if {_b} is set:",
"\t# This will fail",
"",
"set {_runnable} a new runnable:",
"\tbroadcast \"Welcome!\"",
"run {_runnable}",
"run {_runnable} after 2 seconds",
"after 1 second run {_runnable}"
})
@Since("INSERT VERSION")
public class ExprSecRunnable extends SectionExpression<Object> {

public static class SkriptRunnable {
@@ -34,6 +73,10 @@ public void run(Event event, @Nullable Object locals) {
exprRunnable.runSection(event, locals);
}

@Override
public String toString() {
return "skript runnable";
}
}

public static class SkriptRunnableEvent extends Event {
@@ -80,7 +123,7 @@ public boolean isSingle() {
}

@Override
public Class<?> getReturnType() {
public Class<SkriptRunnable> getReturnType() {
return SkriptRunnable.class;
}

25 changes: 25 additions & 0 deletions src/test/skript/tests/syntaxes/effects/EffRun.sk
Original file line number Diff line number Diff line change
@@ -58,3 +58,28 @@ test "run external functions":
run {_function}
assert {ExprFunction} is true with "external local function didn't run"
delete {ExprFunction}

test "run variable runnable":
set {_a} to 1
set {_runnable} to a new runnable:
assert {_a} is 1 with "Local variable 'a' should keep context from before the runnable section is instantiated"
assert {_b} is not set with "Local variable 'b' should not be present as it's created after the execution of this runnable"
add 1 to {_a}
assert {_a} is 2 with "Local variable 'a' should still be changeable"
set {_c} to 4
execute {_runnable} after 1 tick
set {_b} to 3
assert {_a} is 1 with "Local variable 'a' should keep context from only this event and not be changed from within the runnable"
assert {_c} is not set with "Local variable 'c' should not be present as it's created within the runnable and should not be transferred outside of it"

test "run section runnable:":
set {_a} to 1
after 1 tick execute a new runnable:
assert {_a} is 1 with "Local variable 'a' should keep context from before the runnable section is instantiated"
assert {_b} is not set with "Local variable 'b' should not be present as it's created after the execution of this runnable"
add 1 to {_a}
assert {_a} is 2 with "Local variable 'a' should still be changeable"
set {_c} to 4
set {_b} to 3
assert {_a} is 1 with "Local variable 'a' should keep context from only this event and not be changed from within the runnable"
assert {_c} is not set with "Local variable 'c' should not be present as it's created within the runnable and should not be transferred outside of it"