Files
fabric-carpet/docs/scarpet/language/SystemFunctions.md
T
2020-03-18 02:00:17 -03:00

14 KiB

System functions

Type conversion functions

copy(expr)

Returns the deep copy of the expression. Can be used to copy mutable objects, like maps and lists

type(expr)

Returns the string value indicating type of the expression. Possible outcomes are null, number, string, list, iterator, as well as minecraft related concepts like block, entity, nbt

bool(expr)

Returns a boolean context of the expression. Note that there are no true/false values in scarpet. true is alias of 1, and false is 0. Bool is also interpreting string values as boolean, which is different from other places where boolean context can be used. This can be used in places where API functions return string values to represent binary values

bool(pi) => 1
bool(false) => 0
bool('') => 0
bool(l()) => 0
bool(l('')) => 1
bool('foo') => 1
bool('false') => 0
bool('nulL') => 0
if('false',1,0) => 1

number(expr)

Returns a numeric context of the expression. Can be used to read numbers from strings

number(null) => null
number(false) => 0
number('') => null
number('3.14') => 3.14
number(l()) => 0
number(l('')) => 1
number('foo') => null
number('3bar') => null
number('2')+number('2') => 4

str(expr, params? ... ), str(expr, param_list)

Returns a formatted string representing expression. Accepts formatting style accepted by String.format. Supported types (with "%?" syntax):

  • d, o, x: integers, octal, hex
  • a, e, f, g: floats
  • b: booleans
  • s: strings
str(null) => null
str(false) => 0
str('') => null
str('3.14') => 3.14
str(l()) => 0
str(l('')) => 1
str('foo') => null
str('3bar') => null
str(2)+str(2) => 22
str('pi: %.2f',pi) => 'pi: 3.14'
str('player at: %d %d %d',pos(player())) => 'player at: 567, -2423, 124'

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.

synchronize(lock, expression)

Evaluates expression synchronized with respect to the lock lock. Returns the value of the expression.


Auxiliary functions

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

Returns lowercase, uppercase or titlecase representation of a string representation of the passed expression

lower('aBc') => 'abc'
upper('aBc') => 'ABC'
title('aBc') => 'Abc'

replace(string, regex, repl?); replace_first(string, regex, repl?)

Replaces all, or first occurence of a regular expression in the string with repl expression, or nothing, if not specified

replace('abbccddebfg','b+','z')  // => azccddezfg
replace_first('abbccddebfg','b+','z')  // => azccddebfg

length(expr)

Returns length of the expression, the length of the string, the length of the integer part of the number, or length of the list

length(pi) => 1
length(pi*pi) => 1
length(pi^pi) => 2
length(l()) => 0
length(l(1,2,3)) => 3
length('') => 0
length('foo') => 3

rand(expr), rand(expr, seed)

returns a random number from 0.0 (inclusive) to expr (exclusive). In boolean context (in conditions, boolean functions, or bool), returns false if the randomly selected value is less than 1. This means that rand(2) returns true half of the time and rand(5) returns true for 80% (4/5) of the time. If seed is not provided, uses a random seed. If seed is provided, each consecutive call to rand() will act like 'next' call to the same random object. Scarpet keeps track of up to 1024 custom random number generators, so if you exceed this number (per app), then your random sequence will revert to the beginning.

map(range(10), floor(rand(10))) => [5, 8, 0, 6, 9, 3, 9, 9, 1, 8]
map(range(10), bool(rand(2))) => [1, 1, 1, 0, 0, 1, 1, 0, 0, 0]
map(range(10), str('%.1f',rand(_))) => [0.0, 0.4, 0.6, 1.9, 2.8, 3.8, 5.3, 2.2, 1.6, 5.6]

perlin(x), perlin(x, y), perlin(x, y, z), perlin(x, y, z, seed)

returns a noise value from 0.0 to 1.0 (roughly) for 1, 2 or 3 dimensional coordinate. The default seed it samples from is 0, but seed can be specified as a 4th argument as well. In case you need 1D or 2D noise values with custom seed, use null for z, or y and z arguments respectively.

Perlin noise is based on a square grid and generates rougher maps comparing to Simplex, which is creamier. Querying for lower-dimensional result, rather than affixing unused dimensions to constants has a speed benefit,

