ReactiveSk enables object-oriented and lightweight reactive programming for Skript.
Skript 2.6.3 ~ 2.12.2Paper 1.12.2 ~ 1.21.8
- ReactiveSk
- Class Definition
- Class Function Definition
- Calling Class Functions
- Types and Arrays
- Variable Declaration (Typed Variables)
- Observation - Observer
- Grammar Summary
- Example: Full Class Sample
- Define a class using
class Name[constructorParameters...]:. - The class body can contain
field:,init:, andfunction ...:sections.
class Test2[test2: array of string, val test: string]:
field:
private var test3: string
init:
resolve test3 := "resolved!"
function name(test: string) :: string:
return "aaa"
class Counter[val count: integer]:
function increment(): Counter:
send "%[this].count%" to console
if count of [this] is 12:
send "it is 12!" to console
return new Counter([this] -> count + 1)
- Constructor parameters are placed inside square brackets
[...]. - Parameters marked with
val/var/factorbecome fields automatically and are assigned during instance creation.- Example:
val test: string→testis a read-only property - Example:
var test: string→testis a mutable property
- Example:
- Parameters without
val/vardo not become properties, but they can be accessed inside theinitsection using the parameter name (e.g.[test2]).
- Declare additional fields in the
fieldsection. - You can specify access modifiers and mutability.
private var test3: string
Fields declared in field must be initialized (resolved) in the init section using resolve, otherwise an error occurs.
resolve test3 := "resolved!"
Fields that must be resolved cannot be accessed directly using [name] before they are resolved. If you need to access them, use [this] -> name.
There are two kinds of modifiers: access modifiers and declaration modifiers.
private: Not accessible from outside the class
val: Read-only propertyvar: Mutable propertyfactor: Read-only property that acts as a reactive factor (observable)
factor can be reassigned like var. When a variable marked with factor changes, it automatically emits notifications.
The init section is an initialization block executed after the constructor.
- Initialize all fields declared in
fieldwithresolve. - Use constructor parameters without
val/varinsideinitto perform initialization logic (e.g.[test2]).
The instance itself is available as [this] inside init.
init:
resolve test3 := "resolved!"
All fields must be resolved on every execution path.
Declare functions as function name(parameters...) :: returnType:. Use return <expression> to return values from the function body.
The return type is optional.
The instance is available as [this] inside functions.
function name(test: string) :: string:
return "aaa"
function name(test: string):
# code...
When a return type is declared, all code paths must guarantee execution of a return.
%object% -> functionName(%objects%) then functionName(%objects%)...
val count := [classInstance] -> count() then sendCount()
[classInstance] -> count() then sendCount()
These calls are non-suspending and return a value immediately. If the function contains constructs that lead to wait, the returned value may be null.
- Note:
- Explicit types should be provided for parameters (arrays are supported).
- Basic type example:
string - Array type:
array of <type>- Example:
array of string
- Example:
- Variables are declared with
val(immutable) orvar(mutable). - Syntax:
- With explicit type and initializer:
val | var name (type) := value,[declare] immutable | mutable name (type) := value - With type inference:
val | var name := value,[declare] immutable | mutable name := value
- With explicit type and initializer:
- Examples:
val title (string) := "test" # explicit type + initializer
val msg := "hello" # type inference + initializer
immutable title (string) := "test" # explicit type + initializer
immutable msg := "hello" # type inference + initializer
var count (integer) := 0 # mutable
set [count] to [count] + 1 # reassignment
mutable count := 0 # mutable
set [count] to [count] + 1 # reassignment
val count (integer) # declaration only
set [count] to 10 # assignment
You cannot declare val name without a type or initializer. Avoid unnecessarily widening the scope of variables declared without initialization.
Access variables using [name].
For custom types, access fields with [name].field, [name] -> field, or field of [name].
Arguments and properties defined in class functions and init are accessible using the methods above.
send "%[player].level%"
send "%[player]->level%"
send "%[player] -> level%"
send "%level of [player]%"
Fields marked with factor emit notifications when changed, implementing an observer pattern.
class Player[factor level: long, factor exp: long]:
function addExp(amount: long):
notify that set [this] -> exp to [this] -> exp + [amount]
if [this].exp >= 100:
notify that set [this].level to [this].level + 1
set exp of [this] to exp of [this] - 100
observe Player factor level:
if [instance] -> level >= 10:
send "Reached level 10! Congratulations!"
Start observing with observe <ClassName> factor <fieldName>:.
[instance]: the instance that changed[old]: previous value[new]: new value
If a single logic path changes a
factorfield twice, two notifications are emitted. Notifications run on the main thread, but their ordering is not guaranteed. All observers execute assuspend.
By default, changing a field (e.g. set [object].field to value) does not emit a notification.
Use notify that to emit a notification explicitly. For example:
notify that set [this].field to value
This prevents unnecessary notifications by default.
- Short BNF-like summary and usage:
class <name>[<constructor-params>]:
field:
init:
function <name>(<params...>) :: <returnType>:
set [<name>] to <expression>
set <field-access> to <expression>
notify that set <field-access> to <expression>
observe <class-name> factor <field-name>:
<statements...>
class Player[factor level: long, val name: string]:
field:
private var metadata: string
init:
resolve metadata := "default"
function addExp(amount: long):
notify that set [this] -> level to [this] -> level + [amount]
if [this] -> level >= 100:
notify that set [this] -> level to level of [this] - 100
observe Player factor level:
send "%[instance] -> name% leveled up to %[new]!"