From 0e9df3701ff832202eed46a681edd355a25d7b96 Mon Sep 17 00:00:00 2001 From: gnembon Date: Thu, 22 Jul 2021 22:22:38 -0300 Subject: [PATCH] rework for events and other stored functions. Fixes #984 --- docs/scarpet/api/Entities.md | 11 +- docs/scarpet/api/Events.md | 55 ++++++--- .../ArmorStandEntity_scarpetMarkerMixin.java | 2 +- .../java/carpet/script/CarpetEventServer.java | 109 +++++++++++------- .../java/carpet/script/CarpetScriptHost.java | 42 +++---- .../carpet/script/CarpetScriptServer.java | 95 ++------------- .../java/carpet/script/EntityEventsGroup.java | 6 +- src/main/java/carpet/script/ScriptHost.java | 5 + src/main/java/carpet/script/api/Entities.java | 1 - 9 files changed, 139 insertions(+), 187 deletions(-) diff --git a/docs/scarpet/api/Entities.md b/docs/scarpet/api/Entities.md index 69aa759a..9e0be92b 100644 --- a/docs/scarpet/api/Entities.md +++ b/docs/scarpet/api/Entities.md @@ -815,22 +815,25 @@ In case you want to pass an event handler that is not defined in your module, pl ### `entity_load_handler(descriptor / descriptors, function)`, `entity_load_handler(descriptor / descriptors, call_name, ... args?)` -Attaches a callback to when any entity matching the following type / types is loaded in the game, allowing to grab a handle +Attaches a callback to trigger when any entity matching the following type / types is loaded in the game, allowing to grab a handle to an entity right when it is loaded to the world without querying them every tick. Callback expects two parameters - the entity, -and a boolean indicating if the entity was newly created(`true`) or just loaded from disk. Single argument functions accepting +and a boolean value indicating if the entity was newly created(`true`) or just loaded from disk. Single argument functions accepting only entities are allowed, but deprecated and will be removed at some point. If callback is `null`, then the current entity handler, if present, is removed. Consecutive calls to `entity_load_handler` will add / subtract of the currently targeted entity types pool. -Like other global events, calls to `entity_load_handler` can only be attached in apps with global scope. Player scope makes so -that it is not clear which player to use run the load call. +Like other global events, calls to `entity_load_handler` should only be attached in apps with global scope. For player scope apps, +it will be called multiple times, once for each player. That's likely not what you want to do. ``` // veryfast method of getting rid of all the zombies. Callback is so early, its packets haven't reached yet the clients // so to save on log errors, removal of mobs needs to be scheduled for later. entity_load_handler('zombie', _(e, new) -> schedule(0, _(outer(e)) -> modify(e, 'remove'))) +// another way to do it is to remove the entity when it starts ticking +entity_load_handler('zombie', _(e, new) -> entity_event(e, 'on_tick', _(e) -> modify(e, 'remove'))) + // making all zombies immediately faster and less susceptible to friction of any sort entity_load_handler('zombie', _(e, new) -> entity_event(e, 'on_tick', _(e) -> modify(e, 'motion', 1.2*e~'motion'))) ``` diff --git a/docs/scarpet/api/Events.md b/docs/scarpet/api/Events.md index fb72e013..008c250a 100644 --- a/docs/scarpet/api/Events.md +++ b/docs/scarpet/api/Events.md @@ -6,11 +6,14 @@ events occur, but app designers can create their own events and trigger them acr When loading the app, each function that starts with `__on_` and has the required arguments, will be bound automatically to a corresponding built-in event. '`undef`'ying -of such function would result in unbinding the app from this event. +of such function would result in unbinding the app from this event. Defining event hanlder via `__on_(... args) -> expr` is +equivalent of defining it via `handle_event('', _(... args) -> expr)` In case of `player` scoped apps, all player action events will be directed to the appropriate player hosts. Global events, like `'tick'`, that don't have a specific -player target will be executed multiple times, once for each player, and once in `'global'` scoped apps. +player target will be executed multiple times, once for each player app instance. While each player app instance is independent, +statically defined event handlers will be copied to each players app, but if you want to apply them in more controlled way, +defining event handlers for each player in `__on_start()` function is preferred. Most built-in events strive to report right before they take an effect in the game. The purpose of that is that this give a choice for the programmer to handle them right away (as it happens, potentially affect the course of action by changing the @@ -21,24 +24,43 @@ more control over these events. Programmers can also define their own events and signal other events, including built-in events, and across all loaded apps. -## Event list +## App scopes and event distribution -Here is a list of events that are handled by default in scarpet. This list includes prefixes for function names, allowing apps -to autoload them, but you can always add any function to any event (using `/script event` command) -if it accepts required number of parameters. +Events triggered in an app can result in zero, one, or multiple executions, depending on the type of the event, and the app scope. + * player targeted events (like `player_breaks_block`) target each app once: + * for global scoped apps - targets a single app instance and provides `player` as the first argument. + * for player scoped apps - targets only a given player instance, providing player argument for API consistency, + since active player in player scoped apps can always be retrived using `player()`. + * global events could be handled by multiple players multiple times (like `explosion`, or `tick`): + * for global scoped apps - triggered once for the single app instance. + * for player scoped apps - triggered N times for each player separately, so they can do something with that information + * custom player targeted events (using `signal_event(, , data)`): + * for global scoped apps - doesn't trigger at all, since there is no way to pass the required player. + To target global apps with player information, use `null` for player target, and add player information to the `data` + * for player scoped apps - triggers once for the specified player and its app instance + * custom general events (using `signal_event(, null, data)`) behave same like built-in global events: + * for global scoped apps - triggers once for the only global instance + * for player scoped apps - triggers N times, once for each player app instance + +## Built-in events + +Here is the list of events that are handled by default in scarpet. This list includes prefixes for function names, allowing apps +to register them when the app starts, but you can always add any handler function to any event using `/script event` command, +if it accepts the required number of parameters for the event. ## Meta-events These events are not controlled / triggered by the game per se, but are important for the flow of the apps, however for all -intent and purpose can be treated as regular events. Unlike regular events, they cannot be hooked up to with 'handle_event', -but the apps themselves need to have them defined as distinct function definitions, same they cannot be signalled. +intent and purpose can be treated as regular events. Unlike regular events, they cannot be hooked up to with `handle_event()`, +and the apps themselves need to have them defined as distinct function definitions. They also cannot be triggered via `signal_event()`. ### `__on_start()` Called once per app in its logical execution run. For `'global'` scope apps its executed right after the app is loaded. For `'player'` scope apps, it is triggered once per player before the app can be used by that player. Since each player app acts independently from other player apps, this is probably the best location to include some player specific initializations. Static code (i.e. code typed directly in the app code that executes immediately, outside of function definitions), will only execute once -per app, regardless of scope, `'__on_start()'` allows to reliably call player specific initializations. +per app, regardless of scope, `'__on_start()'` allows to reliably call player specific initializations. However, most event handlers +defined in the static body of the app will be copied over to each player scoped instance when they join. ### `__on_close()` @@ -50,8 +72,7 @@ the system is closing down exceptionally. ## Built-in global events Global events will be handled once per app that is with `'global'` scope. With `player` scoped apps, each player instance - is responsible independently from handling their events, so a global event may be executed multiple times for each player. - + will be triggerd once for each player, so a global event may be executed multiple times for such apps. ### `__on_server_starts()` Event triggers after world is loaded and after all startup apps have started. It won't be triggered with `/reload`. @@ -73,13 +94,13 @@ Duplicate of `tick`, just automatically located in the end. Use `__on_tick() -> ### `__on_chunk_generated(x, z)` Called right after a chunk at a given coordinate is full generated. `x` and `z` correspond to the lowest x and z coords in the chunk. Handling of this event is scheduled as an off-tick task happening after the -chunk is confirmed to be generated and loaded to the game, so -handling of this event is not technically guaranteed, if the game crashes while players are moving for example, and can -be missed on the next start, if the game successfully saves it and fails to execute all remaining tasks. In normal operation -this should not happen, but be warned. +chunk is confirmed to be generated and loaded to the game, due to the off-thread chunk loading in the game. So +handling of this event is not technically guaranteed if the game crashes while players are moving for example, and the game +decides to shut down after chunk is fully loaded and before its handler is processed in between ticks. In normal operation +this should not happen, but let you be warned. ### `__on_chunk_loaded(x, z)` -Called right after a chunk at a given coordinate is loaded. All newly generated chunks are loaded as well. +Called right after a chunk at a given coordinate is loaded. All newly generated chunks are considered loaded as well. `x` and `z` correspond to the lowest x and z coordinates in the chunk. ### `__on_lightning(block, mode)` @@ -103,7 +124,7 @@ The parameter `entities` contains the list of entities that have been affected b Triggered when a carpet mod rule is changed. It includes extension rules, not using default `/carpet` command, which will then be namespaced as `namespace:rule`. -### entity load event -> check `entity_load_handler()` +### Entity load event -> check in details on `entity_load_handler()` These will trigger every time an entity of a given type is loaded into the game: spawned, added with a chunks, spawned from commands, anything really. Check `entity_load_handler()` in the entity section for details. diff --git a/src/main/java/carpet/mixins/ArmorStandEntity_scarpetMarkerMixin.java b/src/main/java/carpet/mixins/ArmorStandEntity_scarpetMarkerMixin.java index 5bb4b7c2..9150fa92 100644 --- a/src/main/java/carpet/mixins/ArmorStandEntity_scarpetMarkerMixin.java +++ b/src/main/java/carpet/mixins/ArmorStandEntity_scarpetMarkerMixin.java @@ -38,7 +38,7 @@ public abstract class ArmorStandEntity_scarpetMarkerMixin extends LivingEntity if (owner.isPresent()) { String hostName = StringUtils.removeStart(owner.get(),prefix); - if (!hostName.isEmpty() && CarpetServer.scriptServer.getHostByName(hostName) == null) + if (!hostName.isEmpty() && CarpetServer.scriptServer.getAppHostByName(hostName) == null) { discard(); //discard } diff --git a/src/main/java/carpet/script/CarpetEventServer.java b/src/main/java/carpet/script/CarpetEventServer.java index fa80b5b7..9319b591 100644 --- a/src/main/java/carpet/script/CarpetEventServer.java +++ b/src/main/java/carpet/script/CarpetEventServer.java @@ -3,7 +3,9 @@ package carpet.script; import carpet.CarpetServer; import carpet.CarpetSettings; import carpet.helpers.TickSpeed; +import carpet.script.exception.IntegrityException; import carpet.script.exception.InternalExpressionException; +import carpet.script.exception.InvalidCallbackException; import carpet.script.value.BlockValue; import carpet.script.value.BooleanValue; import carpet.script.value.EntityValue; @@ -25,7 +27,6 @@ import net.minecraft.entity.TntEntity; import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.passive.MerchantEntity; import net.minecraft.entity.projectile.ProjectileEntity; -import net.minecraft.server.MinecraftServer; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.entity.Entity; import net.minecraft.server.network.ServerPlayerEntity; @@ -46,7 +47,6 @@ import net.minecraft.village.Merchant; import net.minecraft.village.TradeOffer; import net.minecraft.world.World; import net.minecraft.world.explosion.Explosion; -import org.apache.commons.lang3.tuple.Pair; import java.util.ArrayList; import java.util.Arrays; @@ -70,10 +70,17 @@ import java.util.stream.Collectors; public class CarpetEventServer { public final List scheduledCalls = new LinkedList<>(); - public final MinecraftServer server; + public final CarpetScriptServer scriptServer; private static final List NOARGS = Collections.emptyList(); public final Map customEvents = new HashMap<>(); + + + public enum CallbackResult + { + SUCCESS, PASS, FAIL + } + public static class Callback { public final String host; @@ -81,7 +88,6 @@ public class CarpetEventServer public final FunctionValue function; public final List parametrizedArgs; - public Callback(String host, String target, FunctionValue function, List parametrizedArgs) { this.host = host; @@ -95,18 +101,17 @@ public class CarpetEventServer * @param sender - entity command source * @param runtimeArgs = options */ - public boolean execute(ServerCommandSource sender, List runtimeArgs) + public CallbackResult execute(ServerCommandSource sender, List runtimeArgs) { if (!this.parametrizedArgs.isEmpty()) { runtimeArgs = new ArrayList<>(runtimeArgs); runtimeArgs.addAll(this.parametrizedArgs); } - if (CarpetServer.scriptServer == null) return false; // already stopped - return CarpetServer.scriptServer.runEventCall( + if (CarpetServer.scriptServer == null) return CallbackResult.FAIL; // already stopped + return CarpetServer.scriptServer.events.runEventCall( sender.withLevel(CarpetSettings.runPermissionLevel), - host, optionalTarget, function, runtimeArgs - ); + host, optionalTarget, function, runtimeArgs); } /** @@ -115,18 +120,12 @@ public class CarpetEventServer * @param optionalRecipient - optional target player argument * @param runtimeArgs = options */ - public int signal(ServerCommandSource sender, ServerPlayerEntity optionalRecipient, List runtimeArgs) + public CallbackResult signal(ServerCommandSource sender, ServerPlayerEntity optionalRecipient, List runtimeArgs) { // recipent of the call doesn't match the handlingHost if (optionalRecipient != null && !optionalRecipient.getEntityName().equals(optionalTarget)) - return 0; - List args = runtimeArgs; - if (!this.parametrizedArgs.isEmpty()) - { - args = new ArrayList<>(runtimeArgs); - args.addAll(this.parametrizedArgs); - } - return CarpetServer.scriptServer.signal(sender, optionalRecipient, host, function, args, false); + return CallbackResult.FAIL; + return execute(sender, runtimeArgs); } @@ -178,10 +177,8 @@ public class CarpetEventServer */ public void execute() { - CarpetServer.scriptServer.runScheduledCall(ctx.origin, ctx.s, host, (CarpetScriptHost) ctx.host, function, parametrizedArgs); + CarpetServer.scriptServer.events.runScheduledCall(ctx.origin, ctx.s, host, (CarpetScriptHost) ctx.host, function, parametrizedArgs); } - - } public static class CallbackList @@ -190,7 +187,6 @@ public class CarpetEventServer public final List callList; public final int reqArgs; final boolean isSystem; - final boolean isGlobalOnly; final boolean perPlayerDistribution; public CallbackList(int reqArgs, boolean isSystem, boolean isGlobalOnly) @@ -198,7 +194,6 @@ public class CarpetEventServer this.callList = new ArrayList<>(); this.reqArgs = reqArgs; this.isSystem = isSystem; - this.isGlobalOnly = isGlobalOnly; perPlayerDistribution = isSystem && !isGlobalOnly; } @@ -230,7 +225,7 @@ public class CarpetEventServer // supressing calls where target player hosts simply don't match // handling global hosts with player targets is left to when the host is resolved (few calls deeper). if (nameCheck != null && call.optionalTarget != null && !nameCheck.equals(call.optionalTarget)) continue; - if (!call.execute(source, argv)) fails.add(call); + if (call.execute(source, argv) == CallbackResult.FAIL) fails.add(call); } for (Callback call : fails) callList.remove(call); CarpetProfiler.end_current_section(currentSection); @@ -245,7 +240,9 @@ public class CarpetEventServer int successes = 0; for (Callback call: callList) { - successes += Math.max(0, call.signal(sender, optinoalReceipient, callArg)); + //successes += Math.max(0, call.signal(sender, optinoalReceipient, callArg)); + if (call.signal(sender, optinoalReceipient, callArg) == CallbackResult.SUCCESS) successes ++; + } //for (Callback call : fails) callList.remove(call); return successes; @@ -253,7 +250,7 @@ public class CarpetEventServer public boolean addFromExternal(ServerCommandSource source, String hostName, String funName, Consumer hostOnEventHandler) { - ScriptHost host = CarpetServer.scriptServer.getHostByName(hostName); + ScriptHost host = CarpetServer.scriptServer.getAppHostByName(hostName); if (host == null) { // impossible call to add @@ -991,8 +988,6 @@ public class CarpetEventServer public final String name; public final CallbackList handler; - @Deprecated - public final boolean globalOnly; public final boolean isPublic; // public events can be targetted with __on_ defs public Event(String name, int reqArgs, boolean isGlobalOnly) { @@ -1002,7 +997,6 @@ public class CarpetEventServer { this.name = name; this.handler = new CallbackList(reqArgs, true, isGlobalOnly); - this.globalOnly = isGlobalOnly; this.isPublic = isPublic; byName.put(name, this); } @@ -1050,7 +1044,6 @@ public class CarpetEventServer { this.name = name; this.handler = new CallbackList(1, false, false); - this.globalOnly = false; this.isPublic = true; server.events.customEvents.put(name, this); } @@ -1122,9 +1115,9 @@ public class CarpetEventServer } - public CarpetEventServer(MinecraftServer server) + public CarpetEventServer(CarpetScriptServer scriptServer) { - this.server = server; + this.scriptServer = scriptServer; Event.clearAllBuiltinEvents(); } @@ -1155,6 +1148,45 @@ public class CarpetEventServer scheduledCalls.add(new ScheduledCall(context, function, args, due)); } + public void runScheduledCall(BlockPos origin, ServerCommandSource source, String hostname, CarpetScriptHost host, FunctionValue udf, List argv) + { + if (hostname != null && !scriptServer.modules.containsKey(hostname)) // well - scheduled call app got unloaded + return; + try + { + host.callUDF(origin, source, udf, argv); + } + catch (NullPointerException | InvalidCallbackException | IntegrityException ignored) { } + } + + public CallbackResult runEventCall(ServerCommandSource sender, String hostname, String optionalTarget, FunctionValue udf, List argv) + { + CarpetScriptHost appHost = scriptServer.getAppHostByName(hostname); + // no such app + if (appHost == null) return CallbackResult.FAIL; + // dummy call for player apps that reside on the global copy - do not run them, but report as passes. + if (appHost.isPerUser() && optionalTarget==null) return CallbackResult.PASS; + ServerPlayerEntity target = null; + if (optionalTarget != null) + { + target = sender.getServer().getPlayerManager().getPlayer(optionalTarget); + if (target == null) return CallbackResult.FAIL; + } + ServerCommandSource source = sender.withLevel(CarpetSettings.runPermissionLevel); + CarpetScriptHost executingHost = appHost.retrieveForExecution(sender, target); + if (executingHost == null) return CallbackResult.FAIL; + try + { + executingHost.callUDF(BlockPos.ORIGIN, source.withLevel(CarpetSettings.runPermissionLevel), udf, argv); + return CallbackResult.SUCCESS; + } + catch (NullPointerException | InvalidCallbackException | IntegrityException error) + { + CarpetScriptServer.LOG.error("Got exception: "+error.getMessage()); + return CallbackResult.FAIL; + } + } + public boolean addEventFromCommand(ServerCommandSource source, String event, String host, String funName) { Event ev = Event.getEvent(event, CarpetServer.scriptServer); @@ -1178,7 +1210,7 @@ public class CarpetEventServer public boolean handleCustomEvent(String event, CarpetScriptHost host, FunctionValue function, List args) { - Event ev = Event.getOrCreateCustom(event, host.getScriptServer()); + Event ev = Event.getOrCreateCustom(event, scriptServer); onEventAddedToHost(ev, host); return ev.handler.addEventCallInternal(host, function, args==null?NOARGS:args); } @@ -1233,15 +1265,4 @@ public class CarpetEventServer // remove scheduled calls scheduledCalls.removeIf(sc -> sc.host != null && sc.host.equals(host.getName())); } - - private Pair decodeCallback(String funName) - { - Pattern find = Pattern.compile("(\\w+)\\(from (\\w+)\\)"); - Matcher matcher = find.matcher(funName); - if(matcher.matches()) - { - return Pair.of(matcher.group(2), matcher.group(1)); - } - return Pair.of(null, funName); - } } diff --git a/src/main/java/carpet/script/CarpetScriptHost.java b/src/main/java/carpet/script/CarpetScriptHost.java index 8e8a4698..5895a805 100644 --- a/src/main/java/carpet/script/CarpetScriptHost.java +++ b/src/main/java/carpet/script/CarpetScriptHost.java @@ -10,6 +10,7 @@ import carpet.script.command.CommandArgument; import carpet.script.command.CommandToken; import carpet.script.exception.CarpetExpressionException; import carpet.script.exception.ExpressionException; +import carpet.script.exception.IntegrityException; import carpet.script.exception.InternalExpressionException; import carpet.script.exception.InvalidCallbackException; import carpet.script.utils.AppStoreManager; @@ -644,37 +645,19 @@ public class CarpetScriptHost extends ScriptHost } } - public List retrieveForExecution(ServerCommandSource source, String optionalTarget) + public CarpetScriptHost retrieveForExecution(ServerCommandSource source, ServerPlayerEntity player) { - List targets = new ArrayList<>(); - if (perUser) + CarpetScriptHost target = null; + if (!perUser) { - if (optionalTarget == null) - { - for (ServerPlayerEntity player : source.getServer().getPlayerManager().getPlayerList()) - { - CarpetScriptHost host = (CarpetScriptHost) retrieveForExecution(player.getEntityName()); - targets.add(host); - if (host.errorSnooper == null) host.setChatErrorSnooper(player.getCommandSource()); - } - } - else - { - ServerPlayerEntity player = source.getServer().getPlayerManager().getPlayer(optionalTarget); - if (player != null) - { - CarpetScriptHost host = (CarpetScriptHost) retrieveForExecution(player.getEntityName()); - targets.add(host); - if (host.errorSnooper == null) host.setChatErrorSnooper(player.getCommandSource()); - } - } + target = this; } - else + else if (player != null) { - targets.add(this); - if (this.errorSnooper == null) this.setChatErrorSnooper(source); + target = (CarpetScriptHost) retrieveForExecution(player.getEntityName()); } - return targets; + if (target != null && target.errorSnooper == null) target.setChatErrorSnooper(source); + return target; } public CarpetScriptHost retrieveOwnForExecution(ServerCommandSource source) throws CommandSyntaxException @@ -830,6 +813,7 @@ public class CarpetScriptHost extends ScriptHost try { // TODO: this is just for now - invoke would be able to invoke other hosts scripts + assertAppIntegrity(function.getModule()); Context context = new CarpetContext(this, source, BlockPos.ORIGIN); return function.getExpression().evalValue( () -> function.lazyEval(context, Context.VOID, function.getExpression(), function.getToken(), argv), @@ -860,7 +844,7 @@ public class CarpetScriptHost extends ScriptHost } try { - // TODO: this is just for now - invoke would be able to invoke other hosts scripts + assertAppIntegrity(function.getModule()); Context context = new CarpetContext(this, source, BlockPos.ORIGIN); return function.getExpression().evalValue( () -> function.execute(context, Context.VOID, function.getExpression(), function.getToken(), argv), @@ -874,7 +858,7 @@ public class CarpetScriptHost extends ScriptHost } } - public Value callUDF(BlockPos pos, ServerCommandSource source, FunctionValue fun, List argv) throws InvalidCallbackException + public Value callUDF(BlockPos pos, ServerCommandSource source, FunctionValue fun, List argv) throws InvalidCallbackException, IntegrityException { if (CarpetServer.scriptServer.stopAll) return Value.NULL; @@ -889,7 +873,7 @@ public class CarpetScriptHost extends ScriptHost } try { - // TODO: this is just for now - invoke would be able to invoke other hosts scripts + assertAppIntegrity(fun.getModule()); Context context = new CarpetContext(this, source, pos); return fun.getExpression().evalValue( () -> fun.execute(context, Context.VOID, fun.getExpression(), fun.getToken(), argv), diff --git a/src/main/java/carpet/script/CarpetScriptServer.java b/src/main/java/carpet/script/CarpetScriptServer.java index f6000cb5..516c8643 100644 --- a/src/main/java/carpet/script/CarpetScriptServer.java +++ b/src/main/java/carpet/script/CarpetScriptServer.java @@ -12,8 +12,6 @@ import carpet.script.bundled.BundledModule; import carpet.CarpetServer; import carpet.script.bundled.FileModule; import carpet.script.bundled.Module; -import carpet.script.exception.IntegrityException; -import carpet.script.exception.InvalidCallbackException; import carpet.script.language.Arithmetic; import carpet.script.language.ControlFlow; import carpet.script.language.DataStructures; @@ -23,7 +21,6 @@ import carpet.script.language.Sys; import carpet.script.language.Threading; import carpet.script.utils.AppStoreManager; import carpet.script.value.FunctionValue; -import carpet.script.value.Value; import carpet.utils.CarpetProfiler; import carpet.utils.Messenger; import com.mojang.brigadier.exceptions.CommandSyntaxException; @@ -35,7 +32,6 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.util.WorldSavePath; -import net.minecraft.util.math.BlockPos; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -120,9 +116,9 @@ public class CarpetScriptServer private void init() { ScriptHost.systemGlobals.clear(); - events = new CarpetEventServer(server); + events = new CarpetEventServer(this); modules = new HashMap<>(); - unloadableModules = new HashSet(); + unloadableModules = new HashSet<>(); tickStart = 0L; stopAll = false; holyMoly = server.getCommandManager().getDispatcher().getRoot().getChildren().stream().map(CommandNode::getName).collect(Collectors.toSet()); @@ -232,7 +228,7 @@ public class CarpetScriptServer return moduleNames; } - public ScriptHost getHostByName(String name) + public CarpetScriptHost getAppHostByName(String name) { if (name == null) return globalHost; @@ -310,7 +306,10 @@ public class CarpetScriptServer if (newHost.isPerUser()) { // that will provide player hosts right at the startup - newHost.retrieveForExecution(source, null); + for (ServerPlayerEntity player : source.getServer().getPlayerManager().getPlayerList()) + { + newHost.retrieveForExecution(player.getCommandSource(), player); + } } else { @@ -372,86 +371,6 @@ public class CarpetScriptServer return false; } - public boolean runEventCall(ServerCommandSource sender, String hostname, String optionalTarget, FunctionValue udf, List argv) - { - CarpetScriptHost host = globalHost; - if (hostname != null) - host = modules.get(hostname); - if (host == null) return false; - // dummy call for player apps that reside on the global copy - do not run them, but report as successes. - if (host.isPerUser() && optionalTarget==null) return true; - ServerPlayerEntity target = null; - if (optionalTarget != null) - { - target = sender.getServer().getPlayerManager().getPlayer(optionalTarget); - if (target == null) return false; - } - int successes = signal(sender, target, hostname, udf, argv, true ); - return successes >= 0; - } - - public void runScheduledCall(BlockPos origin, ServerCommandSource source, String hostname, CarpetScriptHost host, FunctionValue udf, List argv) - { - if (hostname != null && !modules.containsKey(hostname)) // well - scheduled call app got unloaded - return; - try - { - host.callUDF(origin, source, udf, argv); - } - catch (NullPointerException | InvalidCallbackException ignored) - { - } - } - - /** - * returns number of successful calls in the host or -1 if failed exceptionally - * @param sender - * @param optionalRecipient - * @param hostname - * @param udf - * @param argv - * @param reportFails - returns -1 when any call failed - * @return - */ - public int signal(ServerCommandSource sender, ServerPlayerEntity optionalRecipient, String hostname, FunctionValue udf, List argv, boolean reportFails) - { - - if (hostname == null) - { - if (optionalRecipient != null) return 0; - try - { - globalHost.callUDF(BlockPos.ORIGIN, sender, udf, argv); - } - catch (NullPointerException | InvalidCallbackException npe) - { - return reportFails?-1:0; - } - return 1; - } - String hostRecipient = optionalRecipient == null?null:optionalRecipient.getEntityName(); - ServerCommandSource source = (optionalRecipient == null)? sender : optionalRecipient.getCommandSource().withLevel(CarpetSettings.runPermissionLevel); - int successes = 0; - for (CarpetScriptHost host : modules.get(hostname).retrieveForExecution(sender, hostRecipient)) - { - // getPlayer will always return nonnull cause retrieve for execution only returns hosts for existing players. - ServerCommandSource executingSource = host.perUser - ? source.getServer().getPlayerManager().getPlayer(host.user).getCommandSource() - : source.getServer().getCommandSource(); - try - { - host.callUDF(BlockPos.ORIGIN, source.withLevel(CarpetSettings.runPermissionLevel), udf, argv); - } - catch (NullPointerException | InvalidCallbackException | IntegrityException npe) - { - if (reportFails) return -1; - continue; - } - successes ++; - } - return successes; - } - public void tick() { CarpetProfiler.ProfilerToken token; diff --git a/src/main/java/carpet/script/EntityEventsGroup.java b/src/main/java/carpet/script/EntityEventsGroup.java index 5d328ec3..f21e2948 100644 --- a/src/main/java/carpet/script/EntityEventsGroup.java +++ b/src/main/java/carpet/script/EntityEventsGroup.java @@ -38,7 +38,7 @@ public class EntityEventsGroup { Map.Entry, CarpetEventServer.Callback> action = iterator.next(); Pair key = action.getKey(); - ScriptHost host = CarpetServer.scriptServer.getHostByName(key.getLeft()); + ScriptHost host = CarpetServer.scriptServer.getAppHostByName(key.getLeft()); if (host == null) { iterator.remove(); @@ -52,7 +52,7 @@ public class EntityEventsGroup continue; } } - if (!type.call(action.getValue(), entity, args)) + if (type.call(action.getValue(), entity, args) == CarpetEventServer.CallbackResult.FAIL) iterator.remove(); } if (actionSet.isEmpty()) actions.remove(type); @@ -125,7 +125,7 @@ public class EntityEventsGroup } return new CarpetEventServer.Callback(key.getLeft(), key.getRight(), function, extraArgs); } - public boolean call(CarpetEventServer.Callback tickCall, Entity entity, Object ... args) + public CarpetEventServer.CallbackResult call(CarpetEventServer.Callback tickCall, Entity entity, Object ... args) { assert args.length == argcount-1; return tickCall.execute(entity.getCommandSource(), makeArgs(entity, args)); diff --git a/src/main/java/carpet/script/ScriptHost.java b/src/main/java/carpet/script/ScriptHost.java index 919e0ced..05b3e0f2 100644 --- a/src/main/java/carpet/script/ScriptHost.java +++ b/src/main/java/carpet/script/ScriptHost.java @@ -305,6 +305,11 @@ public abstract class ScriptHost return data; } + protected void assertAppIntegrity(Module module) + { + getModuleData(module); + } + public void addUserDefinedFunction(Context ctx, Module module, String name, FunctionValue fun) { getModuleData(module).globalFunctions.put(name, fun); diff --git a/src/main/java/carpet/script/api/Entities.java b/src/main/java/carpet/script/api/Entities.java index db68f7be..8f4de7c8 100644 --- a/src/main/java/carpet/script/api/Entities.java +++ b/src/main/java/carpet/script/api/Entities.java @@ -262,7 +262,6 @@ public class Entities { expression.addContextFunction("entity_load_handler", -1, (c, t, lv) -> { - if (c.host.isPerUser()) throw new InternalExpressionException("'entity_load_handler' can only be called in apps with global scope"); if (lv.size() < 2) throw new InternalExpressionException("'entity_load_handler' required the entity type, and a function to call"); Value entityValue = lv.get(0); List descriptors = (entityValue instanceof ListValue)