diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java index 49467223..2ea87de9 100644 --- a/src/main/java/carpet/script/CarpetExpression.java +++ b/src/main/java/carpet/script/CarpetExpression.java @@ -1745,6 +1745,9 @@ public class CarpetExpression *

String with gamemode, or null if not a player.

*

query(e,'gamemode_id')

*

Good'ol gamemode id, or null if not a player.

+ *

query(e,'permission_level')

+ *

Player's permission level, or null if not applicable for this entity.

+ * *

query(e,'effect',name?)

*

Without extra arguments, it returns list of effect active on a living entity. * Each entry is a triple of short effect name, amplifier, and remaining duration. @@ -1836,6 +1839,12 @@ public class CarpetExpression *

Make noises.

*

modify(e, 'ai', boolean)

*

If called with false value, it will disable AI in the mob. true will enable it again.

+ *

modify(e, 'no_clip', boolean)

+ *

Sets if the entity obeys any collisions, including collisions with the terrain and basic physics. + * Not affecting players, since they are controlled client side

+ *

modify(e, 'effect', name, duration, amplifier?, show_particles?, show_icon?)

+ *

Applies status effect to the living entity. Takes several optional parameters, which default to 0, + * true and true.

*

modify(e, 'home', null), modify(e, 'home', block, distance?), modify(e, 'home', x, y, z, distance?)

*

Sets AI to stay around the home position, within distance blocks from it. distance * defaults to 16 blocks. null removes it. May not work fully with mobs that have this AI built in, like @@ -2640,7 +2649,7 @@ public class CarpetExpression *

load_app_data(), load_app_data(file)

*

Loads the app data associated with the app from the world /scripts folder. Without argument returns the memory * managed and buffered / throttled NBT tag. With a file name - reads explicitly a file with that name from the scripts - * floder.

+ * folder.

*

You can use app data to save non-vanilla information separately from the world and other scripts.

*

store_app_data(tag), store_app_data(tag, file)

*

Stores the app data associated with the app from the world /scripts folder. With the file parameter @@ -2665,6 +2674,25 @@ public class CarpetExpression *

schedule(delay, function, args...)

*

Schedules a user defined function to run with a specified delay ticks of delay. * Scheduled functions run at the end of the tick, and they will run in order they were scheduled.

+ *

statistic(player, category, entry)

+ *

Queries in-game statistics for certain values. Categories include:

+ * + *

For the options of entry, consult your statistics page, or give it a guess.

+ *

The call will return null if the statistics options are incorrect, or player didn't get these + * in their history. If the player encountered the statisitic, or game created for him empty one, it will + * return a number. Scarpet will not affect the entries of the statistics, even if it is just creating empty ones. + * With null response it could either mean your input is wrone, or statistic has effectively + * a value of 0.

*

plop(pos, what)

*

Plops a structure or a feature at a given pos, so block, triple position coordinates * or a list of coordinates. To what gets plopped and exactly where it often depends on the @@ -3010,12 +3038,12 @@ public class CarpetExpression }); //"overidden" native call to cancel if on main thread - this.expr.addLazyFunction("join_task", 1, (c, t, lv) -> { + this.expr.addLazyFunction("task_join", 1, (c, t, lv) -> { if (((CarpetContext)c).s.getMinecraftServer().isOnThread()) - throw new InternalExpressionException("'join_task' cannot be called from main thread to avoid deadlocks"); + throw new InternalExpressionException("'task_join' cannot be called from main thread to avoid deadlocks"); Value v = lv.get(0).evalValue(c); if (!(v instanceof ThreadValue)) - throw new InternalExpressionException("'join_task' could only be used with a task value"); + throw new InternalExpressionException("'task_join' could only be used with a task value"); Value ret = ((ThreadValue) v).join(); return (_c, _t) -> ret; }); @@ -3483,7 +3511,11 @@ public class CarpetExpression * should be running at startup. WARNING: all apps will run once at startup anyways, so be aware that their actions * that are called statically, will be performed once anyways. * - *

Unloading an app removes all of its state from the game, disables commands, and removes bounded events

+ *

Unloading an app removes all of its state from the game, disables commands, and removes bounded events, + * and saves its global state. If more + * cleanup is needed, one can define an __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 explicitly for the things mentioned + * in the previous statement.

*

Scripts can be loaded in shared(global) and player mode. Default is player, so all globals and stored functions are * individual for each player, meaning scripts don't need to worry about making sure they store some intermittent data * for each player independently. In global mode - all global values and stored functions are shared among all players. @@ -3523,8 +3555,10 @@ public class CarpetExpression * __on_player_releases_item(player, item_tuple, hand) // client action (e.g. bow) * __on_player_finishes_using_item(player, item_tuple, hand)) // server action (e.g. food), called item is from before it is used. * __on_player_clicks_block(player, block, face) // left click attack on a block - * __on_player_right_clicks_block(player, item_tuple, hand, block, face, hitvec) * __on_player_breaks_block(player, block) // called after block is broken (the caller receives previous blockstate) + * __on_player_right_clicks_block(player, item_tuple, hand, block, face, hitvec) // player right clicks block with anything + * __on_player_interacts_with_block(player, hand, block, face, hitvec) //right click on a block resulted in activation of said block + * __on_player_places_block(player, item_tuple, hand, block) // player have just placed the block. * __on_player_interacts_with_entity(player, entity, hand) * __on_player_attacks_entity(player, entity) * __on_player_rides(player, forward, strafe, jumping, sneaking) @@ -3537,6 +3571,8 @@ public class CarpetExpression * __on_player_stops_sprinting(player) * __on_player_drops_item(player) * __on_player_drops_stack(player) + * + * __on_statistic(player, category, event, value) // player statistic changes * *

