Files
cell/docs/actors.md
2026-02-18 10:18:28 -06:00

8.6 KiB

title, description, weight, type
title description weight type
Actors and Modules The ƿit execution model 20 docs

ƿit organizes code into two types of scripts: modules (.cm) and actors (.ce).

The Actor Model

ƿit is built on the actor model of computation. Each actor:

  • Has its own isolated memory — actors never share state
  • Runs to completion each turn — no preemption
  • Performs its own garbage collection
  • Communicates only through message passing

This isolation makes concurrent programming safer and more predictable.

Modules (.cm)

A module is a script that returns a value. The returned value is cached and frozen (made stone).

// math_utils.cm
var math = use('math/radians')

var distance = function(x1, y1, x2, y2) {
  var dx = x2 - x1
  var dy = y2 - y1
  return math.sqrt(dx * dx + dy * dy)
}

var midpoint = function(x1, y1, x2, y2) {
  return {
    x: (x1 + x2) / 2,
    y: (y1 + y2) / 2
  }
}

return {
  distance: distance,
  midpoint: midpoint
}

Key properties:

  • Must return a value — it's an error not to
  • Executed once per actor — subsequent use() calls return the cached value
  • Return value is stone — immutable, safe to share
  • Modules can import other modules with use()

Using Modules

var utils = use('math_utils')
var d = utils.distance(0, 0, 3, 4)  // 5

Actors (.ce)

An actor is a script that does not return a value. It runs as an independent unit of execution.

// worker.ce
print("Worker started")

$receiver(function(msg) {
  print("Received:", msg)
  send(msg, {status: "ok"})
})

Key properties:

  • Must not return a value — it's an error to return
  • Has access to actor intrinsics (functions starting with $)
  • Runs until explicitly stopped or crashes

Actor Intrinsics

Actors have access to special functions prefixed with $:

$self

Reference to the current actor. This is a stone (immutable) actor object.

print($self)            // actor reference
print(is_actor($self))  // true

$overling

Reference to the parent actor that started this actor. null for the root actor. Child actors are automatically coupled to their overling — if the parent dies, the child dies too.

if ($overling != null) {
  send($overling, {status: "ready"})
}

$stop()

Stop the current actor. When called with an actor argument, stops that underling (child) instead.

$stop()          // stop self
$stop(child)     // stop a child actor

Important: $stop() does not halt execution immediately. Code after the call continues running in the current turn — it only prevents the actor from receiving future messages. Structure your code so that nothing runs after $stop(), or use return to exit the current function first.

// Wrong — code after $stop() still runs
if (done) $stop()
do_more_work()  // this still executes!

// Right — return after $stop()
if (done) { $stop(); return }
do_more_work()

$start(callback, program)

Start a new child actor from a script. The callback receives lifecycle events:

  • {type: "greet", actor: <ref>} — child started successfully
  • {type: "stop"} — child stopped cleanly
  • {type: "disrupt", reason: ...} — child crashed
$start(function(event) {
  if (event.type == 'greet') {
    print("Child started:", event.actor)
    send(event.actor, {task: "work"})
  }
  if (event.type == 'stop') {
    print("Child stopped")
  }
  if (event.type == 'disrupt') {
    print("Child crashed:", event.reason)
  }
}, "worker")

$delay(callback, seconds)

Schedule a callback after a delay. Returns a cancel function that can be called to prevent the callback from firing.

var cancel = $delay(function() {
  print("5 seconds later")
}, 5)

// To cancel before it fires:
cancel()

$clock(callback)

Get called every frame/tick. The callback receives the current time as a number.

$clock(function(t) {
  // called each tick with current time
})

$receiver(callback)

Set up a message receiver. The callback is called with the incoming message whenever another actor sends a message to this actor.

To reply to a message, call send(message, reply_data) — the message object contains routing information that directs the reply back to the sender.

$receiver(function(message) {
  // handle incoming message
  send(message, {status: "ok"})
})

$portal(callback, port)

Open a network port to receive connections from remote actors.

$portal(function(connection) {
  // handle new connection
}, 8080)

$contact(callback, record)

Connect to a remote actor at a given address.

$contact(function(connection) {
  // connected
}, {host: "example.com", port: 80})

$time_limit(requestor, seconds)

Wrap a requestor with a timeout. Returns a new requestor that will cancel the original and call its callback with a failure if the time limit is exceeded. See Requestors for details.

var timed = $time_limit(my_requestor, 10)

timed(function(result, reason) {
  // reason will explain timeout if it fires
}, initial_value)

$couple(actor)

Couple the current actor to another actor. When the coupled actor dies, the current actor also dies. Coupling is automatic between a child actor and its overling (parent).

$couple(other_actor)

$unneeded(callback, seconds)

Schedule the actor for removal after a specified time. The callback fires when the time elapses.

$unneeded(function() {
  // cleanup before removal
}, 30)

$connection(callback, actor, config)

Get information about the connection to another actor. For local actors, returns {type: "local"}. For remote actors, returns connection details including latency, bandwidth, and activity.

$connection(function(info) {
  if (info.type == "local") {
    print("same machine")
  } else {
    print(info.latency)
  }
}, other_actor, {})

Runtime Functions

These functions are available in actors without the $ prefix:

send(actor, message, callback)

Send a message to another actor. The message must be an object record.

The optional callback receives the reply when the recipient responds.

send(other_actor, {type: "ping"}, function(reply) {
  print("Got reply:", reply)
})

To reply to a received message, pass the message itself as the first argument — it contains routing information:

$receiver(function(message) {
  send(message, {result: 42})
})

Messages are automatically flattened to plain data.

is_actor(value)

Returns true if the value is an actor reference.

if (is_actor(some_value)) {
  send(some_value, {ping: true})
}

log

Channel-based logging. Any log.X(value) writes to channel "X". Three channels are conventional: log.console(msg), log.error(msg), log.system(msg) — but any name works.

Channels are routed to configurable sinks (console or file) defined in .cell/log.toml. See Logging for the full guide.

use(path)

Import a module. See Module Resolution below.

args

Array of command-line arguments passed to the actor.

sequence(), parallel(), race(), fallback()

Requestor composition functions. See Requestors for details.

Module Resolution

When you call use('name'), ƿit searches:

  1. Current package — files relative to package root
  2. Dependencies — packages declared in cell.toml
  3. Core — built-in ƿit modules
// From within package 'myapp':
use('utils')           // myapp/utils.cm
use('helper/math')     // myapp/helper/math.cm
use('json')            // core json module
use('otherlib/foo')    // dependency 'otherlib', file foo.cm

Files in the internal/ directory are private to the package.

Example: Simple Actor System

// main.ce - Entry point
var config = use('config')

print("Starting application...")

$start(function(event) {
  if (event.type == 'greet') {
    send(event.actor, {task: "process", data: [1, 2, 3]})
  }
  if (event.type == 'stop') {
    print("Worker finished")
    $stop()
  }
}, "worker")

$delay(function() {
  print("Shutting down")
  $stop()
}, 10)
// worker.ce - Worker actor
$receiver(function(msg) {
  if (msg.task == "process") {
    var result = array(msg.data, function(x) { return x * 2 })
    send(msg, {result: result})
  }
  $stop()
})
// config.cm - Shared configuration
return {
  debug: true,
  timeout: 30
}