Skip to content

Sync Emit

Overview

Sync emit is a special form of emit that executes an event immediately and synchronously, bypassing the event queue, and assigns the result to a field.

emit Entity.event(args) -> field
This enables user-defined pure functions — events that compute and return a value.


Syntax

emit EventName(args) -> field
emit Entity.event(args) -> field
The result written to field is the last assigned value inside the called event.


Example

entity MathLib {
  event clamp(value, min, max) {
    check value < min: min -> value
    check value > max: max -> value
    value -> result
  }
  event abs(value) {
    check value < 0: value * -1 -> value
    value -> result
  }
  event lerp(a, b, t) {
    a + (b - a) * t -> result
  }
}
entity Player {
  has hp: 150
  has max_hp: 100
  has x: 0.0
  event heal(amount) {
    emit MathLib.clamp(hp + amount, 0, max_hp) -> hp
  }
  event tick(dt) {
    emit MathLib.lerp(x, 10, 0.1) -> x
  }
}

Rules

Sync calls can be chained

emit X() -> field can be used inside another sync event, as long as the target is a different event. Chains like A -> B -> C are fully supported:

entity Pipeline {
  event process(value) {
    emit MathLib.abs(value) -> value            # ok — calls MathLib.abs
    emit MathLib.clamp(value, 0, 100) -> value  # ok — calls MathLib.clamp
    value -> result
  }
}

entity MathLib {
  event abs(value) {
    check value < 0: value * -1 -> value
    value -> result
  }
  event clamp(value, min, max) {
    check value < min: min -> value
    check value > max: max -> value
    value -> result
  }
}

Recursion and Loops

Sync calls now support deep recursion. This allows you to implement while and for logic by having an event call itself or other events synchronously.

Since the runtime uses a managed heap-based stack instead of the system call stack, these calls are safe and won't crash the application even at high iteration counts.

entity Loop {
  has counter: 0

  # Recursive loop implementation
  event repeat(times) {
    check counter < times: {
      counter + 1 -> counter
      emit self.repeat(times) -> result  # OK: recursive call
    }
    counter -> result
  }
}

Safety Limit

To prevent accidental infinite loops from hanging the engine, a safety threshold is enforced:

  • Max Depth: 100,000,000 nested calls.

  • Exceeding this limit throws a [Sync Error] but keeps the engine running.

Regular emit inside sync events

Regular emit is allowed inside sync events. The events are held in a separate side-effect queue and flushed to the main queue after the outermost sync call completes. This means side effects happen after the result is returned, not during.

entity MathLib {
  event clamp(value, min, max) {
    check value < min: min -> value
    check value > max: max -> value
    value -> result
    emit log("clamped:", result)  # queued, fires after sync call returns
  }
}

Return value

The return value is the last value assigned inside the event body, regardless of which branch executed.

event clamp(value, min, max) {
  check value < min: min -> value
  check value > max: max -> value
  value -> result             # this is the return value
}


Comparison with regular emit

emit X() emit X() -> field
Execution Queued, deferred Immediate, synchronous
Return value None Last assigned value
emit X() -> field allowed inside Yes Yes, unless recursive
Regular emit allowed inside Yes Yes (deferred to after sync)
Recursion Yes (indirect) No (runtime error)
Chaining to other events Yes
Use case Events, side effects Pure computation, pipelines

Use Cases

Math utilities

entity MathLib {
  event clamp(value, min, max) {
    check value < min: min -> value
    check value > max: max -> value
    value -> result
  }
  event abs(value) {
    check value < 0: value * -1 -> value
    value -> result
  }
  event lerp(a, b, t) {
    a + (b - a) * t -> result
  }
  event normalize(value, min, max) {
    (value - min) / (max - min) -> result
  }
}

Chained computation

entity StatLib {
  event raw_to_final(damage, armor, crit) {
    emit StatLib.apply_armor(damage, armor) -> damage
    emit StatLib.apply_crit(damage, crit) -> damage
    damage -> result
  }
  event apply_armor(damage, armor) {
    damage * (1 - armor / 100) -> result
  }
  event apply_crit(damage, crit) {
    check crit: damage * 2 -> damage
    damage -> result
  }
}
entity Player {
  has hp: 100
  has armor: 30
  event take_damage(raw, crit) {
    emit StatLib.raw_to_final(raw, armor, crit) -> final
    hp - final -> hp
    check hp <= 0: emit die
  }
}

Stat computation

entity StatLib {
  event apply_armor(damage, armor) {
    damage * (1 - armor / 100) -> result
  }
  event exp_to_next(level) {
    level * level * 50 -> result
  }
}
entity Player {
  has hp: 100
  has armor: 30
  has level: 5
  has exp: 0
  event take_damage(amount) {
    emit StatLib.apply_armor(amount, armor) -> amount
    hp - amount -> hp
    check hp <= 0: emit die
  }
  event add_exp(amount) {
    exp + amount -> exp
    emit StatLib.exp_to_next(level) -> needed
    check exp >= needed: emit level_up
  }
}

Shared library via import

mathlib.morph:

entity MathLib {
  event clamp(value, min, max) {
    check value < min: min -> value
    check value > max: max -> value
    value -> result
  }
}
game.morph:
import "mathlib.morph"
entity Player {
  has hp: 100
  has max_hp: 100
  event heal(amount) {
    emit MathLib.clamp(hp + amount, 0, max_hp) -> hp
  }
}