docs revamp

This commit is contained in:
2025-12-17 02:53:01 -06:00
parent 3211b59408
commit 363ca1c3c1
33 changed files with 2308 additions and 1439 deletions

View File

@@ -1,13 +1,9 @@
nav:
- index.md
- quickstart.md
- tutorial.md
- cellscript.md
- actors.md
- rendering.md
- resources.md
- input.md
- exporting.md
- ...
- Appendix A - dull: dull
- Appendix B - api: api
- packages.md
- cli.md
- c-modules.md
- Standard Library: library

230
docs/actors.md Normal file
View File

@@ -0,0 +1,230 @@
# Actors and Modules
Cell organizes code into two types of scripts: **modules** (`.cm`) and **actors** (`.ce`).
## The Actor Model
Cell 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')
function distance(x1, y1, x2, y2) {
var dx = x2 - x1
var dy = y2 - y1
return math.sqrt(dx * dx + dy * dy)
}
function midpoint(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
log.console("Worker started")
$_.on_message = function(msg) {
log.console("Received:", msg)
// Process message...
}
```
**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 `$`:
### $me
Reference to the current actor.
```javascript
log.console($me) // actor reference
```
### $_.stop()
Stop the current actor.
```javascript
$_.stop()
```
### $send(actor, message, callback)
Send a message to another actor.
```javascript
$send(other_actor, {type: "ping", data: 42}, function(reply) {
log.console("Got reply:", reply)
})
```
Messages are automatically **splatted** — flattened to plain data without prototypes.
### $start(callback, program)
Start a new actor from a script.
```javascript
$start(function(new_actor) {
log.console("Started:", new_actor)
}, "worker")
```
### $delay(callback, seconds)
Schedule a callback after a delay.
```javascript
$delay(function() {
log.console("5 seconds later")
}, 5)
```
### $clock(callback)
Get called every frame/tick.
```javascript
$clock(function(dt) {
// Called each tick with delta time
})
```
### $receiver(callback)
Set up a message receiver.
```javascript
$receiver(function(message, reply) {
// Handle incoming message
reply({status: "ok"})
})
```
### $portal(callback, port)
Open a network port.
```javascript
$portal(function(connection) {
// Handle new connection
}, 8080)
```
### $contact(callback, record)
Connect to a remote address.
```javascript
$contact(function(connection) {
// Connected
}, {host: "example.com", port: 80})
```
### $time_limit(requestor, seconds)
Wrap a requestor with a timeout.
```javascript
$time_limit(my_requestor, 10) // 10 second timeout
```
## Module Resolution
When you call `use('name')`, Cell searches:
1. **Current package** — files relative to package root
2. **Dependencies** — packages declared in `cell.toml`
3. **Core** — built-in Cell 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 starting with underscore (`_helper.cm`) are private to the package.
## Example: Simple Actor System
```javascript
// main.ce - Entry point
var config = use('config')
log.console("Starting application...")
$start(function(worker) {
$send(worker, {task: "process", data: [1, 2, 3]})
}, "worker")
$delay(function() {
log.console("Shutting down")
$_.stop()
}, 10)
```
```javascript
// worker.ce - Worker actor
$receiver(function(msg, reply) {
if (msg.task == "process") {
var result = array(msg.data, x => x * 2)
reply({result: result})
}
})
```
```javascript
// config.cm - Shared configuration
return {
debug: true,
timeout: 30
}
```

277
docs/c-modules.md Normal file
View File

@@ -0,0 +1,277 @@
# Writing C Modules
Cell makes it easy to extend functionality with C code. C files in a package are compiled into a dynamic library and can be imported like any other module.
## Basic Structure
A C module exports a single function that returns a JavaScript value:
```c
// mymodule.c
#include "cell.h"
#define CELL_USE_NAME js_mypackage_mymodule_use
static JSValue js_add(JSContext *js, JSValue self, int argc, JSValue *argv) {
double a = js2number(js, argv[0]);
double b = js2number(js, argv[1]);
return number2js(js, a + b);
}
static JSValue js_multiply(JSContext *js, JSValue self, int argc, JSValue *argv) {
double a = js2number(js, argv[0]);
double b = js2number(js, argv[1]);
return number2js(js, a * b);
}
static const JSCFunctionListEntry js_funcs[] = {
MIST_FUNC_DEF(mymodule, add, 2),
MIST_FUNC_DEF(mymodule, multiply, 2),
};
CELL_USE_FUNCS(js_funcs)
```
## Symbol Naming
The exported function must follow this naming convention:
```
js_<package>_<filename>_use
```
Where:
- `<package>` is the package name with `/` and `.` replaced by `_`
- `<filename>` is the C file name without extension
Examples:
- `mypackage/math.c``js_mypackage_math_use`
- `gitea.pockle.world/john/lib/render.c``js_gitea_pockle_world_john_lib_render_use`
## Required Headers
Include `cell.h` for all Cell integration:
```c
#include "cell.h"
```
This provides:
- QuickJS types and functions
- Conversion helpers
- Module definition macros
## Conversion Functions
### JavaScript ↔ C
```c
// Numbers
double js2number(JSContext *js, JSValue v);
JSValue number2js(JSContext *js, double g);
// Booleans
int js2bool(JSContext *js, JSValue v);
JSValue bool2js(JSContext *js, int b);
// Strings (must free with JS_FreeCString)
const char *JS_ToCString(JSContext *js, JSValue v);
void JS_FreeCString(JSContext *js, const char *str);
JSValue JS_NewString(JSContext *js, const char *str);
```
### Blobs
```c
// Get blob data (returns pointer, sets size in bytes)
void *js_get_blob_data(JSContext *js, size_t *size, JSValue v);
// Get blob data in bits
void *js_get_blob_data_bits(JSContext *js, size_t *bits, JSValue v);
// Create new stone blob from data
JSValue js_new_blob_stoned_copy(JSContext *js, void *data, size_t bytes);
// Check if value is a blob
int js_is_blob(JSContext *js, JSValue v);
```
## Function Definition Macros
### JSC_CCALL
Define a function with automatic return value:
```c
JSC_CCALL(mymodule_greet,
const char *name = JS_ToCString(js, argv[0]);
char buf[256];
snprintf(buf, sizeof(buf), "Hello, %s!", name);
ret = JS_NewString(js, buf);
JS_FreeCString(js, name);
)
```
### JSC_SCALL
Shorthand for functions taking a string first argument:
```c
JSC_SCALL(mymodule_strlen,
ret = number2js(js, strlen(str));
)
```
### MIST_FUNC_DEF
Register a function in the function list:
```c
MIST_FUNC_DEF(prefix, function_name, arg_count)
```
## Module Export Macros
### CELL_USE_FUNCS
Export an object with functions:
```c
static const JSCFunctionListEntry js_funcs[] = {
MIST_FUNC_DEF(mymod, func1, 1),
MIST_FUNC_DEF(mymod, func2, 2),
};
CELL_USE_FUNCS(js_funcs)
```
### CELL_USE_INIT
For custom initialization:
```c
CELL_USE_INIT(
JSValue obj = JS_NewObject(js);
// Custom setup...
return obj;
)
```
## Complete Example
```c
// vector.c - Simple 2D vector operations
#include "cell.h"
#include <math.h>
#define CELL_USE_NAME js_mypackage_vector_use
JSC_CCALL(vector_length,
double x = js2number(js, argv[0]);
double y = js2number(js, argv[1]);
ret = number2js(js, sqrt(x*x + y*y));
)
JSC_CCALL(vector_normalize,
double x = js2number(js, argv[0]);
double y = js2number(js, argv[1]);
double len = sqrt(x*x + y*y);
if (len > 0) {
JSValue result = JS_NewObject(js);
JS_SetPropertyStr(js, result, "x", number2js(js, x/len));
JS_SetPropertyStr(js, result, "y", number2js(js, y/len));
ret = result;
}
)
JSC_CCALL(vector_dot,
double x1 = js2number(js, argv[0]);
double y1 = js2number(js, argv[1]);
double x2 = js2number(js, argv[2]);
double y2 = js2number(js, argv[3]);
ret = number2js(js, x1*x2 + y1*y2);
)
static const JSCFunctionListEntry js_funcs[] = {
MIST_FUNC_DEF(vector, length, 2),
MIST_FUNC_DEF(vector, normalize, 2),
MIST_FUNC_DEF(vector, dot, 4),
};
CELL_USE_FUNCS(js_funcs)
```
Usage in Cell:
```javascript
var vector = use('vector')
var len = vector.length(3, 4) // 5
var n = vector.normalize(3, 4) // {x: 0.6, y: 0.8}
var d = vector.dot(1, 0, 0, 1) // 0
```
## Combining C and Cell
A common pattern is to have a C file provide low-level functions and a `.cm` file provide a higher-level API:
```c
// _vector_native.c
// ... raw C functions ...
```
```javascript
// vector.cm
var native = this // C module passed as 'this'
function Vector(x, y) {
return {x: x, y: y}
}
Vector.length = function(v) {
return native.length(v.x, v.y)
}
Vector.normalize = function(v) {
return native.normalize(v.x, v.y)
}
return Vector
```
## Build Process
C files are automatically compiled when you run:
```bash
cell build
cell update
```
The resulting dynamic library is placed in `~/.cell/lib/`.
## Platform-Specific Code
Use filename suffixes for platform variants:
```
audio.c # default
audio_playdate.c # Playdate
audio_emscripten.c # Web/Emscripten
```
Cell selects the appropriate file based on the target platform.
## Static Declarations
Keep internal functions and variables `static`:
```c
static int helper_function(int x) {
return x * 2;
}
static int module_state = 0;
```
This prevents symbol conflicts between packages.

View File

@@ -1,626 +1,288 @@
# Cell Language
Cell is a JavaScript variant used in cell. While very similar to JavaScript, it has several important differences that make it more suitable for actor-based programming.
Cell is a scripting language for actor-based programming. It combines a familiar syntax with a prototype-based object system and strict immutability semantics.
Variables are delcared with 'var'. Var behaves like let.
Constants are declared with 'def'.
!= and == are strict, there is no !== or ===.
There is no undefined, only null.
There are no classes, only objects and prototypes.
There are no arraybuffers.
There are no automatic conversions for types.
No proxy.
No JS modules.
No bigint.
No reflect.
No weakmap, weakset, weakref.
No with.
No Number, Date, Array, Math, Function
No setters or getters or any other property descriptor things
No eval.
No new keyword.
No instanceof.
## Basics
The point of cell is to merge the prototype system and actor systems, by giving ergonomic ways to go from prototypical intherited objects to pure data records suitable for sending over the wire.
### Variables and Constants
length(value)
Length. Find the length of an array in elements, a blob in bits, or a text in codepoints. For functions, it is the arity (or number of inputs).
```javascript
var x = 10 // mutable variable (block-scoped like let)
def PI = 3.14159 // constant (cannot be reassigned)
```
If the value is a record containing a length field
### Data Types
If the length field contains a function, then length(my_record) has the same effect as my_record.length().
Cell has six fundamental types:
If the length field contains a number, return the number.
- **number** — DEC64 decimal floating point (no rounding errors)
- **text** — Unicode strings
- **logical** — `true` or `false`
- **null** — the absence of a value (no `undefined`)
- **array** — ordered, numerically-indexed sequences
- **object** — key-value records with prototype inheritance
- **blob** — binary data (bits, not bytes)
- **function** — first-class callable values
All other values produce null.
### Literals
## statements
throw
Throw an error. If an actor has an uncaught error, it crashes.
```javascript
// Numbers
42
3.14
1_000_000 // underscores for readability
## Native constants and functions
// Text
"hello"
'world'
`template ${x}` // string interpolation
false
// Logical
true
false
// Null
null
use(text)
Use a module. Returns the module. Modules are a script which return a single value. The value is cached, and the module is only reloaded when the file changes. The value is stone, so it cannot be modified.
The returned value is actually a cell runtime owned object, which may be modified for hot reloading purposes.
stone(value)
Petrify the value, turning it into stone. Its contents are preserved, but it can no longer be modified by the assign statement or the blob.write functions. This is usually performed on arrays, records, and blobs. All other types are already stone. Attempting to turn a value that is stone to stone will have no effect.
The stone operation is deep. Any mutable objects in the value will also be turned to stone.
This can not be reversed. Immutable objects can never become mutable. A mutable copy can be made of an immutable object, but the original object remains immutable.
The stone function returns the value.
isa(object, master_object)
Return true if master is in the value's prototype chain.
isa(object, function)
isa(value, function)
Return true if function.proto is in the value's prototype chain.
isa(1, stone) true
isa(1, number) true
isa("1", stone) true
isa("1", text) true
isa("1", number) false
isa($me, actor) true
isa($me, object) false
isa($me, stone) true
isa(object, array)
Return true if the object has keys for each value in the array.
reverse(array|blob)
The reverse function makes a new array or blob with the elements or bits in the opposite order.
It returns a new, reversed array or blob.
fn
The function object; not a function.
fn.apply(function, array)
Apply. Execute the function and return its return value. Pass the elements of the array as input values. See proxy.
If the first input value is not a function, apply returns its first input value.
If length(array) is greater than length(function), it disrupts.
If the second argument is not an array, it is used as a single input value.
### log
log(*name*, *options*)
Establish a logging channel.
log.*channel*(*text)
Write to a channel. There are a few already defined:
log.console
log.error
### text
text(*array*, *separator*)
Convert an array to text. All are concatenated to create a single text, separated by the separator. The default separator is a space.
text(*number*, *radix*)
Convert a number to text. The radix is 2 through 37.The default radix is 10.
text(*text*)
Return the text.
text(*text*, *from*, *to*)
Return the text from the from index to the to index.
text.lower(text)
The lower function returns a text in which all uppercase characters are converted to lowercase.
text.normalize(text)
Unicode normalize. Return a text whose textual value is the same as text, but whose binary representation is in the specified Unicode normalization form. The two texts will display the same, but might not be equal.
text.extract(text, pattern, from, to)
The text is matched to the pattern. If it does not match, the result is null. If the pattern does match, then the result is a record containing the saved fields.
text.replace(text, target, replacement, limit)
Return a new text in which the target is replaced by the replacement.
text: the source text.
target: a text or pattern that should be replaced in the source.
replacement: text to replace the matched text, or a function that takes the matched text and the starting position, and returns the replacement text or null if it should be left alone.
limit: The maximum number of replacements. The default is all possible replacements. The limit includes null matches.
text.format(text, collection, transformer)
The format function makes a new text with substitutions in an original text. A collection is either an array of texts or a record of texts.
A search is made for {left brace and }right brace in the text. If they are found, the middle text between them is examined. If the collection is an array, the middle text is used as a number, and then the matched {left brace and middle and }right brace are replaced with the text at that subscript in the array. If the collection is a record, and if the middle text is the key of a member of the collection with a text value, then the value of the member is used in the substitution. Unmatched text is not altered.
The text between {left brace and }right brace is broken on the :colon character. The left text will be used as a number or name to select a value from the collection. (The value need not be a text.)
If a transformer input is a function, then it is called with the collection[left text] and right text as inputs. If it returns a text, then the substitution is made.
If a transformer input is a record, then the right text is used to select a function from the transformer. That function is passed the value from the collection. If the return value is a text, that text will substitute. If there is no colon, then the empty text is used to select the function from the transformer. If the transformer does not produce a function, or if the function does not return a text, then no replacement occurs. If transformer[right text](collection[left text]) produces a text, then the substitution is made.
If the substitution is not made, and if collection[left text] is a number, then the right text is used as a format input in calling collection[left text].text(right text). If it returns a text, then the substitution is made.
text.codepoint(text)
The codepoint function returns the codepoint number of the first character of the text. If the input value is not a text, or if it is the empty text, then it returns null.
text.search(text, target, from)
Search the text for the target. If the target is found, return the character position of the left-most part of the match. If the search fails, return null.
text: the source text.
target: a text or pattern that should be found in the source.
from: The starting position for the search. The default is 0, the beginning of the text. If from is negative, it is added to length(text).
text.trim(text, reject)
The trim function removes selected characters from the ends of a text. The default is to remove control characters and spaces.
text.upper(text)
The upper function returns a text in which all lowercase characters are converted to uppercase.
### logical
logical(value)
Convert a value to a logical (boolean).
If value is 0, false, "false", or null, return false.
If value is 1, true, or "true", return true.
Otherwise, return null.
### number
number(logical)
Result is 1 or 0.
number(number)
Return the number.
number(text, radix)
Convert a text to a number. The radix is 2 through 37. The default radix is 10.
number(text, format)
The number function converts a text into a number.
If it is unable to (possibly because of a formatting error), it returns null. The format character determines how the text is interpreted. If the format is not one of those listed, then null is returned.
format radix separator decimal point
"" 10 .period
"n"
"u" _underbar
"d" ,comma
"s" space
"v" .period ,comma
"l" dependent on locale
"i" _underbar
"b" 2
"o" 8
"h" 16
"t" 32
"j" 0x- base 16
0o- base 8
0b- base 2
otherwise base 10
number.whole(number)
Return the whole part of a number.
4.9 => 4
4.2 => 4
number.fraction(number)
Return the fractional part of a number
number.floor(number, place)
If place is 0 or null, the number is rounded down to the greatest integer that is less than or equal to the number. If place is a small positive integer, then the number is rounded down to that many decimal places. For positive numbers, this is like discarding decimal places.
number.ceiling(number, place)
If place is 0 or null, the number is rounded up to the smallest integer that is greater than or equal to the number. If place is a small positive integer, then the number is rounded up to that decimal place.
number.abs(number)
Absolute value. Return the positive form of the number. If the input value is not a number, the result is null.
number.round(number, place)
If place is 0 or null, the number is rounded to the nearest integer. If place is a small integer, then the number is rounded to that many decimal places.
number.sign(number)
The sign function returns -1 if the number is negative, 0 if the number is exactly 0, 1 if the number is positive, and null if it is not a number.
number.trunc(number, place)
The number is truncated toward zero. If the number is positive, the result is the same as floor(place). If the number is negative, the result is the same as ceiling(place).
If place is a small integer, then the number is rounded down to that many decimal places. This is like discarding decimal places.
number.min(...vals)
Returns the smallest of all vals.
number.max(...vals)
Returns the largest of all vals.
number.remainder(dividend, divisor)
Remainder. For fit integers, the remainder is dividend - ((dividend // divisor) * divisor).
### array
array(number)
Create an array of the specified size. All elements are null.
array(number, initial_value)
Make an array. All of the elements are initialized to initial_value.
number is a non-negative integer, the intended length of the new array.
If initial_value is a function, then the function is called for each element to produce initialization values. If the function has an arity of 1 or more, it is passed the element number.
array(array)
Copy.
array(array, function, reverse, exit)
Map. Call the function with each element of the array, collecting the return values in a new array. The function is passed each element and its element number.
function (element, element_nr)
If reverse is true, then it starts with the last element and works backwards.
If exit is not null, then when the function returns the exit value, then the array function returns early. The exit value will not be stored into the new array. If the array was processed normally, then the returned array will be shorter than the input array. If the array was processed in reverse, then the returned array will have the same length as the input array, and the first elements will be null. The elements in the new array that were not set due to an early exit will be set to null.
array(array, another_array)
Concat. Produce a new array that concatenates the array and another_array.
array(array, from, to)
Slice. Make a mutable copy of all or part of an array.
array: the array to copy
from: the position at which to start copying. Default: 0, the beginning. If negative, add length(array).
to: the position at which to stop copying. Default: length(array), the end. If negative, add length(array).
If, after adjustment, from and to are not valid integers in the proper range, then it returns null. from must be positive and less than or equal to to. to must be less than or equal to length(array).
array(object)
Keys. Make an array containing all of the text keys in the object. The keys are not guaranteed to be in any particular order.
array(text)
Split the text into grapheme clusters. A grapheme cluster is a Unicode codepoint and its contributing combining characters, if any.
array(text, separator)
Split the text into an array of subtexts. The separator can be a text or pattern.
array(text, length)
Dice the text into an array of subtexts of a given length.
array.reduce(array, function, initial, reverse)
Reduce. The reduce function takes a function that takes two input values and returns a value.
function (first, second) {
return ...
// Arrays
[1, 2, 3]
["a", "b", "c"]
// Objects
{name: "cell", version: 1}
{x: 10, y: 20}
```
### Operators
```javascript
// Arithmetic
+ - * / %
** // exponentiation
// Comparison (always strict)
== // equals (like === in JS)
!= // not equals (like !== in JS)
< > <= >=
// Logical
&& || !
// Assignment
= += -= *= /=
```
### Control Flow
```javascript
// Conditionals
if (x > 0) {
log.console("positive")
} else if (x < 0) {
log.console("negative")
} else {
log.console("zero")
}
The function is called for each element of the array, passing the result of the previous iteration to the next iteration.
The initial value is optional. If present, the function is called for every element of the array.
// Ternary
var sign = x > 0 ? 1 : -1
If initial is null:
// Loops
for (var i = 0; i < 10; i++) {
log.console(i)
}
If length(array) is 0, then it returns null.
If length(array) is 1, then it returns array[0].
If length(array) is 2, it returns function(array[0], array[1]).
If length(array) is 3, it returns function(function(array[0], array[1]), array[2]).
And so on.
If initial is not null:
for (var item of items) {
log.console(item)
}
If length(array) is 0, then it returns initial.
If length(array) is 1, then it returns function(initial, array[0]).
If length(array) is 2, it returns function(function(initial, array[0]), array[1]).
If length(array) is 3, it returns function(function(function(initial, array[0]), array[1]), array[2]).
And so on.
If reverse is true, then the work begins at the end of the array and works backward.
for (var key in obj) {
log.console(key, obj[key])
}
array.for(array, function, reverse, exit)
For each. Call the function with each element of the array. The function is passed each element and its element number.
while (condition) {
// body
}
(element, element_nr)
If reverse is true, then it starts with the last element and works backwards.
// Control
break
continue
return value
throw "error message"
```
If exit is not null, then when the function returns the exit value, then the for function returns early. The exit value is usually true or false, but it may be anything. If exit is null, then every element is processed.
### Functions
The for function returns null unless it did an early exit, when it returns the exit value.
```javascript
// Named function
function add(a, b) {
return a + b
}
array.find(array, function, reverse, from)
Call the function for each element of the array, passing each element and its element number.
// Anonymous function
var multiply = function(a, b) {
return a * b
}
(element, element_nr)
If the function returns true, then find returns the element number of the current element.
// Arrow function
var square = x => x * x
var sum = (a, b) => a + b
If the second input value is not a function, then it is compared exactly to the elements.
// Rest parameters
function log_all(...args) {
for (var arg of args) log.console(arg)
}
If the reverse input value is true, then search begins at the end of the array and works backward.
// Default parameters
function greet(name, greeting = "Hello") {
return `${greeting}, ${name}!`
}
```
The from input value gives the element number to search first. The default is 0 unless reverse is true, when the default is length(array) - 1.
All closures capture `this` (like arrow functions in JavaScript).
find returns the element number of the found value. If nothing is found, find returns null.
## Arrays
array.filter(array, function)
The filter function calls a function for every element in the array, passing each element and its element number.
Arrays are **distinct from objects**. They are ordered, numerically-indexed sequences. You cannot add arbitrary string keys to an array.
(element, element_nr)
When the function's return value is true, then the element is copied into a new array. If the function's return value is false, then the element is not copied into the new array. If the return value is not a logical, then the filter returns null.
```javascript
var arr = [1, 2, 3]
arr[0] // 1
arr[2] = 10 // [1, 2, 10]
length(arr) // 3
It returns a new array. The length of the new array is between 0 thru length(array). It returns null if the function input is not a function.
// Array spread
var more = [...arr, 4, 5] // [1, 2, 10, 4, 5]
```
array.sort(array, select)
The sort function produces a new array in which the values are sorted. Sort keys must be either all numbers or all texts. Any other type of key or any error in the key calculation will cause the sort to fail, returning null. The sort is ascending. The sort is stable, so the relative order of equal keys is preserved.
## Objects
The optional select input determines how the sort key for each element is selected.
Objects are key-value records with prototype-based inheritance.
select type Sort key for array[index] Description
null array[index] The sort key is the element itself. This is useful for sorting simple arrays of numbers or texts.
text array[index][select] The sort key is the select field of each record element. This is useful for sorting arrays of records.
number array[index][select] The sort key is the select element of each array element. This is useful for sorting arrays of arrays.
array select[index] select is an array of the same length containing the sort keys.
It returns a new, sorted array.
```javascript
var point = {x: 10, y: 20}
point.x // 10
point["y"] // 20
### object
object(object)
Shallow mutable copy. Text keys
// Object spread
var point3d = {...point, z: 30}
splat(object)
Create a new object with the same fields as the object, compressed down its prototype chain, for all keys.
// Prototype inheritance
var colored_point = {__proto__: point, color: "red"}
colored_point.x // 10 (inherited)
```
The only types in a splat will be objects, arrays, numbers, boolean, and text.
### Prototypes
When sending an object with $send, it is automatically splat'd.
```javascript
// Create object with prototype
var child = meme(parent)
meme(record, record)
create a new record with the first as its prototype, and add the second's fields. prototypes cannot be changed.
// Get prototype
var p = proto(child)
proto(object)
Return the object's prototype.
// Check prototype chain
isa(child, parent) // true
```
object(object, another_object)
Combine. Make a copy of a object, and then put all the fields of another_object into the copy.
## Immutability with Stone
object(object, array_of_keys)
Select. Make a new object containing only the fields that are named by the array_of_keys.
The `stone()` function makes values permanently immutable.
object(array_of_keys)
Set. Make a object using the array as the source of the keys. Each field value is true.
```javascript
var config = stone({
debug: true,
maxRetries: 3
})
object(array_of_keys, value)
Value Set. Make a object using the array as the source of the keys. Each field value is value.
config.debug = false // Error! Stone objects cannot be modified
```
object(array_of_keys, function)
Functional Value Set. Make a object using the array as the source of the keys. The function is called for each key, yielding the field values.
Stone is **deep** — all nested objects and arrays are also frozen. This cannot be reversed.
### parallelism
Callbacks
A callback function is a function that is used to deliver functional results from the future. A callback function has this signature:
```javascript
stone.p(value) // returns true if value is stone
```
function (value, reason)
The value is the value of the operation if it was successful. The value is null if it is unsuccessful.
## Built-in Functions
If the value is null, then the optional reason may include an error message or indication of the cause of the unsuccess.
### length(value)
Requestor functions take a callback.
Returns the length of arrays (elements), text (codepoints), blobs (bits), or functions (arity).
Requestors
A requestor function encapsulates a unit of work, communicating the result thru a callback, allowing work to progress over many turns. A requestor function has this signature:
```javascript
length([1, 2, 3]) // 3
length("hello") // 5
length(function(a,b){}) // 2
```
function requestor(callback, value)
When the requestor function is finished, it calls the callback function with the result.
### use(path)
The optional value is some value that is used to determine the result.
Import a module. Returns the cached, stone value.
A common usage is to send a message to some actor. When the reply eventually arrives, extract a result from the reply and pass it to the callback.
```javascript
var math = use('math/radians')
var json = use('json')
```
A requestor function can optionally return a cancel function.
### isa(value, type)
Cancels
A requestor function may optionally return a cancel function. When the cancel function is called, it attempts to stop the work of the requestor. A cancel function try to send a message to some actor informing it that the result is no longer needed. The purpose of cancel is to stop work that is no longer required. It is advisory. It is not an undo. It is not guaranteed, particularly in the case where cancel is called after some actor has completed its work.
Check type or prototype chain.
Cancel is most effective with the requestor factories that are organizing the work.
```javascript
isa(42, number) // true
isa("hi", text) // true
isa([1,2], array) // true
isa({}, object) // true
isa(child, parent) // true if parent is in prototype chain
```
A cancel function has this signature:
### reverse(array)
function cancel(reason, abandon | true)
The reason is optional, but if included might be logged or propagated. If abandon is true, then simply stop the requested work. If abandon is false, then if possible, complete the assignment, perhaps by calling the callback with the reason.
Returns a new array with elements in reverse order.
parallel(requestor_array, throttle, need)
Parallel. The parallel requestor factory returns a resquestor function. When the requestor function is called with a callback function and a value, every requestor in the requestor_array is called with the value, and the results are placed in corresponding elements of the results array. This all happens in parallel. The value produced by the first element of the requestor_array provides the first element of the result. The completed results array is passed to the callback function.
```javascript
reverse([1, 2, 3]) // [3, 2, 1]
```
By default, it starts all of the requestors in the requestor_array at once, each in its own turn so that they do not interfere with each other. This can shock some systems by unleashing a lot of demand all at once. To mitigate the shock, the optional throttle argument sets the maximum number of requestors running at a time. As requestors succeed or fail, waiting requestors can be started. The throttle is optional. If provided, the throttle is a number: the maximum number of requestors to have running at any time.
### logical(value)
Ordinarily, the number of successes must be the same as the number of requestors in the requestor_array. If you need few successes, specify your need with the need argument. The need could be 1, meaning that 1 or more successes are needed. The need could be 0, meaning that no successes are needed. If the number of successes is greater than or equal to need, then the whole operation succeeds. The need must be between 0 and requestor_array.length.
Convert to boolean.
The requestor function itself returns a cancel function that cancels all of the pending requestors from the requestor_array.
```javascript
logical(0) // false
logical(1) // true
logical("true") // true
logical("false") // false
logical(null) // false
```
race(requestor_array, throttle, need)
Race. The race function is a requestor factory that takes an array of requestor functions and returns a requestor function that starts all of the requestors in the requestor_array at once.
## Logging
By default, it starts all of the requestors in the requestor_array at once, each in its own turn so that they do not interfere with each other. This can shock some systems by unleashing a lot of demand at once. To mitigate the shock, the optional throttle argument sets the maximum number of requestors running at a time. As requestors succeed or fail, waiting requestors can be started.
```javascript
log.console("message") // standard output
log.error("problem") // error output
```
By default, a single result is produced. If an array of results is need, specify the needed number of results in the need parameter. When the needed number of successful results is obtained, the operation ends. The results go into a sparce array aligned with the requestor_array, and unfinished requestors are cancelled. The need argument must be between 1 and requestor_array.length.
## Pattern Matching
The returned requestor function returns a cancel function that cancels all of the pending requestors from the requestor_array.
Cell supports regex patterns in string functions, but not standalone regex objects.
sequence(requestor_array)
Sequence. The sequence requestor factory that takes a requestor_array and returns a requestor function that starts all of the requestors in the requestor_array one at a time. The result of each becomes the input to the next. The last result is the result of the sequence.
```javascript
text.search("hello world", /world/)
text.replace("hello", /l/g, "L")
```
sequence returns a requestor that returns a cancel function and processes each requestor in requestor_array one at a time. Each of those requestors is passed the result of the previous requestor as its value argument. If all succeed, then the sequence succeeds, giving the result of the last of the requestors. If any fail, then the sequence fails.The sequence succeeds if every requestor in the requestor_array succeeds
## Error Handling
fallback(requestor_array)
The fallback requestor factory returns a requestor function that tries each of the requestors in the requstor_array until it gets a success. When the requestor is called, it calls the first requestor in requestor_array. If that is eventually successful, its value is passed to the callback. But if that requestor fails, the next requestor is called, and so on. If none of the requestors is successful, then the fallback fails. If any one succeeds, then the fallback succeeds.
```javascript
try {
riskyOperation()
} catch (e) {
log.error(e)
}
The fallback requestor returns a cancel function that can be called when the result is no longer needed.
throw "something went wrong"
```
## Standard library
### time
use('time')
constants
time.second : 1
The number of seconds in a second.
time.minute : 60
The number of seconds in a minute.
time.hour : 3_600
The number of seconds in an hour of 60 minutes.
time.day : 86_400
The number of seconds in a day of 24 hours.
time.week : 604_800
The number of seconds in a week of 7 days.
time.month : 2_629_746
The number of seconds in a Gregorian month of 30.436875 days.
time.year : 31_556_952
The number of seconds in a Gregorian year of 365.2425 days
time has three functions: time.number(), time.record(), time.text().
time.number() returns the time now as a number of seconds since the epoch.
time.record() returns the time now as a record, with year, month, day, hour, minute, second, and nanosecond.
time.text(format) returns the time now as a text, using the format string. The standard format string is "yyyy-MM-dd HH:mm:ss.SSS".
Pass a time into a function to convert it.
It's easy to say "tomorrow": time.number() + time.day = this time tomorrow. As a string: time.text(time.number() + time.day).
time.number(text, format, zone)
time.number(record)
returns the time as a number of seconds since the epoch.
time.text(number, format, zone)
time.text(record, format, zone)
returns the time as a text, using the format string. The standard format string is "yyyy-MM-dd HH:mm:ss.SSS".
time.record(number)
time.record(text, format, zone)
### math
There are three math variants: use('math/radians'), use('math/degrees'), use('math/cycles'). Each has the same functions, but with different representation of angles.
arc_cosine(number)
arc_sine(number)
arc_tangent(number, denominator)
Compute the arc tangent of a If the optional denominator is supplied, then the value is obtained from number / denominator, using the signs of both to determine the quadrant. The denominator is allowed to be 0. The result is the inverse tangent, in radians, between neg(π) and π.
e(power = 1)
ln(number)
Natural log
log(number)
Base 10 log
log2(number)
Base 2 log
power(first, second)
root(radicand, number)
sine(number)
sqrt(number)
tangent(number)
### blob
use('blob')
A blob is a binary large object, a linear container of bits. Blobs can be used to encode data, messages, sounds, images, public keys, network addresses, and encrypted payloads.
A blob can be in one of two states, either antestone or stone. In the mutable antestone state, the write functions may be used to append bits to the blob. In the immutable stone state, bits can be harvested from the blob. Bits can be written to blobs as fixed size bit fields, that is a sequence of bits with a specified length, or as a kim.
blob.make()
Make a new empty blob.
blob.make(capacity)
Make a new empty blob with an initial capacity in bits. When turned to stone, the excess bits are discarded. If the initial capacity is too small, the write functions will extended it. A good initial guess can improve performance.
blob.make(length, logical)
Make a new blob containing all zeros (false) or all ones (true).
blob.make(length, random)
Make a new blob of a given length whose content is random. The random input is a random generator function that returns fit numbers, like random.random_fit().
blob.make(blob, from, to)
Make a copy of all or part of a blob. The default of from is 0. The default of to is the length(blob).
blob.write_bit(blob, logical)
Append a bit to the end of the blob. The logical value can be true, false, 1, or 0. Any other value will throw.
blob.write_blob(blob, second_blob)
Append second_blob to the end of blob.
blob.write_dec64(blob, number)
Append a 64 bit DEC64 encoded number to a stone blob.
blob.write_fit(blob, fit, length)
Append a bit field to the blob. If the fit requires more bits than allowed by length, it throws.
blob.write_kim(blob, fit)
Append a fit number or a single character as a kim value.
blob.write_pad(blob, block_size)
Append a 1 bit to the blob followed by enough 0 bits to round up the blob's length to a multiple of the block_size.
blob.write_text(blob, text)
Append a text. This will be encoded as a kim encoded length followed by a sequence of kim encoded UTF-32 characters.
blob.read_blob(blob, from, to)
Make a copy of all or part of a blob. The default of from is 0. The default of to is the length(blob).
blob.read_dec64(blob, from)
Retrieve a 64 bit DEC64 encoded number from a stone blob.
blob.read_fit(blob, from, length)
Retrieve a fit number from a bit field from a stone blob.
blob.read_kim(blob, from)
Retrieve a kim encoded fit number from a stone blob.
blob.read_logical(blob, from)
Retrieve a bit from the blob. If blob is not a stone blob, or if from is out of range, it returns null.
blob.read_text(blob, from)
Retrieve a kim encoded text from a stone blob.
blob.pad?(blob, from, block_size)
Return true if the stone blob's length is a multiple of the block_size (in bits), and if the difference between length and from is less than or equal to the block_size, and if the bit at from is 1, and that any remaining bits are 0. See write_pad.
### json
use('json')
json.encode(value, space, replacer, whitelist)
json.decode(text, reviver)
### random
random.random()
The random function returns a number between 0 and 1. There is a 50% chance that the result is less than 0.5.
random.random_fit()
The random_fit function returns an integer in the range -36028797018963968 thru 36028797018963967 that contains 56 random bits. See fit.
random.random_whole(number)
The random_whole function returns a whole number that is greater than or equal to zero and less than the number, which must be less than 36028797018963968.
## Actors
A program has may have access to functions which are not standard which have been bestowed on it. These begin with a '$'.
$send(actor, message, callback)
$clock(callback)
$delay(callback, seconds)
$time_limit(requestor, seconds)
$contact(callback, record)
$couple(actor)
$portal(callback, port)
$receiver(callback)
$start(callback, program)
$stop(actor)
$unneeded(function, seconds)
$me
The actor object of the running program.
If an actor has an uncaught error, it crashes.

171
docs/cli.md Normal file
View File

@@ -0,0 +1,171 @@
# Command Line Interface
Cell provides a command-line interface for managing packages, running scripts, and building applications.
## Basic Usage
```bash
cell <command> [arguments]
```
## Commands
### cell version
Display the Cell version.
```bash
cell version
# 0.1.0
```
### cell install
Install a package to the shop.
```bash
cell install gitea.pockle.world/john/prosperon
cell install /Users/john/local/mypackage # local path
```
### cell update
Update packages from remote sources.
```bash
cell update # update all packages
cell update <package> # update specific package
```
### cell remove
Remove a package from the shop.
```bash
cell remove gitea.pockle.world/john/oldpackage
```
### cell list
List installed packages.
```bash
cell list # list all installed packages
cell list <package> # list dependencies of a package
```
### cell ls
List modules and actors in a package.
```bash
cell ls # list files in current project
cell ls <package> # list files in specified package
```
### cell build
Build the current package.
```bash
cell build
```
### cell test
Run tests.
```bash
cell test # run tests in current package
cell test all # run all tests
cell test <package> # run tests in specific package
```
### cell link
Manage local package links for development.
```bash
cell link add <canonical> <local_path> # link a package
cell link list # show all links
cell link delete <canonical> # remove a link
cell link clear # remove all links
```
### cell fetch
Fetch package sources without extracting.
```bash
cell fetch <package>
```
### cell upgrade
Upgrade the Cell installation itself.
```bash
cell upgrade
```
### cell clean
Clean build artifacts.
```bash
cell clean
```
### cell help
Display help information.
```bash
cell help
cell help <command>
```
## Running Scripts
Any `.ce` file in the Cell core can be run as a command:
```bash
cell version # runs version.ce
cell build # runs build.ce
cell test # runs test.ce
```
## Package Locators
Packages are identified by locators:
- **Remote**: `gitea.pockle.world/user/repo`
- **Local**: `/absolute/path/to/package`
```bash
cell install gitea.pockle.world/john/prosperon
cell install /Users/john/work/mylib
```
## Configuration
Cell stores its data in `~/.cell/`:
```
~/.cell/
├── packages/ # installed packages
├── lib/ # compiled dynamic libraries
├── build/ # build cache
├── cache/ # downloaded archives
├── lock.toml # installed package versions
└── link.toml # local development links
```
## Environment
Cell reads the `HOME` environment variable to locate the shop directory.
## Exit Codes
- `0` — Success
- Non-zero — Error (check output for details)

View File

@@ -1,26 +1,66 @@
# Preface: The Cell Environment
# Cell
![image](wizard.png)
Cell is an actor based scripting language.
Cell is an actor-based scripting language for building concurrent applications. It combines a familiar JavaScript-like syntax with the actor model of computation.
The idea is to author c code and orchestrate it using cell script, in an actor based environment, and deploy it on many platforms.
## Key Features
## Building code
There are two ways to run cell:
- **Actor Model** — isolated memory, message passing, no shared state
- **Immutability** — `stone()` makes values permanently frozen
- **Prototype Inheritance** — objects without classes
- **C Integration** — seamlessly extend with native code
- **Cross-Platform** — deploy to desktop, web, and embedded
The dev environment is highly dynamic and available only on platforms with dynamic library loading. It lets you spawn actors and test quickly.
## Quick Start
Cake is a static builder for cell, which lets you bundle a package into a static executable for a variety of target platforms.
```javascript
// hello.ce - A simple actor
log.console("Hello, Cell!")
$_.stop()
```
## Packages
Cell doesn't assume an underlying file system.
```bash
cell hello
```
Packages are the fundamental cell unit. A package is made up of a variety of cell script files and potentially C files. When bundling, everything in a package is bundled together; when running in dev, everything in a package is loaded dynamically.
## Documentation
Cell code is written with cellscript.
- [**Cell Language**](cellscript.md) — syntax, types, and built-in functions
- [**Actors and Modules**](actors.md) — the execution model
- [**Packages**](packages.md) — code organization and sharing
- [**Command Line**](cli.md) — the `cell` tool
- [**Writing C Modules**](c-modules.md) — native extensions
## Modules and programs
A module returns a single object. This can be a function, an object of functions, a number, a string, whatever. It is included with the "use" keyword, ie, use('<package>/<module path>').
## Standard Library
A program doesn't return anything. A program is an actor, with its own memory space. Actors do not share memory. Actors can send messages to each other.
- [text](library/text.md) — string manipulation
- [number](library/number.md) — numeric operations
- [array](library/array.md) — array utilities
- [object](library/object.md) — object utilities
- [blob](library/blob.md) — binary data
- [time](library/time.md) — time and dates
- [math](library/math.md) — trigonometry and math
- [json](library/json.md) — JSON encoding/decoding
- [random](library/random.md) — random numbers
## Architecture
Cell programs are organized into **packages**. Each package contains:
- **Modules** (`.cm`) — return a value, cached and frozen
- **Actors** (`.ce`) — run independently, communicate via messages
- **C files** (`.c`) — compiled to native libraries
Actors never share memory. They communicate by sending messages, which are automatically serialized. This makes concurrent programming safe and predictable.
## Installation
```bash
# Clone and bootstrap
git clone https://gitea.pockle.world/john/cell
cd cell
make bootstrap
```
The Cell shop is stored at `~/.cell/`.

10
docs/library/.pages Normal file
View File

@@ -0,0 +1,10 @@
nav:
- text.md
- number.md
- array.md
- object.md
- blob.md
- time.md
- math.md
- json.md
- random.md

151
docs/library/array.md Normal file
View File

@@ -0,0 +1,151 @@
# array
The `array` function and its methods handle array creation and manipulation.
## Creation
### array(number)
Create an array of specified size, filled with `null`.
```javascript
array(3) // [null, null, null]
```
### array(number, initial)
Create an array with initial values.
```javascript
array(3, 0) // [0, 0, 0]
array(3, i => i * 2) // [0, 2, 4]
```
### array(array)
Copy an array.
```javascript
var copy = array(original)
```
### array(array, from, to)
Slice an array.
```javascript
array([1, 2, 3, 4, 5], 1, 4) // [2, 3, 4]
array([1, 2, 3], -2) // [2, 3]
```
### array(array, another)
Concatenate arrays.
```javascript
array([1, 2], [3, 4]) // [1, 2, 3, 4]
```
### array(object)
Get keys of an object.
```javascript
array({a: 1, b: 2}) // ["a", "b"]
```
### array(text)
Split text into grapheme clusters.
```javascript
array("hello") // ["h", "e", "l", "l", "o"]
array("👨‍👩‍👧") // ["👨‍👩‍👧"]
```
### array(text, separator)
Split text by separator.
```javascript
array("a,b,c", ",") // ["a", "b", "c"]
```
### array(text, length)
Split text into chunks.
```javascript
array("abcdef", 2) // ["ab", "cd", "ef"]
```
## Methods
### array.for(arr, fn, reverse, exit)
Iterate over elements.
```javascript
array.for([1, 2, 3], function(el, i) {
log.console(i, el)
})
// With early exit
array.for([1, 2, 3, 4], function(el) {
if (el > 2) return true
log.console(el)
}, false, true) // prints 1, 2
```
### array.find(arr, fn, reverse, from)
Find element index.
```javascript
array.find([1, 2, 3], 2) // 1
array.find([1, 2, 3], x => x > 1) // 1
array.find([1, 2, 3], x => x > 1, true) // 2 (from end)
```
### array.filter(arr, fn)
Filter elements.
```javascript
array.filter([1, 2, 3, 4], x => x % 2 == 0) // [2, 4]
```
### array.reduce(arr, fn, initial, reverse)
Reduce to single value.
```javascript
array.reduce([1, 2, 3, 4], (a, b) => a + b) // 10
array.reduce([1, 2, 3, 4], (a, b) => a + b, 10) // 20
```
### array.sort(arr, select)
Sort array (returns new array).
```javascript
array.sort([3, 1, 4, 1, 5]) // [1, 1, 3, 4, 5]
// Sort by field
array.sort([{n: 3}, {n: 1}], "n") // [{n: 1}, {n: 3}]
// Sort by index
array.sort([[3, "c"], [1, "a"]], 0) // [[1, "a"], [3, "c"]]
```
## Map with array()
The `array(arr, fn)` form maps over elements:
```javascript
array([1, 2, 3], x => x * 2) // [2, 4, 6]
array([1, 2, 3], function(el, i) {
return el + i
}) // [1, 3, 5]
```

182
docs/library/blob.md Normal file
View File

@@ -0,0 +1,182 @@
# blob
Blobs are binary large objects — containers of bits (not bytes). They're used for encoding data, messages, images, network payloads, and more.
## States
A blob exists in one of two states:
- **antestone** (mutable) — write operations allowed
- **stone** (immutable) — read operations allowed
```javascript
var blob = use('blob')
var b = blob.make() // antestone
blob.write_bit(b, 1)
blob.write_fit(b, 42, 8)
stone(b) // now stone, readable
```
## Creation
### blob.make()
Create an empty blob.
```javascript
var b = blob.make()
```
### blob.make(capacity)
Create with initial capacity (bits).
```javascript
var b = blob.make(1024) // 1024 bits capacity
```
### blob.make(length, logical)
Create filled with zeros or ones.
```javascript
blob.make(64, false) // 64 zero bits
blob.make(64, true) // 64 one bits
```
### blob.make(blob, from, to)
Copy a range from another blob.
```javascript
var slice = blob.make(original, 0, 32) // first 32 bits
```
## Writing (antestone only)
### blob.write_bit(b, logical)
Append a single bit.
```javascript
blob.write_bit(b, true) // append 1
blob.write_bit(b, false) // append 0
```
### blob.write_fit(b, value, length)
Append a fixed-width integer.
```javascript
blob.write_fit(b, 255, 8) // 8-bit value
blob.write_fit(b, 1000, 16) // 16-bit value
```
### blob.write_blob(b, other)
Append another blob's contents.
```javascript
blob.write_blob(b, other_blob)
```
### blob.write_dec64(b, number)
Append a 64-bit DEC64 number.
```javascript
blob.write_dec64(b, 3.14159)
```
### blob.write_text(b, text)
Append text (kim-encoded length + characters).
```javascript
blob.write_text(b, "hello")
```
### blob.write_pad(b, block_size)
Pad to block boundary (1 bit + zeros).
```javascript
blob.write_pad(b, 8) // pad to byte boundary
```
## Reading (stone only)
### blob.read_logical(b, from)
Read a single bit.
```javascript
var bit = blob.read_logical(b, 0) // first bit
```
### blob.read_fit(b, from, length)
Read a fixed-width integer.
```javascript
var value = blob.read_fit(b, 0, 8) // read 8 bits from position 0
```
### blob.read_blob(b, from, to)
Extract a range as new blob.
```javascript
var slice = blob.read_blob(b, 8, 24) // bits 8-23
```
### blob.read_dec64(b, from)
Read a 64-bit DEC64 number.
```javascript
var num = blob.read_dec64(b, 0)
```
### blob.read_text(b, from)
Read kim-encoded text.
```javascript
var str = blob.read_text(b, 0)
```
### blob.pad?(b, from, block_size)
Check if padding is valid.
```javascript
if (blob["pad?"](b, pos, 8)) {
// valid byte-aligned padding
}
```
## Length
```javascript
length(b) // returns bit count
```
## Example
```javascript
var blob = use('blob')
// Encode a simple message
var msg = blob.make()
blob.write_fit(msg, 1, 8) // message type
blob.write_fit(msg, 42, 32) // payload
blob.write_text(msg, "hello") // text data
stone(msg)
// Decode
var type = blob.read_fit(msg, 0, 8)
var payload = blob.read_fit(msg, 8, 32)
var txt = blob.read_text(msg, 40)
```

90
docs/library/json.md Normal file
View File

@@ -0,0 +1,90 @@
# json
JSON encoding and decoding.
```javascript
var json = use('json')
```
## Encoding
### json.encode(value, space, replacer, whitelist)
Convert a value to JSON text.
```javascript
json.encode({a: 1, b: 2})
// '{"a":1,"b":2}'
// Pretty print with 2-space indent
json.encode({a: 1, b: 2}, 2)
// '{
// "a": 1,
// "b": 2
// }'
```
**Parameters:**
- **value** — the value to encode
- **space** — indentation (number of spaces or string)
- **replacer** — function to transform values
- **whitelist** — array of keys to include
```javascript
// With replacer
json.encode({a: 1, b: 2}, null, function(key, value) {
if (key == "b") return value * 10
return value
})
// '{"a":1,"b":20}'
// With whitelist
json.encode({a: 1, b: 2, c: 3}, null, null, ["a", "c"])
// '{"a":1,"c":3}'
```
## Decoding
### json.decode(text, reviver)
Parse JSON text to a value.
```javascript
json.decode('{"a":1,"b":2}')
// {a: 1, b: 2}
json.decode('[1, 2, 3]')
// [1, 2, 3]
```
**Parameters:**
- **text** — JSON string to parse
- **reviver** — function to transform parsed values
```javascript
// With reviver
json.decode('{"date":"2024-01-15"}', function(key, value) {
if (key == "date") return parse_date(value)
return value
})
```
## Example
```javascript
var json = use('json')
// Save configuration
var config = {
debug: true,
maxRetries: 3,
endpoints: ["api.example.com"]
}
var config_text = json.encode(config, 2)
// Load configuration
var loaded = json.decode(config_text)
log.console(loaded.debug) // true
```

153
docs/library/math.md Normal file
View File

@@ -0,0 +1,153 @@
# math
Cell provides three math modules with identical functions but different angle representations:
```javascript
var math = use('math/radians') // angles in radians
var math = use('math/degrees') // angles in degrees
var math = use('math/cycles') // angles in cycles (0-1)
```
## Trigonometry
### sine(angle)
```javascript
math.sine(math.pi / 2) // 1 (radians)
math.sine(90) // 1 (degrees)
math.sine(0.25) // 1 (cycles)
```
### cosine(angle)
```javascript
math.cosine(0) // 1
```
### tangent(angle)
```javascript
math.tangent(math.pi / 4) // 1 (radians)
```
### arc_sine(n)
Inverse sine.
```javascript
math.arc_sine(1) // π/2 (radians)
```
### arc_cosine(n)
Inverse cosine.
```javascript
math.arc_cosine(0) // π/2 (radians)
```
### arc_tangent(n, denominator)
Inverse tangent. With two arguments, computes atan2.
```javascript
math.arc_tangent(1) // π/4 (radians)
math.arc_tangent(1, 1) // π/4 (radians)
math.arc_tangent(-1, -1) // -3π/4 (radians)
```
## Exponentials and Logarithms
### e(power)
Euler's number raised to a power. Default power is 1.
```javascript
math.e() // 2.718281828...
math.e(2) // e²
```
### ln(n)
Natural logarithm (base e).
```javascript
math.ln(math.e()) // 1
```
### log(n)
Base 10 logarithm.
```javascript
math.log(100) // 2
```
### log2(n)
Base 2 logarithm.
```javascript
math.log2(8) // 3
```
## Powers and Roots
### power(base, exponent)
```javascript
math.power(2, 10) // 1024
```
### sqrt(n)
Square root.
```javascript
math.sqrt(16) // 4
```
### root(radicand, n)
Nth root.
```javascript
math.root(27, 3) // 3 (cube root)
```
## Constants
Available in the radians module:
```javascript
math.pi // 3.14159...
math.e() // 2.71828...
```
## Example
```javascript
var math = use('math/radians')
// Distance between two points
function distance(x1, y1, x2, y2) {
var dx = x2 - x1
var dy = y2 - y1
return math.sqrt(dx * dx + dy * dy)
}
// Angle between two points
function angle(x1, y1, x2, y2) {
return math.arc_tangent(y2 - y1, x2 - x1)
}
// Rotate a point
function rotate(x, y, angle) {
var c = math.cosine(angle)
var s = math.sine(angle)
return {
x: x * c - y * s,
y: x * s + y * c
}
}
```

143
docs/library/number.md Normal file
View File

@@ -0,0 +1,143 @@
# number
The `number` function and its methods handle numeric conversion and operations.
## Conversion
### number(logical)
Convert boolean to number.
```javascript
number(true) // 1
number(false) // 0
```
### number(text, radix)
Parse text to number. Radix is 2-36 (default: 10).
```javascript
number("42") // 42
number("ff", 16) // 255
number("1010", 2) // 10
```
### number(text, format)
Parse formatted numbers.
| Format | Description |
|--------|-------------|
| `""` | Standard decimal |
| `"u"` | Underbar separator (1_000) |
| `"d"` | Comma separator (1,000) |
| `"s"` | Space separator (1 000) |
| `"v"` | European (1.000,50) |
| `"b"` | Binary |
| `"o"` | Octal |
| `"h"` | Hexadecimal |
| `"j"` | JavaScript style (0x, 0o, 0b prefixes) |
```javascript
number("1,000", "d") // 1000
number("0xff", "j") // 255
```
## Methods
### number.abs(n)
Absolute value.
```javascript
number.abs(-5) // 5
number.abs(5) // 5
```
### number.sign(n)
Returns -1, 0, or 1.
```javascript
number.sign(-5) // -1
number.sign(0) // 0
number.sign(5) // 1
```
### number.floor(n, place)
Round down.
```javascript
number.floor(4.9) // 4
number.floor(4.567, 2) // 4.56
```
### number.ceiling(n, place)
Round up.
```javascript
number.ceiling(4.1) // 5
number.ceiling(4.123, 2) // 4.13
```
### number.round(n, place)
Round to nearest.
```javascript
number.round(4.5) // 5
number.round(4.567, 2) // 4.57
```
### number.trunc(n, place)
Truncate toward zero.
```javascript
number.trunc(4.9) // 4
number.trunc(-4.9) // -4
```
### number.whole(n)
Get the integer part.
```javascript
number.whole(4.9) // 4
number.whole(-4.9) // -4
```
### number.fraction(n)
Get the fractional part.
```javascript
number.fraction(4.75) // 0.75
```
### number.min(...values)
Return the smallest value.
```javascript
number.min(3, 1, 4, 1, 5) // 1
```
### number.max(...values)
Return the largest value.
```javascript
number.max(3, 1, 4, 1, 5) // 5
```
### number.remainder(dividend, divisor)
Compute remainder.
```javascript
number.remainder(17, 5) // 2
```

112
docs/library/object.md Normal file
View File

@@ -0,0 +1,112 @@
# object
The `object` function and related utilities handle object creation and manipulation.
## Creation
### object(obj)
Shallow copy an object.
```javascript
var copy = object(original)
```
### object(obj, another)
Combine two objects.
```javascript
object({a: 1}, {b: 2}) // {a: 1, b: 2}
object({a: 1}, {a: 2}) // {a: 2}
```
### object(obj, keys)
Select specific keys.
```javascript
object({a: 1, b: 2, c: 3}, ["a", "c"]) // {a: 1, c: 3}
```
### object(keys)
Create object from keys (values are `true`).
```javascript
object(["a", "b", "c"]) // {a: true, b: true, c: true}
```
### object(keys, value)
Create object from keys with specified value.
```javascript
object(["a", "b"], 0) // {a: 0, b: 0}
```
### object(keys, fn)
Create object from keys with computed values.
```javascript
object(["a", "b", "c"], (k, i) => i) // {a: 0, b: 1, c: 2}
```
## Prototypes
### meme(prototype)
Create a new object with the given prototype.
```javascript
var animal = {speak: function() { log.console("...") }}
var dog = meme(animal)
dog.speak = function() { log.console("woof") }
```
### proto(obj)
Get an object's prototype.
```javascript
var p = proto(dog) // animal
```
### isa(obj, prototype)
Check if prototype is in the chain.
```javascript
isa(dog, animal) // true
```
## Serialization
### splat(obj)
Flatten an object's prototype chain into a plain object. Only includes primitive types (numbers, text, booleans, arrays, objects).
```javascript
var base = {x: 1}
var derived = meme(base)
derived.y = 2
splat(derived) // {x: 1, y: 2}
```
When sending objects between actors with `$send`, they are automatically splatted.
## Key Iteration
```javascript
var obj = {a: 1, b: 2, c: 3}
// Get all keys
var keys = array(obj) // ["a", "b", "c"]
// Iterate
for (var key in obj) {
log.console(key, obj[key])
}
```

71
docs/library/random.md Normal file
View File

@@ -0,0 +1,71 @@
# random
Random number generation.
```javascript
var random = use('random')
```
## Functions
### random.random()
Returns a number between 0 (inclusive) and 1 (exclusive).
```javascript
random.random() // e.g., 0.7234...
```
### random.random_fit()
Returns a random 56-bit integer in the range -36028797018963968 to 36028797018963967.
```javascript
random.random_fit() // e.g., 12345678901234
```
### random.random_whole(max)
Returns a whole number from 0 (inclusive) to max (exclusive).
```javascript
random.random_whole(10) // 0-9
random.random_whole(100) // 0-99
random.random_whole(6) + 1 // dice roll: 1-6
```
## Examples
```javascript
var random = use('random')
// Random boolean
var coin_flip = random.random() < 0.5
// Random element from array
function pick(arr) {
return arr[random.random_whole(length(arr))]
}
var colors = ["red", "green", "blue"]
var color = pick(colors)
// Shuffle array
function shuffle(arr) {
var result = array(arr) // copy
for (var i = length(result) - 1; i > 0; i--) {
var j = random.random_whole(i + 1)
var temp = result[i]
result[i] = result[j]
result[j] = temp
}
return result
}
// Random in range
function random_range(min, max) {
return min + random.random() * (max - min)
}
var x = random_range(-10, 10) // -10 to 10
```

123
docs/library/text.md Normal file
View File

@@ -0,0 +1,123 @@
# text
The `text` function and its methods handle string conversion and manipulation.
## Conversion
### text(array, separator)
Convert an array to text, joining elements with a separator (default: space).
```javascript
text([1, 2, 3]) // "1 2 3"
text([1, 2, 3], ", ") // "1, 2, 3"
text(["a", "b"], "-") // "a-b"
```
### text(number, radix)
Convert a number to text. Radix is 2-36 (default: 10).
```javascript
text(255) // "255"
text(255, 16) // "ff"
text(255, 2) // "11111111"
```
### text(text, from, to)
Extract a substring from index `from` to `to`.
```javascript
text("hello world", 0, 5) // "hello"
text("hello world", 6) // "world"
```
## Methods
### text.lower(text)
Convert to lowercase.
```javascript
text.lower("HELLO") // "hello"
```
### text.upper(text)
Convert to uppercase.
```javascript
text.upper("hello") // "HELLO"
```
### text.trim(text, reject)
Remove characters from both ends. Default removes whitespace.
```javascript
text.trim(" hello ") // "hello"
text.trim("xxhelloxx", "x") // "hello"
```
### text.search(text, target, from)
Find the position of `target` in `text`. Returns `null` if not found.
```javascript
text.search("hello world", "world") // 6
text.search("hello world", "xyz") // null
text.search("hello hello", "hello", 1) // 6
```
### text.replace(text, target, replacement, limit)
Replace occurrences of `target` with `replacement`.
```javascript
text.replace("hello", "l", "L") // "heLLo"
text.replace("hello", "l", "L", 1) // "heLlo"
// With function
text.replace("hello", "l", function(match, pos) {
return pos == 2 ? "L" : match
}) // "heLlo"
```
### text.format(text, collection, transformer)
Substitute `{key}` placeholders with values from a collection.
```javascript
text.format("Hello, {name}!", {name: "World"})
// "Hello, World!"
text.format("{0} + {1} = {2}", [1, 2, 3])
// "1 + 2 = 3"
```
### text.normalize(text)
Unicode normalize the text (NFC form).
```javascript
text.normalize("café") // normalized form
```
### text.codepoint(text)
Get the Unicode codepoint of the first character.
```javascript
text.codepoint("A") // 65
text.codepoint("😀") // 128512
```
### text.extract(text, pattern, from, to)
Match a pattern and extract named groups.
```javascript
text.extract("2024-01-15", /(\d+)-(\d+)-(\d+)/)
// Returns match info
```

116
docs/library/time.md Normal file
View File

@@ -0,0 +1,116 @@
# time
The time module provides time constants and conversion functions.
```javascript
var time = use('time')
```
## Constants
| Constant | Value | Description |
|----------|-------|-------------|
| `time.second` | 1 | Seconds in a second |
| `time.minute` | 60 | Seconds in a minute |
| `time.hour` | 3600 | Seconds in an hour |
| `time.day` | 86400 | Seconds in a day |
| `time.week` | 604800 | Seconds in a week |
| `time.month` | 2629746 | Seconds in a month (30.44 days) |
| `time.year` | 31556952 | Seconds in a year (365.24 days) |
## Getting Current Time
### time.number()
Get current time as seconds since epoch.
```javascript
var now = time.number() // e.g., 1702656000
```
### time.record()
Get current time as a record.
```javascript
var now = time.record()
// {year: 2024, month: 1, day: 15, hour: 10, minute: 30, second: 45, nanosecond: 123456789}
```
### time.text(format)
Get current time as formatted text.
```javascript
time.text() // default format
time.text("yyyy-MM-dd HH:mm:ss.SSS") // custom format
```
## Converting Time
### time.number(text, format, zone)
Parse text to timestamp.
```javascript
time.number("2024-01-15", "yyyy-MM-dd")
```
### time.number(record)
Convert record to timestamp.
```javascript
time.number({year: 2024, month: 1, day: 15})
```
### time.text(number, format, zone)
Format timestamp as text.
```javascript
time.text(1702656000, "yyyy-MM-dd") // "2024-01-15"
```
### time.record(number)
Convert timestamp to record.
```javascript
time.record(1702656000)
// {year: 2024, month: 1, day: 15, ...}
```
## Time Arithmetic
```javascript
var now = time.number()
// Tomorrow at this time
var tomorrow = now + time.day
// One week ago
var last_week = now - time.week
// In 2 hours
var later = now + (2 * time.hour)
// Format future time
log.console(time.text(tomorrow))
```
## Example
```javascript
var time = use('time')
// Measure execution time
var start = time.number()
// ... do work ...
var elapsed = time.number() - start
log.console(`Took ${elapsed} seconds`)
// Schedule for tomorrow
var tomorrow = time.number() + time.day
log.console(`Tomorrow: ${time.text(tomorrow, "yyyy-MM-dd")}`)
```

193
docs/packages.md Normal file
View File

@@ -0,0 +1,193 @@
# Packages
Packages are the fundamental unit of code organization and sharing in Cell.
## Package Structure
A package is a directory containing a `cell.toml` manifest:
```
mypackage/
├── cell.toml # package manifest
├── main.ce # entry point (optional)
├── utils.cm # module
├── helper/
│ └── math.cm # nested module
├── render.c # C extension
└── _internal.cm # private module (underscore prefix)
```
## cell.toml
The package manifest declares metadata and dependencies:
```toml
package = "mypackage"
version = "1.0.0"
[dependencies]
prosperon = "gitea.pockle.world/john/prosperon"
mylib = "/Users/john/work/mylib"
```
### Fields
- **package** — canonical package name
- **version** — semantic version
- **dependencies** — map of alias to package locator
## Module Resolution
When importing with `use()`, Cell searches in order:
1. **Local package** — relative to package root
2. **Dependencies** — via aliases in `cell.toml`
3. **Core** — built-in Cell modules
```javascript
// In package 'myapp' with dependency: renderer = "gitea.pockle.world/john/renderer"
use('utils') // myapp/utils.cm
use('helper/math') // myapp/helper/math.cm
use('renderer/sprite') // renderer package, sprite.cm
use('json') // core module
```
### Private Modules
Files starting with underscore are private:
```javascript
// _internal.cm is only accessible within the same package
use('internal') // OK from same package
use('myapp/internal') // Error from other packages
```
## Package Locators
### Remote Packages
Hosted on Gitea servers:
```
gitea.pockle.world/user/repo
```
### Local Packages
Absolute filesystem paths:
```
/Users/john/work/mylib
```
Local packages are symlinked into the shop, making development seamless.
## The Shop
Cell stores all packages in the **shop** at `~/.cell/`:
```
~/.cell/
├── packages/
│ ├── core -> gitea.pockle.world/john/cell
│ ├── gitea.pockle.world/
│ │ └── john/
│ │ ├── cell/
│ │ └── prosperon/
│ └── Users/
│ └── john/
│ └── work/
│ └── mylib -> /Users/john/work/mylib
├── lib/
│ ├── local.dylib
│ └── gitea_pockle_world_john_prosperon.dylib
├── build/
│ └── <content-addressed cache>
├── cache/
│ └── <downloaded zips>
├── lock.toml
└── link.toml
```
### lock.toml
Tracks installed package versions:
```toml
[gitea.pockle.world/john/prosperon]
type = "gitea"
commit = "abc123..."
updated = 1702656000
```
### link.toml
Development links override package resolution:
```toml
[gitea.pockle.world/john/prosperon]
target = "/Users/john/work/prosperon"
```
## Installing Packages
```bash
# Install from remote
cell install gitea.pockle.world/john/prosperon
# Install from local path
cell install /Users/john/work/mylib
```
## Updating Packages
```bash
# Update all
cell update
# Update specific package
cell update gitea.pockle.world/john/prosperon
```
## Development Workflow
For active development, link packages locally:
```bash
# Link a package for development
cell link add gitea.pockle.world/john/prosperon /Users/john/work/prosperon
# Changes to /Users/john/work/prosperon are immediately visible
# Remove link when done
cell link delete gitea.pockle.world/john/prosperon
```
## C Extensions
C files in a package are compiled into a dynamic library:
```
mypackage/
├── cell.toml
├── render.c # compiled to mypackage.dylib
└── render.cm # optional Cell wrapper
```
The library is named after the package and placed in `~/.cell/lib/`.
See [Writing C Modules](c-modules.md) for details.
## Platform-Specific Files
Use suffixes for platform-specific implementations:
```
mypackage/
├── audio.c # default implementation
├── audio_playdate.c # Playdate-specific
└── audio_emscripten.c # Web-specific
```
Cell selects the appropriate file based on the build target.

View File

@@ -1,77 +0,0 @@
# Programs: Programs and Modules
Prosperon organizes your code into two broad categories: **modules** and **programs**. Modules are used to extend programs with new functionality, while programs are used to spawn actors.
## Modules
A **module** is any file that returns a single value. This return value is commonly an object, but it can be any data type (string, number, function, etc.). Once a module returns its value, Prosperon **freezes** that value, preventing accidental modification. The module is then cached so that subsequent imports of the same module dont re-run the file—they reuse the cached result.
### Importing a Module
Use the built-in `use` function to import a module by file path (or by name if resolvable via Prosperons path settings). For example:
```
var myModule = use('scripts/modules/myModule')
```
`use('module')` returns the **exact** same object if called multiple times, since modules are cached and not re-run.
Dull based modules are resolved by searching for them from the `prosperon.PATH` array. Engine modules are stored under `scripts/modules`, which is already added to the PATH for you.
Prosperon can also load C based modules. If two modules have the same path resolution, the C based library will be imported.
## Programs
An **program** is a file that **does not** return a value. Instead, the files contents run top to bottom as soon as the program is spawned. Programs are your games “live” scripts: each program can hold its own state and logic, spawn sub-programs, schedule timed tasks, and eventually **kill** itself (or be killed) when its done.
### Program Intrinsic Functions
Certain functions are intrinsic to the program and cannot be overridden. Theyre assigned to each new program instance at spawn time:
1. **`spawn(script, config, callback)`**
Creates (spawns) a new program from another script file.
- **`script`**: Path to the program script (a file containing statements, not returning anything).
- **`config`**: Optional object of extra properties to assign to the new program.
- **`callback(underling, info)`**: Optional function invoked right after the program is instantiated but before it fully initializes.
The newly spawned program:
- Receives a reference to its parent (the `overling`) and can store child programs (the `underlings`).
- Automatically calls `awake()` if that function is defined, after basic setup completes.
- Registers any recognized event handlers (like `update`, `draw`, etc.) if they exist.
2. **`kill()`**
Destroys the program, all of its timers, and recursively kills any underling (child) programs. If the program has a parent, it is removed from the parents `underlings` set.
3. **`delay(fn, seconds)`**
Runs the given function `fn` after `seconds`. This is implemented under the hood with a timer that automatically clears itself once it fires.
- **Example**:
```js
this.delay(_ => {
log.console("3 seconds later!")
}, 3)
```
4. **`clear()`**
Recursively kills all child programs, clearing your immediate `underlings` set. This is not called automatically. You can use it to manually clean up all children without necessarily killing the program itself.
### The program Lifecycle
Specific hooks can be set on a program when it is initialized.
- **Awake**: If the new program defines `awake()`, Prosperon calls it after the script finishes its top-level execution. This is a common place to do initialization.
- **Garbage**: When the program is killed, if it has a `garbage()` function, Prosperon calls it before final removal.
- **Then**: If the program has a `then()` function, Prosperon calls it at the very end of the kill process, allowing any final statements after your `garbage()` logic completes.
- **Registration**: In addition, if the object has **any** function named the same thing as a hook created with **prosperon.on**, that function will be registered with it after initialization.
### Overlings and Underlings
Programs have access to its creator and other programs created underneath it, termed its overling and underlings.
- **`this.overling`** is the parent program that spawned the current one.
- **`this.underlings`** is a set of child programs that the current program has spawned.
Killing a parent automatically kills all of its underlings, which in turn can kill their own underlings, and so on.
## Program Documentation
Prosperon includes a module called `doc.js` which helps generate documentation for your modules and programs. Any function and value can be assigned a docstring, and prosperon will then be able to generate documentation for it via doc.js. Look under the module API for more info.

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>Prosperon</string>
<key>CFBundleIdentifier</key>
<string>pockle.world.prosperon</string>
<key>CFBundleName</key>
<string>Prosperon</string>
<key>CFBundleVersion</key>
<string>0.5</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2024 Pockle World. All rights reserved.</string>
</dict>
</plist>

View File

@@ -1,164 +0,0 @@
JAVASCRIPT VISION
I see objects as being a sort of combination of a lisp cell and a record: symbols, which are used internally, and are private and non iterable, and record string values, which are iterable, readable, and writable; of course everything becomes locked in when stone.
CELLSCRIPT
Javascript to its core. Objects. What does the language need? It can be quite small, I think. The key is, ANYTHING that we want to be fast and JIT'd, must be present. So, record lookups. These are actually quicker in a jit'd language that have them as a feature. Most things should be libraries. Blobs need to be in the runtime.
## Actors and Objects
Actors have a unique memory space and are made up of many objects. Objects are created in the Self style, but with a limitation: only one parent.
Actors only communicate with messages. Messages are a record of data consisting of a few base types: text, numbers, arrays, records, boolean values. There is no RPC, and it is not recommended to build it into your message passing protocol. Messages are very high level things: "do X", which the actor can then go and carry out.
Cell provides a fast way to condense an object for sending.
## How is it different from Javascript?
Cell condenses Javascript down into a few core ideas. There are three pillars which cell relies on:
1. The idea of actors as a method of communication between parts of a program.
2. The idea of objects as a way to organize and encapsulate data.
3. The idea of the capability model as security.
Javascript already supplied some of these things; Cell takes the core of Javascript and makes these ideas more explicit, and layers on the actor communication. It removes some goofy suckiness with javascript.
It acts as something like an operating system at the application level. It allows random code to be ran on your machine without worrying it will break something. This is built into the language.
It is completly dynamically typed. In comparison with C, in C, you can treat everything as everything: it is almost not typed at all. If you try to use a type as another type, no error is thrown; it might work, but it mightly silently not work. In Cell, data has a hard type, but if you use it "incorrectly", it will throw, and you can correct it. It's a live system.
Cell is linked very closely with C. It's best to think of cell as a layer for message passing on top of C. It is a way to describe how to translate C tasks from one section of the program to another - or to totally different computers (actors).
As such, cell's primary duty is marshalling data; so it has been designed for that to be as fast as possible. It has a syntax similar to C to make it easy to translate formulae from cell to C (or the other way, if desired).
Unlike many actor languages, Cell does not eschew assignment. You must have some assignment. However, when it comes to actor->actor communication, you do not assign. RPC is too direct: one actor should not care all that much what specific functions another actor has available. It should request it to do something, and get a result, or possibly not get a result. It doesn't care what the actor does as long as that gets done.
But within itself, it will assign; it must. Actors, or cells, are best thought of as computers or nodes within the internet. You request data from a URL by typing it into your browser; that computer you're attempting to reach may not even be on. It very likely has written some other data to disk whenever you contact it. But you're not doing the specific assigning. You just request data with HTTP commands.
## Objects and actors
Objects and actors are both similar ideas: they can hold data and respond to messages. Objects, local to an actor, can be thought of more like an RPC idea: they're invoked and return immediately. However, a failed RPC can crash an object; and in that case, the actor halts. It can be corrected.
## What does Cell bring you over C?
Programs which are built with C; they're built statically; they're built to not crash; they're built doing extremely low level things, like assignment.
The goal of cell is to thrust your C code into the parallel, actor realm. It lets your code crash and resume it; even rewriting the C code which is butressing your cell code and reloading it live.
There are two primary sorts of Cell modules you create from C code: data and IO. C code like
Where there were two similar things in javscript, one has been deleted and one kept. For example, there is only null now, no undefined. There are not four ways to test for equality; there is one.
The purpose of this is to be a great language for passing messages. So it should be fast at creating records first and foremost, and finding items on them. So it needs first class, jitt'd records.
Finally, it needs to use less memory. Deleting a bunch of this stuff should make that simpler.
What is present?
Objects, prototypes, numbers, arrays, strings, true, false, null.
Things to do:
merge typeof and instanceof. Misty has array? stone? number? etc; it needs to be generic. 5 is number returns true.
No new operator. It's the same idea though: simply instead of 'var guy = new sprite({x,y})' you would say 'var guy = sprite({x,y})', and sprite would simply be a function written to return a sprite object.
One number type. Dec64. Numeric stack can be added in later: a bigint library, for example, built inside cell.
Simplify the property attributes stuff. It is simple: objects have text keys and whatever values. Objects can also have objects as values. These work like symbols. You can share them, if desired. No well known symbols exist to eliminate that much misdirection. Obejcts basically work like private keys. If you serialize an object, objects that are keys are not serialized; only textual keys are. You can do something about it with a json() method that is invoked, if you desire. You cannot retrieve
var works like let; use var instead of let
no const
Function closures and _ => all work the same and close over the 'this' variable
Totally delete modules, coroutines, generators, proxy .. this deletes a lot of the big switch statement
Add the 'go' statement for tail calls
Add the 'do' statement
Implementation detail: separate out arrays and objects. They are not the same. Objects no longer need to track if they're fast arrays or not. They're not. Arrays are. Always.
Add the functional proxy idea. Log will be implemented through that.
Remove ===; it's just == now, and !=.
Remove 'continue'; now, break handles both. For a do statement, label it, and break to that label; so
var x = 0
do loop {
x++
if (x < 5) break loop // goes back to loop
break // exits loop
}
rename instanceof to 'is'
remove undefined; all are 'null' now
remove 'delete'; to remove a field, assign it to null
remove with
Remove Object. New records have a prototype of nothing. There are no more 'type prototypes' at all.
Arrays are their own type
Remove property descriptors. Properties are always settable, unless the object as a whole is stone. Stone is an object property instead of a shape property.
Syntax stuff .. would like to invoke functions without (). This can effectively simulate a "getter". Make ? and all other characters usable for names. No reserve words, which are endlessly irritating.
----
This will all actually come about gradually. Add a few things at a time, fix up code that did not adhere. For a lot of this, no new functions will even need to be written; it's a matter of not calling certain functions that are no longer relevant, or calling different functions when required.
## Benchmarks to implement
### general speed
binarytrees
coro-prime-sieve
edigits
fannkuch-redux
fasta
http-server
json serialize/deserialize
knucleotide
lru
mandelbrot
merkletrees
nbody
nsieve
pidigits
regex-redux
secp256k1
spectral-norm
### function calling and recursion stress - test goto
naive recursive fibonacci [fib(35) or fib(40)]
tak
ackermann
### numeric
sieve of eratosthenes [10^7 bits]
spectral norm [5500 x 5500 matrix]
n-body sim [50 000 - 100 000 steps]
mandelbrot [1600x1200 image, max iter = 50]
### memory & gc torture
binary trees [depth 18 (~500 000 nodes)]
richards task scheduler
fannkuch redux [n=11 or 12]
### dynamic object & property access
deltablue constraint solver
splay tree [256k nodes]
json, wota, nota decode->encode [use 2MB example]
### string / regex kernels
regex-DNA
fasta
word-frequency
### concurrency/message passing
ping-pong [two actors exhange a small record N times, 1M messages end to end]
chameneos [mating color swap game w/ randezvous]
For all, track memory and time.

View File

@@ -1,57 +0,0 @@
# Cell
Cell is an actor system, intended to make it easy to parallelize programs.
Programs are written in cell, but cell also makes it trivial to interface with C code.
## Packages
Each cell package has a .cell directory at its root. .ce files within this directory can be launched as cell actors, and .cm files can be imported with the 'use' statement.
### File search
When a file is imported, cell will search the following locations:
- The current package
- Imported packages
- The cell standard library
Files leading with an underscore are not available to import from a package (but are OK for within a package)
For example, consider this project:
prosperon/
prosperon/sprite.cm
prosperon/render.cm
prosperon/_help.cm
prosperon/render.c
prosperon/ui/
prosperon/ui/button.cm
sprite.cm can use(render); but an outside package must use(prosperon/render). sprite can use(help), but an outside package cannot use(prosperon/help).
A file like render.c above will be compiled into a dynamic library for the target platform (ie, on macos a .dylib), named the name of the package, 'prosperon.dylib'. For a local package, it will be local.dylib.
In the case above, the js_prosperon_render_use function will be found in the prosperon.dylib, and the result of that will be passed to render.cm as 'this', allowing for trivial extension.
If a C function OR a .cm file is found for a particular import, searching stops, preventing, for example, accidentally applying a .cm file from a local find to a package that has one which is similarly named.
### Importing packages
Each package has a list of packages it depends on. They can use their own naming convention for them. Packages are imported from URLs, so, gitea.pockle.world/john/prosperon imports 'prosperon'; but, when importing, you can say, 'renderer = gitea.pockle.world/john/prosperon', and that means you can use(renderer/sprite), etc.
## Modules
Modules have a .cm extension. Modules return a single value.
## Programs
Programs have a .ce extension. Programs do not return a value.
## The scripting language
Cell is basically javascript, but with some key modifications:
- blobs instead of arraybuffers, which are written and read bit by bit
- All equivalences are hard; there is only != and ==, and they do what !== and === do in javascript
- There is no undefined, only null
- 'var' acts like let, and 'def' acts like const; there is no let and const
- logging is done with the global 'log' object; log.console outputs to console, log.error outputs an error, etc.
- All numbers are dec64, and are all therefore exact; no floating point rounding errors.
- All closures act like _ =>, and close over the 'this' variable
- Arrays are distinct from objects
- There is no 'delete' operator, just assign to null
- There is no 'with'
- There are no property descriptors, objects are more like records that can hold functions, numbers, anything.
- stone() makes an object immutable, forever

View File

@@ -1,103 +0,0 @@
# Cell structure
Cell doesn't have the notion of a file system; it has packages. Each package is a directory with a cell.toml file. You cannot have nested packages.
A package is a collection of actors, modules, and other files.
Packages are stored in a cell shop, in a development environment, located at ~/.cell.
Inside the shop there is .cell/packages/<each package>
A package can be a gitea url, like gitea.pockle.world/john/prosperon, which effectively clones to ~/.cell/packages/gitea.pockle.world/john/prosperon
Or it can be a local absolute path (on computers with a file system), like .cell/packages/User/john/work/prosperon, which is a symlink to the actual directory.
Cell itself is stored as a package in .cell/packages/<core>, where <core> is specified in shop.toml (e.g., `core = "bootstrap"` for local development, or `core = "gitea.pockle.world/john/cell"` for a remote package). Updating cell involves getting cell from somewhere, and rebuilding it. The cell core is itself a package.
When an actor or module is requested, it's loaded first from the package, if not found, it checks in packages (via aliases in cell.toml dependencies), and if not found there, it checks in the core package.
Packages can declare aliases for packages, so the "accio" package can say "prosperon = gitea.pockle.world/john/prosperon", and then load "prosperon/sprite".
A module in prosperon can simply load "sprite" to load its own sprite.
the "cell upgrade" command is a command to upgrade the cell version in a shop.
## commands
cell update - update all packages in the shop, and builds their code for local use
cell install <package> - install a package
cell remove <package> - remove a package
cell build - build the package in the current directory into a cell executable
cell version - the version of cell being used
## building
cell builds itself from source on your compuer!
all source for every single part of a cell program are located in the cell shop.
the cell shop looks like this:
.cell
packages
core <--- this is the core cell
gitea.pockle.world/john/cell <---- core contents. this is linked to core
gitea.pockle.world/john/prosperon
cell.toml <--- the manifest of the package
mod1.cm
mod2.cm
actor1.ce
addon.c <--- a C file for compiling into the cell executable
etc
gitea.pockle.world/john/accio
/Users/john/work/prosperon
...
link.toml - temporary links for this cell shop
lock.toml - manifest of installed packages
lib
<dynamic libraries>
cache
<downloaded packages>
build - content addressed hash
fajfola214o12
90823tjoag
...
## getting a module or actor
When a module <name/mod> is requested, from a within package <a> ..
1) cell looks within package <a> for a folder <name> and loads <name/mod>, if present
2) cell looks for a package <name> with a top level <mod>
3) cell looks in the core for <name/mod>
The core can be set and have something else linked into it, if you want to break your cell build
## cell build
cell build compiles all c into the appropriate binaries. All object files are stored in the .cell/cache
there are two ways to build a cell program: as a shared library, or as a static binary.
Cell script files are simply compiled into built objects
Then, all C files in the package are compiled.
If you have a file like fd.c and fd_playdate.c, that is a signal to cell to compile fd.c usually, but then for the playdate target, compile fd_playdate.c instead.
files name "main.c" are not compiled.
each cell.toml in a package
### shared library
this is more suitable for development. firstly, the cell core must build a shared library for the platform.
Then, each package compiles, linking to the cell core shared library. Modules are written to include the cell core headers.
Shared libraries are stored in the build directory, hashed with the content hash
When update is run, it copies the dynamic libraries into .cell/shared, with their package name; ie, the dynamic library for the gitea.pockle.world/john/prosperon would be at .cell/shared/gitea_pockle_world_john_prosperon.dylib.
There will be commands to gather them all together; so you can request "all dylibs for platform x", and it will give you a folder of them.
### bootstrapping cell
after the cell shared libraries are all compiled, the main.c of the cell core is compiled against the cell core shared library, creating a thin cell runner. However, even on subsequent updates to cell, this runner is not updated; only for bootstrapping.
### static binary
For static binaries, rather than going through and compiling each dynamic library for a package, a static binary is created for a particular package, based on its dependencies. All C symbols, plus the main.c runner from the cell core, are packaged into one binary. It works just like the basic cell program.
However, you can also specify an entry point for this binary, for example, a particular actor. When the binary is run, that particular actor runs.

View File

@@ -1,32 +0,0 @@
Prosperon Commercial License
Last Updated: February 11, 2025
Grant of License
Upon purchasing a Prosperon license, Pockle World LLC (“Licensor”) grants you (“Licensee”) a non-exclusive, worldwide, commercial license to use the Prosperon game engine (the “Software”) to develop, publish, and distribute one or more video game or interactive software products (the “Products”).
Closed-Source Restriction
The Software is currently closed-source. Licensee shall not redistribute or disclose the Prosperon engine source code, in whole or in part, unless expressly permitted by Pockle World LLC.
Permitted Usage
Licensee may create and distribute compiled or packaged Products that incorporate or depend upon the Software, for commercial or non-commercial purposes, without owing royalties or additional fees to Licensor (beyond the initial purchase).
Ownership and Copyright
The Software is owned by Pockle World LLC. All rights not expressly granted in this License are reserved by Pockle World LLC.
Modifications
You may modify the scripts of your own Projects, but you may not share or distribute any modifications to the Prosperon engines proprietary binaries, libraries, or source code itself.
No Warranty
THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
Limitation of Liability
IN NO EVENT SHALL LICENSOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE.
Term and Termination
This License shall remain in effect perpetually, unless you fail to comply with any term of this License. In such event, your rights under this License will terminate immediately without notice from Pockle World LLC.
Support and Updates
Purchase of the Software includes access to all updates through Prosperon v1.X. Additional support plans may be purchased separately.
Governing Law
This License shall be governed by and construed in accordance with the laws of the jurisdiction in which Pockle World LLC is located, without regard to conflict-of-law principles.

View File

@@ -1,42 +0,0 @@
## Hot Reloading
Hot reloading is of primary concern here. Cell does its best to hot reload modules when possible.
The key function can be used to create a stable key for a module, which can be used to hot reload modules and aid in data migration.
During hot reload ...
All var declarations are rebound, so old var -> new value
Def declarations are not rebound, but properties of objects may be adjusted.
Returned objects and functions are wrapped in a trampoline and adjusted. The original export shape is stable (same set of keys); you will get a warning if the shape changes, and you must reload the program to get the correct new shape.
Hot-reload binding rules
var bindings are rebound on hot reload
New evaluation replaces the old value.
Use var for:
tunables / config
behavior references
caches youre happy to discard
anything you expect to change when code changes
def bindings are never rebound
The binding identity persists across reloads.
Use def for:
long-lived state
prototypes / identity anchors
capability keys
registries you want to keep alive
def objects may have their methods patched
Hot reload is best-effort. You get the full benefit when modules export a stable API object and keep long-lived identity in def (protos/state), while keeping tunables in var and reading them at use-time; caching primitives or function references opts out of live updates.
A module is “hot-reload friendly” if its export is a function or an object (not a primitive).
On reload, the runtime:
Re-evaluates the module.
Rebinds all vars: old var binding becomes the new value from the new evaluation.
pasted
Keeps all def bindings (identity does not change), but:
patches function-valued fields on def objects in place to match new code (and any other fields you explicitly define as reloadable).
pasted
Patches the module export handle (runtime-owned, stone-to-userland) so existing importers keep the same identity, but calls/fields can reflect new code.
pasted
Checks export shape: if the export is an object, its set of text keys must match the previous version; otherwise warn and require full program reload.

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<copyright>Copyright (c) 2024 Pockle World. All Rights Reserved.</copyright>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
name="Prosperon"
version="0.4"
type="win32"
/>
<description>Prosperon Game Engine</description>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>

View File

@@ -1,16 +0,0 @@
101 ICON "icon.ico"
102 RT_MANIFEST "resource.manifest"
103 VERSIONINFO
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "Pockle World"
VALUE "FileDescription", "Prosperon Game Engine"
VALUE "FileVersion", "0.4.0"
VALUE "LegalCopyright", "Copyright 2024 Pockle World."
VALUE "ProductVersion", "0.4.0"
END
END
END

View File

@@ -1,152 +0,0 @@
# Graphics drawing
## Terminology
Texture - a set of bytes on the GPU, not directly accessible
Surface - a set of bytes in RAM, modifiable
rect - a rectangle of {x,y,width,height}
Image - combination of a texture and rect, where the rect defines the UV coordinates on the texture to draw
# Drawing, cameras, viewports, logical size, and so on
A camera is a view into the game world. A camera can be "rendered", which means it renders the world, and what it can see in the world. A camera may draw to a surface, or to the main window. Objects in the world will render so that if their position is equal to the camera position, that is in the center of the screen. HUD functions always render so [0,0] is the bottom left of the camera's view.
Cameras always draw to their own render target. Then, they draw that render target to the framebuffer.
# COORDINATES
Screen coordinates start in the upper left corner at [0,0] and extend to the bottom right, in pixels. Raw mouse coordinates are in these.
# RENDERING PIPELINE
In prosperon, you call graphics rendering functions at well defined hook points. These are interleaved as necessary with predefined created objects, like sprites, 3d world models, and so on.
The engine stores a command buffer. When you issue "draw" commands, they are recorded into the command buffer. These are batched as much as they can be; if there is no significant state change between, the draw commands can be coalesced into one. Then, for each camera, the draw commands are executed.
# RENDERING COMPONENTS
## MATERIALS
A material defines the inputs to a shader.
## PIPELINES
Pipelines are how the rendering engine is set up. Switching pipelines can be done for special effects.
## SPECIAL EFFECTS
Sometimes you want a special effect. While there are many objects in prosperon you can create and have the engine handle for you, a special effect typically requires a bit of code.
# LAYERS
All things that draw have a layer. If no layer is set, the implicit layer is "0". Even draw and hud functions have a layer. To draw a draw function on a specific layer, set that function's "layer". ie,
this.draw = function() { render.rect(); }
this.draw.layer = -5;
Now that layer will draw at the -5 layer.
# CAMERAS
Everything is drawn via cameras. Cameras can draw directly to the screen, or they can draw to an offscreen render target. By default, everything is drawn to all cameras. There will eventually be a tag that lets you filter what is drawn to specifc cameras.
Cameras have a resolution they draw at, "size".
## TEXTURES
Anatomy of rendpering an image render.image(path)
Path can be a file like "toad"
If this is a gif, this would display the entire range of the animation
It can be a frame of animation, like "frog.0"
If it's an aseprite, it can have multiple animations, like "frog.walk.0"
file^ frame^ idx
render.image("frog.walk.0",
game.image("frog.walk.0") ==> retrieve
image = {
texture: "spritesheet.png",
rect: [x,y,w,h],
time: 100
},
frames: {
toad: {
x: 4,
y: 5,
w: 10,
h: 10
},
frog: {
walk: [
{ texture: spritesheet.png, x: 10, y:10, w:6,h:6, time: 100 },
{ texture: spritesheet.png, x:16,y:10,w:6,h:6,time:100} <--- two frame walk animation
],
},
},
}
texture frog {
texture: {"frog.png"}, <--- this is the actual thing to send to the gpu
x:0,
y:0,
w:10,
h:10
},
## RENDER MODES
/* rendering modes
ps1
gouraud
diffuse // 16 bit color, 5-5-5
7 dynamic lights, 1 ambient
textures are affine
no vertex skinning
256x256 texture max (generally 128x128)
320x240, variable up to 640x480
n64
gouraud
diffuse
combiner // a secondary texture sometimes used to combine
7 dynamic lights, 1 ambient
320x240, or 640x480
sega saturn
gouraud
diffuse
320x240 or 640x480
ps2
phong
diffuse
combiner // second texture for modulation of diffuse
combine_mode // int for how to combine
dreamcast
phong
diffuse
combiner // second texture; could be an environment map, or emboss bump mapping
fog
640x480
640x448, special mode to 1280x1024
gamecube
phong
diffuse
+7 textures // wow!
8 dynamic lights
640x480
*/
/* meshes
position (float3)
color (rgba)
uv
*/
/* materials, modern pbr
any object can act as a "material". The engine expects some standardized things:
diffuse - base color texture
bump - a normal map for dot3 bump maping used in phong shading
height - a grayscale heightmap
occlusion - ambient occlusion texture
emission - texture for where model emits light
bump2 - a second normal map for detail
metallic - a metal/smoothness map
specular - specular map, alternative for the metallic workflow
*/

View File

@@ -1,186 +0,0 @@
# RENDERING PIPELINE
The basic flow for developing graphics here:
1) develop a render graph
2) decide what to draw
The render graph is the "big idea" of how the data flows through a render; inside the execution, you utilize "what to draw".
Prosperon provides you with functions to facilitate the creation of rendering pipelines. For example, you could use "shadow_vol" function to create buffer geometry with shadow volume data.
Unity has a "graphics.rendermesh" function that you can call, and that unity automatically calls for renderer components. It is the same here. But there are a handful of other types to draw, particularly for 2d.
## 2D
### Anatomy of a 2d renderer
Traditionally, 2d rendering is a mix of tilemaps and sprites. Today, it is still more cost effective to render tilemaps, but we have a lot more flexibility.
NES
Nes had 1 tilemap and up to 8 sprites per scanline.
SNES
Up to 4 tilemap backgrounds, with priority, and flipping capability. 32 sprites per scanline, and by setting the priority correctly, they could appear behind background layers.
GB
One background layer, 10 sprites per scanline/40 per frame.
GBA
Up to 4 layers, sprites with affine transforms!
DS
Up to 4 layers; many sprites; and a 3d layer!
Sega saturn
This and everything else with generic vertex processing could do as many background layers and sprites as desired. This is what you get with prosperon on most modern computers. For more limited hardware, your options become limited too!
### Prosperon rendering
Layers
Every drawable 2d thing has a layer. This is an integer that goes from -9223372036854775808 to 9223372036854775808.
!!! On hardware that supports only a limited number of layers, this value must go from 0 to (layer #).
Layer sort
Within a layer, objects are sorted based on a given criteria. By default, this is nothing, and the engine may reorder the draws to optimize for performance. Instead, you can choose to sort by their y axis position, for example.
Parallax
Layers can have a defined parallax value, set at the engine level. Anything on that layer will move with the provided parallax. Each layer has an implicit parallax value of "1", which means it moves "as expected". Below 1 makes it move slower (0 makes it not move at all), 2 makes it move twice as fast, etc.
Tilemaps
These are highly efficient and work just like tilemaps on old consoles. When you submit one of these to draw, Prosperon can efficientally cull what can't be seen by the camera. You can have massive levels with these without any concern for performance. A tilemap is all on its own layer.
Tiles can be flipped; and the entire tilemap can have an affine transformation applied to it.
Sprites each have their own layer and affine transform. Tilemaps are just like a large sprite.
In addition to all of this, objects can have a "draw" event, wherein you can issue direct drawing commands like "render.sprite", "render.text", "render.circle", and so on. This can be useful for special effects, like multi draw passes (set stencil -> draw -> revert stencil). In this case, it is the draw event itself with the layer setting.
## 3D
3d models are like 3d sprites. Add them to the world, and then the engine handles drawing them. If you want special effects, its "draw" command can be overridden.
As sprites and 3d models are sent to render, they are added to a list; sorted; and then finally rendered.
## THE RENDERER
## Fully scriptable
The render layer is where you do larger scale organizing. For example, for a single outline, you might have an object's draw method be the standard:
- draw the model, setting stencil
- draw a scaled up model with a single color
But, since each object doing this won't merge their outlines, you need a larger order solution, wherein you draw *all* models that will be outlined, and then draw *all* scaled up models with a single color. The render graph is how you could do that. The render graph calls draw and render functions; so with a tag system, you can essentially choose to draw whatever you want. You can add new shadow passes; whatever. Of course, prosperon is packed with some standard render graphs to utilize right away.
Each graphical drawing command has a specific pipeline. A pipeline is a static object that defines every rendering detail of a drawing command.
A drawing command is composed of:
- a model
- a material
- a pipeline
The engine handles sorting these and rendering them effectively. There exist helper functions, like "render.image" which will in turn create a material and use the correct model.
You execute a list of drawing commands onto a render target. This might be the computer screen; it might be an offscreen target.
The material's properties are copied into the shader on a given pipeline; they also can have extra properties like "castshadows", "getshadows", and so on.
An *image* is a struct {
texture: GPU texture
rect: UV coordinates
}
## 2D drawing commands
The 2d drawing commands ultimately interface with a VERY limited subset of backend knowledge, and so are easily adaptable for a wide variety of hardware and screen APIs.
The basic 2D drawing techniques are:
Sprite - arbitrarily blit a bitmap to the screen with a given affine transformation and color
Tiles - Uniform squares in a grid pattern, drawn all on a single layer
Text - Generates whatever is needed to display text wrapped in a particular way at a particular coordinate
Particles - a higher order construction
Geometry - programmer called for circles or any other arbitrary shape. Might be slow!
## Effects
An "effect" is essentially a sequence of render commands. Typically, a sprite draws itself to a screen. It may have a unique pipeline for a special effect. But it might also have an "effect", which is actually a sequence of draw instructions. An example might be an outline scenario, where the sprite draws a black version of it scaled 1.1x, and then draws with the typical pipeline afterwards.
## A frame
During a frame, the engine finds everything that needs rendered. This includes enabled models, enabled sprites, tilemaps, etc. This also includes programmer directions inside of the draw() and hud() functions.
This high level commands are culled down, accounting for off screen sprites, etc, into a more compact command queue. This command queue is then rendered in whichever way the backend sees fit. Each "command queue" maps roughly into a "render pass" in vulkan. Once you submit a command queue, the data is sorted, required data is uploaded, and a render pass draws it to the specified frame.
A command is kicked off with a "batch" command.
var batch = render.batch(target, clearcolor) // target is the target buffer to draw onto
target must be known when the batch starts because it must ensure the pipelines fed into it are compatible. If clearcolor is undefined, it does not erase what is present on the target before drawing. To disable depth, simply do not include a depth attachment in the target.
batch.draw(mesh, material, pipeline)
This is the most fundamental draw command you can do. In modern parlance, the pipeline sets up the GPU completely for rendering (stencil, blend, shaders, etc); the material plugs data into the pipeline, via reflection; the mesh determines the geometry that is drawn. A mesh defines everything that's needed to kick of a draw call, including if the buffers are indexed or not, the number of indices to draw, and the first index to draw from.
batch.viewport()
batch.sprite
batch.text // a text object. faster than doing each letter as a sprite, but less flexible
// etc
batch.render(camera)
Batches can be saved to be executed again and again. So, one set of batches can be created, and then drawn from many cameras' perspectives. batch.render must take a camera
Behind the scenes, a batch tries to merge geometry, and does reordering for minimum pipeline changes behind the scenes.
Each render command can use its own unique pipeline, which entails its own shader, stencil buffer setup, everything. It is extremely flexible. Sprites can have their own pipeline.
ULTIMATELY:::
This is a much more functional style than what is typically presented from graphics APIs. Behind the scenes these are all translated to OpenGL or whatever; being functional at this level helps to optimize.
IMPORTANT NOTE:
Optimization only happens at the object level. If you have two pipelines with the exact same characteristics, they will not be batched. Use the exact same pipeline object to batch.
## SCENARIOS
BLOOM BULLETS
You want to draw a background; some ships; and some bullets that have glow to them. This amounts to two ideas:
1) draw the background and ships
2) draw bullets to a texture
3) apply bloom on the bullet
4) draw bullets+bloom over the background and ships
Steps 1, and 2-3, can be done in parallel. They constitute their own command queues. When both are done, the composite can then happen.
var bg_batch = render.batch(surf1, camera);
bg_batch.draw(background)
bg_batch.draw(ships)
bg_batch.end()
var bullet_batch = render.batch(surf2, camera);
bullet_batch.draw(bullets)
bullet_batch.end()
var bloom = render.batch(surf3, postcam)
bloom.draw(bullet_batch.color, bloom_pipeline)
bloom.end()
var final = render.batch(swapchain)
final.draw(bg_batch.color)
final.draw(bloom.color)
final.end()
When 'batch.end' is called, it reorders as needed, uploads data, and then does a render pass.
3D GAME WITH DIRECTIONAL LIGHT SHADOW MAP
var shadow_batch = render.batch(shadow_surf, dir_T)
shadow_batch.draw(scene, depth_mat) // scene returns a list of non culled 3d obejcts; we force it to use depth_mat
shadow_batch.end()
base_mat.shadowmap = shadow_batch.color;
var main_batch = render.batch(swapchain, camera)
main_batch.draw(scene)
main_batch.end()
FIERY LETTERS
This pseudo code draws a "hello world" cutout, with fire behind it, and then draws the game's sprites over that
var main = render.batch(swapchain, 2dcam)
main.draw("hello world", undefined, stencil_pipeline)
main.draw(fire)
main.draw(fullscreen, undefined, stencil_reset)
main.draw(game)
main.end()

View File

@@ -980,6 +980,10 @@ delete globalThis.RegExp
// TODO: delete globalThis
var mem = js.calc_mem()
log.console(json.encode(mem))
log.console("total memory usage: " + text(mem.memory_used_size/1024) + " KB");
$_.clock(_ => {
var val = locator.symbol.call(null, $_, cell.args.arg);

7
mkdocs.yml Normal file
View File

@@ -0,0 +1,7 @@
site_name: Cell
repo_url: https://gitea.pockle.world/john/cell
docs_dir: docs
theme:
name: mkdocs
color_mode: auto