/script event command

*

used to display current events and bounded functions. use add_to ro register new event, or remove_from diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java index 0bc27604..1aecd2a9 100644 --- a/src/main/java/carpet/script/Expression.java +++ b/src/main/java/carpet/script/Expression.java @@ -60,7 +60,7 @@ import static java.lang.Math.min; /** - *

Fundamental components of scarpet programming language (version 1.6).

+ *

Fundamental components of scarpet programming language (towards version 1.7).

* *

Scarpet (a.k.a. Carpet Script, or Script for Carpet) is a programming language designed to provide * the ability to write custom programs to run within Minecraft and @@ -2613,6 +2613,59 @@ public class Expression implements Cloneable * * *


+ *

Threading and Parallel Execution

+ *

Scarpet allows to run threads of execution in parallel to the main script execution thread. In Minecraft, the main + * thread scripts are executed on the main server thread. Since Minecraft is inherently NOT thread safe, it is not that + * beneficial to parallel execution in order to access world resources faster. Both + * GetBlockState and setBlockState are not thread safe and require the execution to park on the server thread, + * where these requests can be executed in the off-tick time in between ticks that didn't take 50ms. There are however + * benefits of running things in parallel, like fine time control not relying on the tick clock, or running things independent + * on each other. You can still run your actions on tick-by-tick basis, either taking control of the execution using + * game_tick() API function (nasty solution), or scheduling tick using schedule() function (much nicer solution), + * but threading often gives the neatest solution to solve problems in parallel (see scarpet camera). + *

+ *

Due to limitations with the game, there are some limits to the threading as well. You cannot for instance join_task() at all + * from the main script and server thread, because any use of Minecraft specific function that require any world access, will + * require to park and join on the main thread to get world access, meaning that calling join on that task would inevitably lead to a + * typical deadlock. You can still join tasks from other threads, just because the only possibility of a deadlock in this case would + * come explicitly from your bad code, not the internal world access behaviour. Some things tough like player or entity manipulation, + * can be effectively parallelized.

+ *

task(function, ?args...., ?executor)

+ *

Creates and runs a parallel task, returning the handle to the task object. Task will return the return value of the function + * when its completed, or will return null immediately if task is still in progress, so grabbing a value of + * a task object is non-blocking. Function can be either function value, or function lambda, or a name of an existing defined function. + * In case function needs arguments to be called with, they should be supplied after the function name, or value. + * Optional executor identifier is to place the task in a specific queue identified by this value. The default + * thread value is the null thread. There is no limits on number of parallel tasks for any executor, so using different queues is solely + * for synchronization purposes. Also, since task function knows how many extra arguments it requires, the use of + * tailing optional parameter for the custom executor thread pool is not ambiguous. + *

+ *
+     * task( _() -> print('Hello Other World') )  => Runs print command on a separate thread
+     * foo(a, b) -> print(a+b); task('foo',2,2)  => Uses existing function definition to start a task
+     * task('foo',3,5,'temp');  => runs function foo with a different thread executor, identified as 'temp'
+     * a = 3; task( _(outer(a), b) -> foo(a,b), 5, 'temp')  => Another example of running the same thing passing arguments using closure over anonymous function as well as passing a parameter.
+     * 
+ *

task_count(executor?)

+ *

If no argument provided, returns total number of tasks being executed in parallel at this moment using scarpet threading system. + * If the executor is provided, returns number of active tasks for that provider. Use task_count(null) to + * get the task count of the default executor only.

+ * + *

task_value(task)

+ *

Returns the task return value, or null if task hasn't finished yet. Its a non-blocking operation. + * Unlike join_task, can be called on any task at any point

+ * + *

task_join(task)

+ *

Waits for the task completion and returns its computed value. If the task has already finished returns it immediately. + * Unless taking the task value directly, i.e. via task_value, this operation is blocking. Since Minecraft has + * a limitation that all world access operations have to be performed on the main game thread in the off-tick time, joining any tasks that use + * Minecraft API from the main thread would mean automatic lock, so joining from the main thread is not allowed. + * Join tasks from other threads, if you really need to, or communicate asynchronously with the task via globals or + * function data / arguments to monitor its progress, communicate, get partial results, or signal termination.

+ * + *

task_completed(task)

+ *

Returns true if task has completed, or false otherwise.

+ * *

Auxiliary functions

* *

lower(expr), upper(expr), title(expr)

@@ -3039,9 +3092,15 @@ public class Expression implements Cloneable return new NumericValue(ThreadValue.taskCount()); }); - addUnaryFunction("join_task", (v) -> { + addUnaryFunction("task_value", (v) -> { if (!(v instanceof ThreadValue)) - throw new InternalExpressionException("'join_task' could only be used with a task value"); + throw new InternalExpressionException("'task_value' could only be used with a task value"); + return ((ThreadValue) v).getValue(); + }); + + addUnaryFunction("task_join", (v) -> { + if (!(v instanceof ThreadValue)) + throw new InternalExpressionException("'task_join' could only be used with a task value"); return ((ThreadValue) v).join(); });