Skip to main content

Subblocks

Easel has a unique feature called Subblocks which allows a function to be called with a subblock of code as a parameter. This gives Easel its unique hierarchical style, which allows game logic to be expressed in a more natural way with less indirection than other programming languages.

The subblock is passed into the function as a callback, assigned to a special variable named delve. The function can then call the delve variable at the appropriate time to execute the callback.

Function signature

Callback parameters

Add two pipe characters || to the function signature to declare a delve parameter which takes no callback parameters.

fn FunctionWithSubblock() || {
delve() // call the subblock
}
fn Example() {
FunctionWithSubblock {
// When `delve(...)` is called above, it goes into this callback section here
}
}

In the function signature, you can specify a comma-separated list of callback parameters inside the pipe characters (|). When calling the function, separate each parameter with a space.

fn FunctionWithSubblock() |x, y| { // comma-separated when declaring
delve(123, 456) // call the subblock with x=123, y=456
}
fn Example() {
FunctionWithSubblock x y { // space-separated when calling
// receives x=123, y=456
}
}

Context callback parameters

If a callback parameter is prefixed by the use keyword, then it will become a context variables in the subblock, even if they are not declared in the subblock.

fn ArrangeRing(numSteps, orbit) |index, use posOffset| {
for i in Range(0,numSteps) {
delve(
index=i,
posOffset=orbit * Direction(1rev * i / numSteps),
)
}
}
fn PlaceObstacle(pos, size, [posOffset]) {
SpawnObstacleAt(pos + posOffset)
}
fn Example() {
ArrangeRing(numSteps=10, orbit=5) index {
// Inside this block, posOffset is an undeclared context variable.

PlaceObstacle(
pos=@(2,3),
size = index < 3 ? 3 : 10, // make first 3 smaller
// the posOffset parameter is passed in implicitly from context
)
}
}

Subject callback parameter

If a callback parameter is prefixed with use this = , then it always becomes a variable named this in the callback, in addition to any other name it may be declared with.

fn this.DrawMe() { }
fn SpawnProjectile() |use this = projectile| {
Spawn projectile {
delve(projectile)
}
}
fn Example() {
SpawnProjectile fireball {
DrawMe // the subject parameter `this` is assigned from context
}
}

Calling the subblock

The subblock may be called zero or more times within the function by calling the delve parameter like any other function. If delve is null or undefined, then calling it will have no effect, but will also not error. If delve is not called with the correct number of parameters, an error will be generated at compile time.

Any parameters for the delve call declared with use will be filled from context if they are not explicitly provided.

fn FunctionWithSubblock() |use name, use age| {
use name="Augustus", age=25
delve() // name and age are passed in from context
}

Receiving callback parameters

When calling a function, the arguments to the subblock may be declared in a different order than they appear in the function signature. Arguments are first matched by name where possible, then by position. It is possible to skip the next positional parameter by using _ as a placeholder. It is also possible to declare a parameter with a different name using newName=oldName syntax.

pub fn Spray() |index, layer, color, radius| {
// ...
}

pub fn Example() {
Spray i _ radius c=color {
// The first callback argument 'i' is bound to the parameter 'index'
// because there is no parameter named 'i' and so the compiler falls back to matching by position.
// The parameter 'layer' is ignored because it is skipped using '_'.
// The 'radius' parameter is found by name, and so it does not matter that its position is incorrect here.
// The 'color' parameter is renamed to 'c' and found by name.
}
}