diff --git a/docs/scarpet/api/Events.md b/docs/scarpet/api/Events.md index 08bc40b3..e646df83 100644 --- a/docs/scarpet/api/Events.md +++ b/docs/scarpet/api/Events.md @@ -27,11 +27,32 @@ Here is a list of events that are handled by default in scarpet. This list inclu to autoload them, but you can always add any function to any event (using `/script event` command) if it accepts required number of parameters. +## 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. + +### `__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. + +### `__on_close()` + +Called once per app when the app is closing or reloading, right before the app is removed. +For player scoped apps, its called once per player. Scarpet app engine will attempt to call `'__on_close()'` even if +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. + ### `__on_server_starts()` Event triggers after world is loaded and after all startup apps have started. It won't be triggered with `/reload`. diff --git a/docs/scarpet/api/Overview.md b/docs/scarpet/api/Overview.md index 7ec68245..568c2b75 100644 --- a/docs/scarpet/api/Overview.md +++ b/docs/scarpet/api/Overview.md @@ -13,6 +13,11 @@ When loaded (via `/script load` command, etc.), the game will run the content of `__config()` function, if present, which will be executed once. Loading the app will also bind specific events to the event system (check Events section for details). +If an app defines `__on_start()` function, it will be executed once before running anything else. For global scoped apps, +this is just after they are loaded, and for player scoped apps, before they are used first time by a player. +Unlike static code (written directly in the body of the app code), that always run once per app, this may run multiple times if +its a player app nd multiple players are on the server. + Unloading an app removes all of its state from the game, disables commands, removes bounded events, and saves its global state. If more cleanup is needed, one can define `__on_close()` function which will be executed when the module is unloaded, or server is closing or crashing. However, there is no need to do that diff --git a/docs/scarpet/resources/editors/idea/4.txt b/docs/scarpet/resources/editors/idea/4.txt index 0238ef4b..a921bb46 100644 --- a/docs/scarpet/resources/editors/idea/4.txt +++ b/docs/scarpet/resources/editors/idea/4.txt @@ -1,7 +1,7 @@ __command() -> __config() -> -__on_chunk_generated(x, z) -> -__on_close() -> +__on_chunk_generated(x, z) -> +__on_close() -> __on_player_attacks_entity(player, entity) -> __on_player_breaks_block(player, block) -> __on_player_clicks_block(player, block, face) -> @@ -25,14 +25,15 @@ __on_player_starts_sprinting(player) -> __on_player_stops_sneaking(player) -> __on_player_stops_sprinting(player) -> __on_player_swings_hand(player, hand) -> -__on_player_switches_slot(player, from, to) -> +__on_player_switches_slot(player, from, to) -> __on_player_swaps_hands(player) -> __on_player_takes_damage(player, amount, source, entity) -> __on_player_trades(player, entity, buy_left, buy_right, sell) -> __on_player_uses_item(player, item_tuple, hand) -> __on_player_wakes_up(player) -> __on_player_escapes_sleep(player) -> -__on_statistic(player, category, event, value) -> +__on_statistic(player, category, event, value) -> +__on_start() -> __on_tick() -> __on_tick_ender() -> __on_tick_nether() -> diff --git a/docs/scarpet/resources/editors/npp/scarpet.xml b/docs/scarpet/resources/editors/npp/scarpet.xml index eeb7ca64..b7735dc6 100644 --- a/docs/scarpet/resources/editors/npp/scarpet.xml +++ b/docs/scarpet/resources/editors/npp/scarpet.xml @@ -129,6 +129,7 @@ __on_player_wakes_up __on_player_escapes_sleep __on_statistic + __on_start __on_tick __on_tick_ender __on_tick_nether diff --git a/src/main/java/carpet/script/CarpetScriptHost.java b/src/main/java/carpet/script/CarpetScriptHost.java index 60070cdc..c79477c0 100644 --- a/src/main/java/carpet/script/CarpetScriptHost.java +++ b/src/main/java/carpet/script/CarpetScriptHost.java @@ -88,7 +88,7 @@ public class CarpetScriptHost extends ScriptHost public static CarpetScriptHost create(CarpetScriptServer scriptServer, Module module, boolean perPlayer, ServerCommandSource source, Function commandValidator, boolean isRuleApp) { - CarpetScriptHost host = new CarpetScriptHost(scriptServer, module, perPlayer, null, Collections.emptyMap(), new HashMap<>(), commandValidator, isRuleApp); + CarpetScriptHost host = new CarpetScriptHost(scriptServer, module, perPlayer, null, Collections.emptyMap(), new HashMap<>(), commandValidator, isRuleApp); // parse code and convert to expression if (module != null) { @@ -227,11 +227,14 @@ public class CarpetScriptHost extends ScriptHost } @Override - protected void transferToChild(ScriptHost host) + protected void setupUserHost(ScriptHost host) { - super.transferToChild(host); + super.setupUserHost(host); // transfer Events - CarpetEventServer.Event.transferAllHostEventsToChild((CarpetScriptHost) host); + CarpetScriptHost child = (CarpetScriptHost) host; + CarpetEventServer.Event.transferAllHostEventsToChild(child); + FunctionValue onStart = child.getFunction("__on_start"); + if (onStart != null) child.callNow(onStart, Collections.emptyList()); } @Override @@ -261,7 +264,9 @@ public class CarpetScriptHost extends ScriptHost { try { - Value ret = callUDF(BlockPos.ORIGIN, scriptServer.server.getCommandSource(), getFunction("__config"), Collections.emptyList()); + FunctionValue configFunction = getFunction("__config"); + if (configFunction == null) return false; + Value ret = callNow(configFunction, Collections.emptyList()); if (!(ret instanceof MapValue)) return false; Map config = ((MapValue) ret).getMap(); setPerPlayer(config.getOrDefault(new StringValue("scope"), new StringValue("player")).getString().equalsIgnoreCase("player")); @@ -283,7 +288,7 @@ public class CarpetScriptHost extends ScriptHost } appConfig = config; } - catch (NullPointerException | InvalidCallbackException ignored) + catch (NullPointerException ignored) { return false; } @@ -631,23 +636,31 @@ public class CarpetScriptHost extends ScriptHost return Value.NULL; } + public Value callNow(FunctionValue fun, List arguments) + { + ServerPlayerEntity player = (user==null)?null:scriptServer.server.getPlayerManager().getPlayer(user); + ServerCommandSource source = (player != null)?player.getCommandSource():scriptServer.server.getCommandSource(); + try + { + return callUDF(BlockPos.ORIGIN, source, fun, arguments); + } + catch (InvalidCallbackException ignored) + { + return Value.NULL; + } + } + @Override public void onClose() { super.onClose(); FunctionValue closing = getFunction("__on_close"); - if (closing != null) + if (closing != null && (parent != null || !isPerUser())) + // either global instance of a global task, or + // user host in player scoped app { - ServerPlayerEntity player = (user==null)?null:scriptServer.server.getPlayerManager().getPlayer(user); - ServerCommandSource source = (player != null)?player.getCommandSource():scriptServer.server.getCommandSource(); - try - { - callUDF(BlockPos.ORIGIN, source, closing, Collections.emptyList()); - } - catch (InvalidCallbackException ignored) - { - } + callNow(closing, Collections.emptyList()); } if (user == null) { diff --git a/src/main/java/carpet/script/CarpetScriptServer.java b/src/main/java/carpet/script/CarpetScriptServer.java index f6f47d2d..8634ca00 100644 --- a/src/main/java/carpet/script/CarpetScriptServer.java +++ b/src/main/java/carpet/script/CarpetScriptServer.java @@ -29,6 +29,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -300,6 +301,12 @@ public class CarpetScriptServer // that will provide player hosts right at the startup newHost.retrieveForExecution(source, null); } + else + { + // global app - calling start now. + FunctionValue onStart = newHost.getFunction("__on_start"); + if (onStart != null) newHost.callNow(onStart, Collections.emptyList()); + } long end = System.nanoTime(); CarpetSettings.LOG.info("App "+name+" loaded in "+(end-start)/1000000+" ms"); return true; diff --git a/src/main/java/carpet/script/ScriptHost.java b/src/main/java/carpet/script/ScriptHost.java index a59f62aa..1cae3c97 100644 --- a/src/main/java/carpet/script/ScriptHost.java +++ b/src/main/java/carpet/script/ScriptHost.java @@ -322,12 +322,12 @@ public abstract class ScriptHost if (oldUserHost != null) return oldUserHost; ScriptHost userHost = this.duplicate(); userHost.user = user; - this.transferToChild(userHost); + this.setupUserHost(userHost); userHosts.put(user, userHost); return userHost; } - protected void transferToChild(ScriptHost host) + protected void setupUserHost(ScriptHost host) { // adding imports host.modules.putAll(this.modules); diff --git a/src/main/resources/assets/carpet/scripts/event_test.sc b/src/main/resources/assets/carpet/scripts/event_test.sc index a88a41fb..35fca5de 100644 --- a/src/main/resources/assets/carpet/scripts/event_test.sc +++ b/src/main/resources/assets/carpet/scripts/event_test.sc @@ -312,3 +312,19 @@ __on_player_swings_hand(player, hand) -> print(' - player: '+player); print(' - hand: '+hand); ); + +__on_start() -> +( + print(''); + print('__on_start(player)'); + print('App is starting for '+player()); + logger('starting for '+player()); +); + +__on_close() -> +( + print(''); + print('__on_close(player)'); + print('App is closing for '+player()); + logger('App is closing for '+player()); +);