363 lines
8.6 KiB
Markdown
363 lines
8.6 KiB
Markdown
---
|
|
title: "Actors and Modules"
|
|
description: "The ƿit execution model"
|
|
weight: 20
|
|
type: "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).
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
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.
|
|
|
|
```javascript
|
|
// 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.
|
|
|
|
```javascript
|
|
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.
|
|
|
|
```javascript
|
|
if ($overling != null) {
|
|
send($overling, {status: "ready"})
|
|
}
|
|
```
|
|
|
|
### $stop()
|
|
|
|
Stop the current actor. When called with an actor argument, stops that underling (child) instead.
|
|
|
|
```javascript
|
|
$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.
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
$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.
|
|
|
|
```javascript
|
|
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.
|
|
|
|
```javascript
|
|
$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.
|
|
|
|
```javascript
|
|
$receiver(function(message) {
|
|
// handle incoming message
|
|
send(message, {status: "ok"})
|
|
})
|
|
```
|
|
|
|
### $portal(callback, port)
|
|
|
|
Open a network port to receive connections from remote actors.
|
|
|
|
```javascript
|
|
$portal(function(connection) {
|
|
// handle new connection
|
|
}, 8080)
|
|
```
|
|
|
|
### $contact(callback, record)
|
|
|
|
Connect to a remote actor at a given address.
|
|
|
|
```javascript
|
|
$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](/docs/requestors/) for details.
|
|
|
|
```javascript
|
|
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).
|
|
|
|
```javascript
|
|
$couple(other_actor)
|
|
```
|
|
|
|
### $unneeded(callback, seconds)
|
|
|
|
Schedule the actor for removal after a specified time. The callback fires when the time elapses.
|
|
|
|
```javascript
|
|
$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.
|
|
|
|
```javascript
|
|
$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.
|
|
|
|
```javascript
|
|
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:
|
|
|
|
```javascript
|
|
$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.
|
|
|
|
```javascript
|
|
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](/docs/logging/) for the full guide.
|
|
|
|
### use(path)
|
|
|
|
Import a module. See [Module Resolution](#module-resolution) below.
|
|
|
|
### args
|
|
|
|
Array of command-line arguments passed to the actor.
|
|
|
|
### sequence(), parallel(), race(), fallback()
|
|
|
|
Requestor composition functions. See [Requestors](/docs/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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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)
|
|
```
|
|
|
|
```javascript
|
|
// 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()
|
|
})
|
|
```
|
|
|
|
```javascript
|
|
// config.cm - Shared configuration
|
|
return {
|
|
debug: true,
|
|
timeout: 30
|
|
}
|
|
```
|