Thou shall not sample from noise changing seed frequently. Scarpet will keep track of the last 256 perlin seeds used for sampling providing similar speed comparing to the default seed of 0. In case the app engine uses more than 256 seeds at the same time, switching between them can get much more expensive.

simplex(x, y), simplex(x, y, z), simplex(x, y, z, seed)

returns a noise value from 0.0 to 1.0 (roughly) for 2 or 3 dimensional coordinate. The default seed it samples from is 0, but seed can be specified as a 4th argument as well. In case you need 2D noise values with custom seed, use null for z argument.

Simplex noise is based on a triangular grid and generates smoother maps comparing to Perlin. To sample 1D simplex noise, affix other coordinate to a constant.

Thou shall not sample from noise changing seed frequently. Scarpet will keep track of the last 256 simplex seeds used for sampling providing similar speed comparing to the default seed of 0. In case the app engine uses more than 256 seeds at the same time, switching between them can get much more expensive.

print(expr)

prints the value of the expression to chat. Passes the result of the argument to the output unchanged, so print-statements can be weaved in code to debug programming issues

print('foo') => results in foo, prints: foo
a = 1; print(a = 5) => results in 5, prints: 5
a = 1; print(a) = 5 => results in 5, prints: 1
print('pi = '+pi) => prints: pi = 3.141592653589793
print(str('pi = %.2f',pi)) => prints: pi = 3.14

sleep(expr)

Halts the execution of the program (and the game itself) for expr milliseconds. All in all, its better to use game_tick(expr) to let the game do its job while the program waits

sleep(50)

time()

Returns the number of milliseconds since 'some point', like Java's System.nanoTime(). It returns a float, which has 1 microsecond precision (0.001 ms)

start_time = time();
flip_my_world_upside_down();
print(str('this took %d milliseconds',time()-start_time))

profile_expr(expression)

Returns number of times given expression can be run in 50ms time. Useful to profile and optimize your code. Note that, even if its only a number, it WILL run these commands, so if they are destructive, you need to be careful.


Access to variables and stored functions (use with caution)

var(expr)

Returns the variable under the name of the string value of the expression. Allows to manipulate variables in more programmatic manner, which allows to use local variable set with a hash map type key-value access, can also be used with global variables

a = 1; var('a') = 'foo'; a => a == 'foo'

undef(expr)

Removes all bindings of a variable with a name of expr. Removes also all function definitions with that name. It can affect global variable pool, and local variable set for a particular function.

inc(i) -> i+1; foo = 5; inc(foo) => 6
inc(i) -> i+1; foo = 5; undef('foo'); inc(foo) => 1
inc(i) -> i+1; foo = 5; undef('inc'); undef('foo'); inc(foo) => Error: Function inc is not defined yet at pos 53
undef('pi')  => bad idea - removes hidden variable holding the pi value
undef('true')  => even worse idea, unbinds global true value, all references to true would now refer to the default 0

vars(prefix)

It returns all names of variables from local scope (if prefix does not start with 'global') or global variables (otherwise). Here is a larger example that uses combination of vars and var functions to be used for object counting

/script run
$ count_blocks(ent) -> (
$   l(cx, cy, cz) = query(ent, 'pos');
$   scan(cx, cy, cz, 16, 16, 16, var('count_'+_) += 1);
$   for ( sort_key( vars('count_'), -var(_)),
$     print(str( '%s: %d', slice(_,6), var(_) ))
$   )
$ )
/script run count_blocks(player())

System key-value storage

Scarpet runs apps in isolation. The can share code via use of shared libraries, but each library that is imported to each app is specific to that app. Apps can store and fetch state from disk, but its restricted to specific locations meaning apps cannot interact via disk either. To facilitate communication for interappperability, scarpet hosts its own key-value storage that is shared between all apps currently running on the host, providing methods for getting an associated value with optional setting it if not present, and an operation of modifying a content of a system global value.

system_variable_get(key, default_value ?)

Returns the variable from the system shared key-value storage keyed with a key value, optionally if value is not present, and default expression is provided, sets a new value to be associated with that key

system_variable_set(key, new_value)

Returns the variable from the system shared key-value storage keyed with a key value, and sets a new mapping for the key