remove cell doc

This commit is contained in:
2025-11-29 23:35:10 -06:00
parent 1d295d11e5
commit acf842e2a1
19 changed files with 170 additions and 819 deletions

View File

@@ -329,6 +329,8 @@ cell = custom_target('cell',
install_headers('source/cell.h', 'source/jsffi.h')
install_headers('source/quickjs.h')
install_headers('source/qjs_macros.h')
install_headers('source/wota.h')
install_headers('source/qjs_wota.h')
tests = [
'spawn_actor',

View File

@@ -1,21 +1,14 @@
// cell compile
var os = use('os')
var io = use('cellfs')
var project_name = cell.config.project.name
if (!project_name) {
log.console("Error: project.name not found in cell.toml")
$_.stop()
}
log.console("Building project: " + project_name)
// 2. Prepare output directory
if (!io.exists('.cell')) {
io.mkdir('.cell')
}
var shop = use('shop')
var config = shop.load_config()
// 3. Find source files
var files = io.enumerate('.', true)
var objects = []
@@ -42,17 +35,34 @@ for (var i = 0; i < files.length; i++) {
}
objects.push(obj_file)
var needs_compile = true
if (io.exists(obj_file)) {
try {
var src_stat = io.stat(file)
var obj_stat = io.stat(obj_file)
if (obj_stat.modtime >= src_stat.modtime) {
needs_compile = false
}
} catch (e) {
needs_compile = true
}
}
log.console("Compiling " + file + " -> " + obj_file)
log.console(`export name is ${use_name}`)
// Compile command
// cc -fPIC -c <file> -O3 -DCELL_USE_NAME=<name> -o <obj_file>
var cmd = 'cc -fPIC -c ' + file + ' -O3 -DCELL_USE_NAME=' + use_name + ' -o ' + obj_file
var ret = os.system(cmd)
if (ret != 0) {
log.console("Compilation failed for " + file)
$_.stop()
if (!needs_compile) {
log.console("Skipping " + file + " (up to date)")
} else {
log.console("Compiling " + file + " -> " + obj_file)
log.console(`export name is ${use_name}`)
// Compile command
// cc -fPIC -c <file> -O3 -DCELL_USE_NAME=<name> -o <obj_file>
var cmd = 'cc -fPIC -c ' + file + ' -O3 -DCELL_USE_NAME=' + use_name + ' -o ' + obj_file
var ret = os.system(cmd)
if (ret != 0) {
log.console("Compilation failed for " + file)
$_.stop()
}
}
}
}
@@ -75,6 +85,10 @@ if (!io.exists('.cell/local')) {
var lib_name = '.cell/local/local' + lib_ext
log.console("Linking " + lib_name)
if (config.compilation) {
link_flags += ` ${config.compilation.LDFLAGS} `
}
var link_cmd = 'cc ' + link_flags + ' ' + objects.join(' ') + ' -lcell -o ' + lib_name
var ret = os.system(link_cmd)

View File

@@ -1,11 +1,22 @@
#include "cell.h"
#include "qjs_macros.h"
// Return the current stack depth.
JSC_CCALL(debug_stack_depth, return number2js(js,js_debugger_stack_depth(js)))
// Return a backtrace of the current call stack.
JSC_CCALL(debug_build_backtrace, return js_debugger_build_backtrace(js,NULL))
// Return the closure variables for a given function.
JSC_CCALL(debug_closure_vars, return js_debugger_closure_variables(js,argv[0]))
// Return the local variables for a specific stack frame.
JSC_CCALL(debug_local_vars, return js_debugger_local_variables(js, js2number(js,argv[0])))
// Return metadata about a given function.
JSC_CCALL(debug_fn_info, return js_debugger_fn_info(js, argv[0]))
// Return an array of functions in the current backtrace.
JSC_CCALL(debug_backtrace_fns, return js_debugger_backtrace_fns(js,NULL))
static const JSCFunctionListEntry js_debug_funcs[] = {

View File

@@ -1,42 +0,0 @@
var debug = this
debug.stack_depth[cell.DOC] = `Return the current stack depth.
:return: A number representing the stack depth.
`
debug.build_backtrace[cell.DOC] = `Build and return a backtrace of the current call stack.
:return: An object representing the call stack backtrace.
`
debug.closure_vars[cell.DOC] = `Return the closure variables for a given function.
:param fn: The function object to inspect.
:return: An object containing the closure variables.
`
debug.local_vars[cell.DOC] = `Return the local variables for a specific stack frame.
:param depth: The stack frame depth to inspect.
:return: An object containing the local variables at the specified depth.
`
debug.fn_info[cell.DOC] = `Return metadata about a given function.
:param fn: The function object to inspect.
:return: An object with metadata about the function.
`
debug.backtrace_fns[cell.DOC] = `Return an array of functions in the current backtrace.
:return: An array of function objects from the call stack.
`
debug.dump[cell.DOC] = `Return a string representation of a given object.
:param obj: The object to dump.
:return: A string describing the object's contents.
`
return this

View File

@@ -1,310 +0,0 @@
function docOf(obj, prop) {
var block = obj[cell.DOC];
if (!block) return '';
// 1) If `block` is a string, that's the entire doc for `obj`.
// If a sub-property is requested, we have nowhere to look → return ''.
if (typeof block == 'string') {
return prop ? '' : block;
}
// 2) Otherwise, if `block` is an object:
// (a) With no `prop`, return block.doc or block[cell.DOC].
// (b) If `prop` is given, look for doc specifically for that property (just one level).
if (typeof block == 'object') {
// 2a) No property → top-level doc
if (!prop) {
if (typeof block.doc == 'string') {
return block.doc;
}
if (typeof block[cell.DOC] == 'string') {
return block[cell.DOC];
}
return '';
}
// 2b) If a prop is requested → see if there's a doc string or object for that property
var subBlock = block[prop];
if (!subBlock) return '';
if (typeof subBlock == 'string') {
return subBlock;
}
if (typeof subBlock == 'object') {
if (typeof subBlock.doc == 'string') {
return subBlock.doc;
}
if (typeof subBlock[cell.DOC] == 'string') {
return subBlock[cell.DOC];
}
return '';
}
}
// If nothing matched, return empty.
return '';
}
function parseDocStr(docStr) {
if (!docStr) return [];
var lines = docStr.split('\n');
var paramRe = /^:param\s+([A-Za-z0-9_]+)\s*:\s*(.*)$/;
var returnRe = /^:return:\s*(.*)$/;
var descLines = [];
var paramLines = [];
var returnLines = [];
for (var i = 0; i < lines.length; i++) {
var line = lines[i].trim();
if (!line) {
// Keep empty lines in the desc section
descLines.push('');
continue;
}
var pm = paramRe.exec(line);
var rm = returnRe.exec(line);
if (pm) {
paramLines.push('**' + pm[1] + '**: ' + pm[2]);
paramLines.push('');
} else if (rm) {
returnLines.push('**Returns**: ' + rm[1]);
returnLines.push('');
} else {
descLines.push(line);
}
}
var final = [];
// Reassemble: description → params → returns
if (descLines.length) {
final.push.apply(final, descLines);
if (paramLines.length || returnLines.length) {
final.push('');
}
}
if (paramLines.length) {
final.push.apply(final, paramLines);
}
if (returnLines.length) {
// If there were param lines, ensure blank line before the returns
if (paramLines.length && returnLines[0] != '') {
final.push('');
}
final.push.apply(final, returnLines);
}
return final;
}
function writeDocFile(obj, title) {
var lines = [];
var docTitle = title || "Documentation";
lines.push('# ' + docTitle + '\n');
walkObject(obj, lines, 1, docTitle);
return lines.join('\n');
// If you had I/O, you'd write to file here
}
/**
* Documents the top-level object and each of its immediate properties,
* but does NOT recurse deeper than that.
*/
function walkObject(obj, lines, level, name) {
// Print top-level doc or fallback heading
var topDocStr = docOf(obj);
if (topDocStr) {
var docOut = parseDocStr(topDocStr);
if (docOut.length > 0) {
lines.push(docOut.join('\n') + '\n');
}
}
var propNames = Object.getOwnPropertyNames(obj);
for (var i = 0; i < propNames.length; i++) {
var prop = propNames[i];
if (prop == 'constructor') continue;
var desc = Object.getOwnPropertyDescriptor(obj, prop);
if (!desc) continue;
// Check if accessor property (getter/setter)
var hasGetter = typeof desc.get == 'function';
var hasSetter = typeof desc.set == 'function';
if (hasGetter || hasSetter) {
writeProperty(lines, obj, prop, desc, level);
continue;
}
// If it's a normal data property
if ('value' in desc) {
var val = desc.value;
// If it's a function, treat it like a method
if (typeof val == 'function') {
writeMethod(lines, obj, prop, val, level);
}
// If it's an object, just print doc for that object (no deep recursion)
else if (val && typeof val == 'object') {
writeSubObject(lines, obj, prop, val, level);
}
// Otherwise, it's a primitive or something else
else {
writeDataProperty(lines, obj, prop, val, level);
}
}
}
}
/**
* Write out a heading and doc for an object property
* (but do NOT recurse into that object's subproperties).
*/
function writeSubObject(lines, parentObj, prop, val, level) {
// E.g. "## someObject <sub>object</sub>"
var heading = '#'.repeat(level + 2) + ' ' + prop + ' <sub>object</sub>';
lines.push(heading + '\n');
// 1) If there's doc assigned specifically to the parent's doc block for `prop`
var docStr = docOf(parentObj, prop);
// 2) If no doc there, see if the object itself has top-level doc
if (!docStr) {
docStr = docOf(val);
}
var docOut = parseDocStr(docStr);
if (docOut.length > 0) {
lines.push(docOut.join('\n') + '\n');
}
}
function writeProperty(lines, parentObj, prop, desc, level) {
// E.g. "## myProp <sub>accessor</sub>"
var heading = '#'.repeat(level + 2) + ' ' + prop + ' <sub>accessor</sub>';
lines.push(heading + '\n');
var hasGetter = typeof desc.get == 'function';
var hasSetter = typeof desc.set == 'function';
if (hasGetter && !hasSetter) {
lines.push('(read only)\n');
} else if (!hasGetter && hasSetter) {
lines.push('(set only)\n');
}
// Look up doc in parent's doc block first, or in the accessor functions
var docStr = docOf(parentObj, prop);
if (!docStr && hasGetter) {
docStr = docOf(desc.get) || docStr;
}
if (!docStr && hasSetter) {
docStr = docOf(desc.set) || docStr;
}
var docOut = parseDocStr(docStr);
if (docOut.length > 0) {
lines.push(docOut.join('\n') + '\n');
}
}
function writeMethod(lines, parentObj, prop, fn, level) {
// Pull doc from the function or from the parent's doc block
var docStr = fn[cell.DOC] || docOf(parentObj, prop) || '';
var paramNames = [];
// Try to extract param names from the doc
if (docStr) {
var docLines = docStr.split('\n');
var paramRe = /^:param\s+([A-Za-z0-9_]+)\s*:\s*(.*)$/;
for (var j = 0; j < docLines.length; j++) {
var pm = paramRe.exec(docLines[j]);
if (pm) paramNames.push(pm[1]);
}
}
// E.g. "## myFunction(param1, param2) <sub>function</sub>"
var heading = '#'.repeat(level + 2) + ' ' + prop;
if (paramNames.length > 0) {
heading += '(' + paramNames.join(', ') + ')';
} else {
// As a fallback, parse function signature
var m = fn.toString().match(/\(([^)]*)\)/);
if (m && m[1].trim()) {
heading += '(' + m[1].trim() + ')';
} else {
// If no params at all (or we cannot detect them), still add ()
heading += '()';
}
}
heading += ' <sub>function</sub>';
lines.push(heading + '\n');
var docOut = parseDocStr(docStr);
if (docOut.length > 0) {
lines.push(docOut.join('\n') + '\n');
}
}
/**
* Write out a heading and doc for a primitive (string, number, boolean, etc).
*/
function writeDataProperty(lines, parentObj, prop, val, level) {
// E.g. "## someString <sub>string</sub>"
var typeStr = typeof val;
var heading = '#'.repeat(level + 2) + ' ' + prop + ' <sub>' + typeStr + '</sub>';
lines.push(heading + '\n');
// Look up doc in parent's doc block
var docStr = docOf(parentObj, prop);
var docOut = parseDocStr(docStr);
if (docOut.length > 0) {
lines.push(docOut.join('\n') + '\n');
}
}
// Provide a doc string for writeDocFile
writeDocFile[cell.DOC] = `Return a markdown string for a given obj, with an optional title.`;
var doc = {writeDocFile: writeDocFile}
doc[cell.DOC] = `
Provides a consistent way to create documentation for prosperon elements. Objects are documented by adding docstrings directly to object-like things (functions, objects, ...), or to an object's own "doc object".
Docstrings are set to the symbol \`cell.DOC\`
\`\`\`js
// Suppose we have a module that returns a function
function greet(name) { log.console("Hello, " + name) }
// We can attach a docstring
greet.doc = \`
Greets the user by name.
:param name: The name of the person to greet.
\`
// A single function is a valid return!
return greet
\`\`\`
\`\`\`js
// Another way is to add a docstring object to an object
var greet = {
hello() { log.console('hello!') }
}
greet[cell.DOC] = {}
greet[cell.DOC][cell.DOC] = 'An object full of different greeter functions'
greet[cell.DOC].hello = 'A greeter that says, "hello!"'
\`\`\`
`
return doc

View File

@@ -29,19 +29,30 @@ static void js_enet_peer_finalizer(JSRuntime *rt, JSValue val)
free(peer->data);
}
/* ENet init/deinit */
// Initialize the ENet library. Must be called before using any ENet functionality.
static JSValue js_enet_initialize(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
if (enet_initialize() != 0) return JS_ThrowInternalError(ctx, "Error initializing ENet");
return JS_NULL;
}
// Deinitialize the ENet library, cleaning up all resources. Call this when you no longer
// need any ENet functionality.
static JSValue js_enet_deinitialize(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
enet_deinitialize();
return JS_NULL;
}
// Create an ENet host for either a client-like unbound host or a server bound to a specific
// address and port:
//
// - If no argument is provided, creates an unbound "client-like" host with default settings
// (maximum 32 peers, 2 channels, unlimited bandwidth).
// - If you pass an "ip:port" string (e.g. "127.0.0.1:7777"), it creates a server bound to
// that address. The server supports up to 32 peers, 2 channels, and unlimited bandwidth.
//
// Throws an error if host creation fails for any reason.
static JSValue js_enet_host_create(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetHost *host;
@@ -112,6 +123,7 @@ wrap:
return obj;
}
// Helper function to get a JSValue for an ENetPeer.
static JSValue peer_get_value(JSContext *ctx, ENetPeer *peer)
{
if (!peer->data) {
@@ -122,7 +134,14 @@ static JSValue peer_get_value(JSContext *ctx, ENetPeer *peer)
return JS_DupValue(ctx, *(JSValue*)peer->data);
}
/* Host service: poll for events */
// Poll for and process any available network events (connect, receive, disconnect, or none)
// from this host, calling the provided callback for each event. This function loops until
// no more events are available in the current timeframe.
//
// :param callback: A function called once for each available event, receiving an event
// object as its single argument.
// :param timeout: (optional) Timeout in milliseconds. Defaults to 0 (non-blocking).
// :return: None
static JSValue js_enet_host_service(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetHost *host = JS_GetOpaque(this_val, enet_host_id);
@@ -170,7 +189,12 @@ static JSValue js_enet_host_service(JSContext *ctx, JSValueConst this_val, int a
return JS_NULL;
}
/* Host connect: client -> connect to server */
// Initiate a connection from this host to a remote server. Throws an error if the
// connection cannot be started.
//
// :param hostname: The hostname or IP address of the remote server (e.g. "example.com" or "127.0.0.1").
// :param port: The port number to connect to.
// :return: An ENetPeer object representing the connection.
static JSValue js_enet_host_connect(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetHost *host = JS_GetOpaque(this_val, enet_host_id);
@@ -194,7 +218,9 @@ static JSValue js_enet_host_connect(JSContext *ctx, JSValueConst this_val, int a
return peer_get_value(ctx, peer);
}
/* Flush queued packets */
// Flush all pending outgoing packets for this host immediately.
//
// :return: None
static JSValue js_enet_host_flush(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetHost *host = JS_GetOpaque(this_val, enet_host_id);
@@ -203,7 +229,10 @@ static JSValue js_enet_host_flush(JSContext *ctx, JSValueConst this_val, int arg
return JS_NULL;
}
/* Broadcast a string or ArrayBuffer */
// Broadcast a string or ArrayBuffer to all connected peers on channel 0.
//
// :param data: A string or ArrayBuffer to broadcast to all peers.
// :return: None
static JSValue js_enet_host_broadcast(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetHost *host = JS_GetOpaque(this_val, enet_host_id);
@@ -257,7 +286,11 @@ static JSValue js_enet_host_get_address(JSContext *js, JSValueConst self, int ar
return JS_NewString(js, ip_str);
}
/* Peer-level operations */
// Peer-level operations
// Request a graceful disconnection from this peer. The connection will close after
// pending data is sent.
//
// :return: None
static JSValue js_enet_peer_disconnect(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
@@ -266,7 +299,10 @@ static JSValue js_enet_peer_disconnect(JSContext *ctx, JSValueConst this_val, in
return JS_NULL;
}
/* Peer send must only accept string or ArrayBuffer */
// Send a string or ArrayBuffer to this peer on channel 0.
//
// :param data: A string or ArrayBuffer to send.
// :return: None
static JSValue js_enet_peer_send(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
@@ -296,6 +332,9 @@ static JSValue js_enet_peer_send(JSContext *ctx, JSValueConst this_val, int argc
return JS_NULL;
}
// Immediately terminate the connection to this peer, discarding any pending data.
//
// :return: None
static JSValue js_enet_peer_disconnect_now(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
@@ -304,6 +343,9 @@ static JSValue js_enet_peer_disconnect_now(JSContext *ctx, JSValueConst this_val
return JS_NULL;
}
// Request a disconnection from this peer after all queued packets are sent.
//
// :return: None
static JSValue js_enet_peer_disconnect_later(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
@@ -312,6 +354,9 @@ static JSValue js_enet_peer_disconnect_later(JSContext *ctx, JSValueConst this_v
return JS_NULL;
}
// Reset this peer's connection, immediately dropping it and clearing its internal state.
//
// :return: None
static JSValue js_enet_peer_reset(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
@@ -320,6 +365,9 @@ static JSValue js_enet_peer_reset(JSContext *ctx, JSValueConst this_val, int arg
return JS_NULL;
}
// Send a ping request to this peer to measure latency.
//
// :return: None
static JSValue js_enet_peer_ping(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
@@ -328,6 +376,13 @@ static JSValue js_enet_peer_ping(JSContext *ctx, JSValueConst this_val, int argc
return JS_NULL;
}
// Configure the throttling behavior for this peer, controlling how ENet adjusts its sending
// rate based on packet loss or congestion.
//
// :param interval: The interval (ms) between throttle adjustments.
// :param acceleration: The factor to increase sending speed when conditions improve.
// :param deceleration: The factor to decrease sending speed when conditions worsen.
// :return: None
static JSValue js_enet_peer_throttle_configure(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
@@ -354,7 +409,7 @@ static JSValue js_enet_peer_timeout(JSContext *ctx, JSValueConst this_val, int a
return JS_NULL;
}
/* Class definitions */
// Class definitions
static JSClassDef enet_host = {
"ENetHost",
.finalizer = js_enet_host_finalizer,
@@ -374,7 +429,6 @@ JSValue js_enet_resolve_hostname(JSContext *js, JSValue self, int argc, JSValue
return JS_NULL;
}
/* Function lists */
static const JSCFunctionListEntry js_enet_funcs[] = {
JS_CFUNC_DEF("initialize", 0, js_enet_initialize),
JS_CFUNC_DEF("deinitialize", 0, js_enet_deinitialize),
@@ -391,7 +445,6 @@ static const JSCFunctionListEntry js_enet_host_funcs[] = {
JS_CGETSET_DEF("address", js_enet_host_get_address, NULL),
};
/* Peer getters */
static JSValue js_enet_peer_get_rtt(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);

View File

@@ -1,151 +0,0 @@
// enet.js doc (updated)
var enet = this;
//------------------------------------------------
// Top-level ENet functions
//------------------------------------------------
enet.initialize[cell.DOC] = `
Initialize the ENet library. Must be called before using any ENet functionality.
Throws an error if initialization fails.
:return: None
`;
enet.deinitialize[cell.DOC] = `
Deinitialize the ENet library, cleaning up all resources. Call this when you no longer
need any ENet functionality.
:return: None
`;
enet.create_host[cell.DOC] = `
Create an ENet host for either a client-like unbound host or a server bound to a specific
address and port:
- If no argument is provided, creates an unbound "client-like" host with default settings
(maximum 32 peers, 2 channels, unlimited bandwidth).
- If you pass an "ip:port" string (e.g. "127.0.0.1:7777"), it creates a server bound to
that address. The server supports up to 32 peers, 2 channels, and unlimited bandwidth.
Throws an error if host creation fails for any reason.
:param address: (optional) A string in 'ip:port' format to bind the host (server), or
omit to create an unbound client-like host.
:return: An ENetHost object.
`;
//------------------------------------------------
// ENetHost methods
//------------------------------------------------
var enet_host = prosperon.c_types.enet_host;
enet_host.service[cell.DOC] = `
Poll for and process any available network events (connect, receive, disconnect, or none)
from this host, calling the provided callback for each event. This function loops until
no more events are available in the current timeframe.
Event object properties:
- type: String, one of "connect", "receive", "disconnect", or "none".
- peer: (present if type = "connect") The ENetPeer object for the new connection.
- channelID: (present if type = "receive") The channel on which the data was received.
- data: (present if type = "receive") The received data as a *plain JavaScript object*.
If the JSON parse fails or the data isn't an object, a JavaScript error is thrown.
:param callback: A function called once for each available event, receiving an event
object as its single argument.
:param timeout: (optional) Timeout in milliseconds. Defaults to 0 (non-blocking).
:return: None
`;
enet_host.connect[cell.DOC] = `
Initiate a connection from this host to a remote server. Throws an error if the
connection cannot be started.
:param host: The hostname or IP address of the remote server (e.g. "example.com" or "127.0.0.1").
:param port: The port number to connect to.
:return: An ENetPeer object representing the connection.
`;
enet_host.flush[cell.DOC] = `
Flush all pending outgoing packets for this host immediately.
:return: None
`;
enet_host.broadcast[cell.DOC] = `
Broadcast a JavaScript object to all connected peers on channel 0. The object is
serialized to JSON, and the packet is sent reliably. Throws an error if serialization fails.
:param data: A JavaScript object to broadcast to all peers.
:return: None
`;
//------------------------------------------------
// ENetPeer methods
//------------------------------------------------
var enet_peer = prosperon.c_types.enet_peer;
enet_peer.send[cell.DOC] = `
Send a JavaScript object to this peer on channel 0. The object is serialized to JSON and
sent reliably. Throws an error if serialization fails.
:param data: A JavaScript object to send.
:return: None
`;
enet_peer.disconnect[cell.DOC] = `
Request a graceful disconnection from this peer. The connection will close after
pending data is sent.
:return: None
`;
enet_peer.disconnect_now[cell.DOC] = `
Immediately terminate the connection to this peer, discarding any pending data.
:return: None
`;
enet_peer.disconnect_later[cell.DOC] = `
Request a disconnection from this peer after all queued packets are sent.
:return: None
`;
enet_peer.reset[cell.DOC] = `
Reset this peer's connection, immediately dropping it and clearing its internal state.
:return: None
`;
enet_peer.ping[cell.DOC] = `
Send a ping request to this peer to measure latency.
:return: None
`;
enet_peer.throttle_configure[cell.DOC] = `
Configure the throttling behavior for this peer, controlling how ENet adjusts its sending
rate based on packet loss or congestion.
:param interval: The interval (ms) between throttle adjustments.
:param acceleration: The factor to increase sending speed when conditions improve.
:param deceleration: The factor to decrease sending speed when conditions worsen.
:return: None
`;
enet_peer.timeout[cell.DOC] = `
Set timeout parameters for this peer, determining how long ENet waits before considering
the connection lost.
:param timeout_limit: The total time (ms) before the peer is disconnected.
:param timeout_min: The minimum timeout (ms) used for each timeout attempt.
:param timeout_max: The maximum timeout (ms) used for each timeout attempt.
:return: None
`;
// Return the enet object.
return enet;

View File

@@ -1,5 +1,4 @@
(function engine() {
cell.DOC = Symbol()
var ACTORDATA = cell.hidden.actorsym
var SYSYM = '__SYSTEM__'
@@ -215,6 +214,16 @@ var SCOPE_CORE = 2
function resolve_path(requested, pkg_context, ext) {
if (requested.endsWith(ext)) ext = ''
// Check for underscore prefix in imports - not allowed except for local access
if (requested.includes('/')) {
var parts = requested.split('/')
if (parts.some(part => part.startsWith('_'))) {
throw new Error(`Cannot import modules with underscore prefix: ${requested}`)
}
} else if (requested.startsWith('_')) {
throw new Error(`Cannot import modules with underscore prefix: ${requested}`)
}
var dependencies = (config && config.dependencies) ? config.dependencies : {}
// Helper to check file existence and return result object
@@ -244,21 +253,52 @@ function resolve_path(requested, pkg_context, ext) {
// Step 1: current package (Local)
if (pkg_context) {
var pkg_path = '.cell/modules/' + pkg_context + '/' + requested + ext
var underscore_pkg_path = '.cell/modules/' + pkg_context + '/_' + requested + ext
var res = check(pkg_path, pkg_context, false, SCOPE_LOCAL)
var underscore_res = check(underscore_pkg_path, pkg_context, false, SCOPE_LOCAL)
// If both regular and underscore versions exist, throw error
if (res && underscore_res) {
throw new Error(`Module conflict: both '${requested}' and '_${requested}' exist in package '${pkg_context}'`)
}
// Prefer regular version, but allow underscore version if no regular exists
if (res) return res
if (underscore_res) return underscore_res
// Check if package is locally replaced
if (config && config.replace && config.replace[pkg_context]) {
var replace_path = config.replace[pkg_context]
var full_path = replace_path + '/' + requested + ext
var underscore_full_path = replace_path + '/_' + requested + ext
res = check(full_path, pkg_context, false, SCOPE_LOCAL)
underscore_res = check(underscore_full_path, pkg_context, false, SCOPE_LOCAL)
if (res && underscore_res) {
throw new Error(`Module conflict: both '${requested}' and '_${requested}' exist in replaced package '${pkg_context}'`)
}
if (res) return res
if (underscore_res) return underscore_res
}
} else {
// Top-level local
var project_path = requested + ext
var underscore_project_path = '_' + requested + ext
var res = check(project_path, null, false, SCOPE_LOCAL)
var underscore_res = check(underscore_project_path, null, false, SCOPE_LOCAL)
// If both regular and underscore versions exist, throw error
if (res && underscore_res) {
throw new Error(`Module conflict: both '${requested}' and '_${requested}' exist locally`)
}
// Prefer regular version, but allow underscore version if no regular exists
if (res) return res
if (underscore_res) return underscore_res
}
// Step 2: dependencies (explicit alias first) and replace directives
@@ -657,21 +697,21 @@ function create_actor(desc = {id:guid()}) {
var $_ = create_actor()
// returns a number between 0 and 1. There is a 50% chance that the result is less than 0.5.
$_.random = function() {
var n = os.random()
return n > 100000
}
$_.random[cell.DOC] = "returns a number between 0 and 1. There is a 50% chance that the result is less than 0.5."
$_.random_fit = os.random
// takes a function input value that will eventually be called with the current time in number form.
$_.clock = function(fn) {
actor_mod.clock(_ => {
fn(time.number())
send_messages()
})
}
$_.clock[cell.DOC] = "takes a function input value that will eventually be called with the current time in number form."
var underlings = new Set() // this is more like "all actors that are notified when we die"
var overling = null
@@ -707,6 +747,7 @@ function peer_connection(peer) {
}
}
// takes a callback function, an actor object, and a configuration record for getting information about the status of a connection to the actor. The configuration record is used to request the sort of information that needs to be communicated. This can include latency, bandwidth, activity, congestion, cost, partitions. The callback is given a record containing the requested information.
$_.connection = function(callback, actor, config) {
var peer = peers[actor[ACTORDATA].id]
if (peer) {
@@ -720,7 +761,6 @@ $_.connection = function(callback, actor, config) {
callback()
}
$_.connection[cell.DOC] = "The connection function takes a callback function, an actor object, and a configuration record for getting information about the status of a connection to the actor. The configuration record is used to request the sort of information that needs to be communicated. This can include latency, bandwidth, activity, congestion, cost, partitions. The callback is given a record containing the requested information."
var peers = {}
var id_address = {}
@@ -728,6 +768,7 @@ var peer_queue = {}
var portal = null
var portal_fn = null
// takes a function input value that will eventually be called with the current time in number form.
$_.portal = function(fn, port) {
if (portal) throw new Error(`Already started a portal listening on ${portal.port}`)
if (!port) throw new Error("Requires a valid port.")
@@ -735,7 +776,6 @@ $_.portal = function(fn, port) {
portal = enet.create_host({address: "any", port})
portal_fn = fn
}
$_.portal[cell.DOC] = "A portal is a special actor with a public address that performs introduction services. It listens on a specified port for contacts by external actors that need to acquire an actor object. The function will receive the record containing the request. The record can have a reply sent through it. A portal can respond by beginning a new actor, or finding an existing actor, or by forwarding the contact message to another actor. This is how distributed Misty networks are bootstrapped. The portal function returns null."
function handle_host(e) {
switch (e.type) {
@@ -778,17 +818,15 @@ function handle_host(e) {
}
}
// takes a callback function, an actor object, and a configuration record for getting information about the status of a connection to the actor. The configuration record is used to request the sort of information that needs to be communicated. This can include latency, bandwidth, activity, congestion, cost, partitions. The callback is given a record containing the requested information.
$_.contact = function(callback, record) {
send(create_actor(record), record, callback)
}
$_.contact[cell.DOC] = `The contact function sends a message to a portal on another machine to obtain an actor object.
The callback is a function with a actor input and a reason input. If successful, actor is bound to an actor object. If not successful, actor is null and reason may contain an explanation.`
// registers a function that will receive all messages...
$_.receiver = function receiver(fn) {
receive_fn = fn
}
$_.receiver[cell.DOC] = "registers a function that will receive all messages..."
$_.start = function start(cb, program, ...args) {
if (!program) return
@@ -813,6 +851,7 @@ $_.start = function start(cb, program, ...args) {
message_queue.push({ startup })
}
// stops an underling or self.
$_.stop = function stop(actor) {
if (!actor) {
need_stop = true
@@ -825,12 +864,13 @@ $_.stop = function stop(actor) {
sys_msg(actor, {kind:"stop"})
}
$_.stop[cell.DOC] = "The stop function stops an underling."
// schedules the removal of an actor after a specified amount of time.
$_.unneeded = function unneeded(fn, seconds) {
actor_mod.unneeded(fn, seconds)
}
// schedules the invocation of a function after a specified amount of time.
$_.delay = function delay(fn, seconds = 0) {
if (seconds <= 0) {
$_.clock(fn)
@@ -844,8 +884,8 @@ $_.delay = function delay(fn, seconds = 0) {
var id = actor_mod.delay(delay_turn, seconds)
return function() { actor_mod.removetimer(id) }
}
$_.delay[cell.DOC] = "used to schedule the invocation of a function..."
// causes this actor to stop when another actor stops.
var couplings = new Set()
$_.couple = function couple(actor) {
if (actor == $_) return // can't couple to self
@@ -853,7 +893,6 @@ $_.couple = function couple(actor) {
sys_msg(actor, {kind:'couple', from: $_})
log.system(`coupled to ${actor}`)
}
$_.couple[cell.DOC] = "causes this actor to stop when another actor stops."
function actor_prep(actor, send) {
message_queue.push({actor,send});

View File

@@ -1,50 +0,0 @@
var js = this
js[cell.DOC] = `
Provides functions for introspecting and configuring the QuickJS runtime engine.
Includes debug info, memory usage, GC controls, code evaluation, etc.
`
js.calc_mem[cell.DOC] = `
:param value: A JavaScript value to analyze.
:return: Approximate memory usage (in bytes) of that single value.
Compute the approximate size of a single JS value in memory. This is a best-effort estimate.
`
js.mem_limit[cell.DOC] = `
:param bytes: The maximum memory (in bytes) QuickJS is allowed to use.
:return: None
Set the upper memory limit for the QuickJS runtime. Exceeding this limit may cause operations to
fail or throw errors.
`
js.gc_threshold[cell.DOC] = `
:param bytes: The threshold (in bytes) at which the engine triggers automatic garbage collection.
:return: None
Set the threshold (in bytes) for QuickJS to perform an automatic GC pass when memory usage surpasses it.
`
js.max_stacksize[cell.DOC] = `
:param bytes: The maximum allowed stack size (in bytes) for QuickJS.
:return: None
Set the maximum stack size for QuickJS. If exceeded, the runtime may throw a stack overflow error.
`
js.gc[cell.DOC] = `
:return: None
Force an immediate, full garbage collection pass, reclaiming unreachable memory.
`
js.eval[cell.DOC] = `
:param src: A string of JavaScript source code to evaluate.
:param filename: (Optional) A string for the filename or label, used in debugging or stack traces.
:return: The result of evaluating the given source code.
Execute a string of JavaScript code in the current QuickJS context.
`
return js

View File

@@ -20,21 +20,4 @@ nota.encode = function(obj, replacer)
return result
}
nota.encode[cell.DOC] = `Convert a JavaScript value into a NOTA-encoded ArrayBuffer.
This function serializes JavaScript values (such as numbers, strings, booleans, arrays, objects, or ArrayBuffers) into the NOTA binary format. The resulting ArrayBuffer can be stored or transmitted and later decoded back into a JavaScript value.
:param value: The JavaScript value to encode (e.g., number, string, boolean, array, object, or ArrayBuffer).
:return: An ArrayBuffer containing the NOTA-encoded data.
:throws: An error if no argument is provided.
`
nota.decode[cell.DOC] = `Decode a NOTA-encoded ArrayBuffer into a JavaScript value.
This function deserializes a NOTA-formatted ArrayBuffer into its corresponding JavaScript representation, such as a number, string, boolean, array, object, or ArrayBuffer. If the input is invalid or empty, it returns null.
:param buffer: An ArrayBuffer containing NOTA-encoded data to decode.
:return: The decoded JavaScript value (e.g., number, string, boolean, array, object, or ArrayBuffer), or null if no argument is provided.
`
return nota

View File

@@ -302,7 +302,6 @@ JSC_SCALL(os_system,
)
JSC_SCALL(os_exit,
printf("exit\n");
exit(0);
)

View File

@@ -1,17 +0,0 @@
var os = this
os.platform[cell.DOC] = "Return a string with the underlying platform name, like 'Windows', 'Linux', or 'macOS'."
os.arch[cell.DOC] = "Return the CPU architecture string for this system (e.g. 'x64', 'arm64')."
os.totalmem[cell.DOC] = "Return the total system RAM in bytes."
os.freemem[cell.DOC] = "Return the amount of free system RAM in bytes, if known."
os.hostname[cell.DOC] = "Return the system's hostname, or an empty string if not available."
os.version[cell.DOC] = "Return the OS or kernel version string, if the platform provides it."
os.now[cell.DOC] = "Return current time (in seconds as a float) with high resolution."
os.power_state[cell.DOC] = "Return a string describing power status: 'on battery', 'charging', 'charged', etc."
os.rusage[cell.DOC] = "Return resource usage stats for this process, if the platform supports it."
os.mallinfo[cell.DOC] = "Return detailed memory allocation info (arena size, free blocks, etc.) on some platforms."
return os

View File

@@ -1,3 +1,4 @@
// epoch = 0000-01-01 00:00:00 +0000
var time = this;
/* -------- host helpers -------------------------------------------------- */
@@ -55,18 +56,13 @@ time.timecode = function(t, fps = 24)
/* per-month day counts (non-leap) */
time.monthdays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
/* -------- core converters ----------------------------------------------- */
// convert seconds-since-epoch → broken-down record
function record(num = now(),
zone = computer_zone(),
dst = computer_dst())
{
/* caller passed an existing record → return it verbatim */
if (typeof num == "object") return num;
/* -------------------------------------------------------------------- */
/* convert seconds-since-epoch → broken-down record */
var monthdays = time.monthdays.slice();
var rec = {
second : 0, minute : 0, hour : 0,
@@ -116,12 +112,8 @@ function record(num = now(),
function number(rec = now())
{
/* fall through for numeric input or implicit “now” */
if (typeof rec == "number") return rec;
/* -------------------------------------------------------------------- */
/* record → seconds-since-epoch */
log.console(typeof rec)
log.console(rec)
log.console(rec.minute)
@@ -158,8 +150,7 @@ function number(rec = now())
return c;
}
/* -------- text formatting ----------------------------------------------- */
// text formatting
var default_fmt = "vB mB d hh:nn:ss a z y c"; /* includes new DST token */
function text(num = now(),
@@ -212,13 +203,4 @@ function text(num = now(),
return fmt;
}
/* -------- docs ---------------------------------------------------------- */
time[cell.DOC] = {
doc : "Time utilities; epoch = 0000-01-01 00:00:00 +0000.",
record : "time.record([num | rec], [zone], [dst]) → detailed record (adds .dst).",
number : "time.number([rec]) → numeric seconds since epoch.",
text : "time.text([val], [fmt], [zone], [dst]) → formatted string (token Z = DST)."
};
return { record, number, text };

View File

@@ -1,96 +0,0 @@
var util = this
return util
util.dainty_assign = function (target, source) {
Object.keys(source).forEach(function (k) {
if (typeof source[k] == "function") return
if (!(k in target)) return
if (Array.isArray(source[k])) target[k] = deep_copy(source[k])
else if (Object.isObject(source[k])) Object.dainty_assign(target[k], source[k])
else target[k] = source[k]
})
}
util.get = function (obj, path, defValue) {
if (!path) return null
var pathArray = Array.isArray(path) ? path : path.match(/([^[.\]])+/g)
var result = pathArray.reduce((prevObj, key) => prevObj && prevObj[key], obj)
return result == null ? defValue : result
}
util.isEmpty = function(o) {
return Object.keys(o).length == 0
}
util.dig = function (obj, path, deflt = {}) {
var pp = path.split(".")
for (var i = 0; i < pp.length - 1; i++) {
obj = obj[pp[i]] = obj[pp[i]] || {}
}
obj[pp[pp.length - 1]] = deflt
return deflt
}
util.access = function (obj, name) {
var dig = name.split(".")
for (var i of dig) {
obj = obj[i]
if (!obj) return null
}
return obj
}
util.mergekey = function (o1, o2, k) {
if (!o2) return
if (typeof o2[k] == "object") {
if (Array.isArray(o2[k])) o1[k] = deep_copy(o2[k])
else {
if (!o1[k]) o1[k] = {}
if (typeof o1[k] == "object") util.merge(o1[k], o2[k])
else o1[k] = o2[k]
}
} else o1[k] = o2[k]
}
util.merge = function (target, ...objs) {
for (var obj of objs) for (var key of Object.keys(obj)) util.mergekey(target, obj, key)
return target
}
util.copy = function (proto, ...objs) {
var c = Object.create(proto)
for (var obj of objs) Object.mixin(c, obj)
return c
}
util.obj_lerp = function(a,b,t) {
if (a.lerp) return a.lerp(b, t)
var obj = {}
Object.keys(a).forEach(function (key) {
obj[key] = a[key].lerp(b[key], t)
})
return obj
}
util.normalizeSpacing = function normalizeSpacing(spacing) {
if (typeof spacing == 'number') {
return {l: spacing, r: spacing, t: spacing, b: spacing}
} else if (Array.isArray(spacing)) {
if (spacing.length == 2) {
return {l: spacing[0], r: spacing[0], t: spacing[1], b: spacing[1]}
} else if (spacing.length == 4) {
return {l: spacing[0], r: spacing[1], t: spacing[2], b: spacing[3]}
}
} else if (typeof spacing == 'object') {
return {l: spacing.l || 0, r: spacing.r || 0, t: spacing.t || 0, b: spacing.b || 0}
} else {
return {l:0, r:0, t:0, b:0}
}
}
function deep_copy(from) {
return json.decode(json.encode(from))
}
return util

View File

@@ -21,23 +21,4 @@ wota.encode = function(obj, replacer)
return result
}
wota.encode[cell.DOC] = `Convert a JavaScript value into a WOTA-encoded ArrayBuffer.
This function serializes JavaScript values (such as numbers, strings, booleans, arrays, objects, or ArrayBuffers) into the WOTA binary format. The resulting ArrayBuffer can be stored or transmitted and later decoded back into a JavaScript value.
:param value: The JavaScript value to encode (e.g., number, string, boolean, array, object, or ArrayBuffer).
:param replacer: An optional function that alters the encoding behavior for specific values.
:return: An ArrayBuffer containing the WOTA-encoded data.
:throws: An error if no argument is provided or if a cyclic object is encountered.
`
wota.decode[cell.DOC] = `Decode a WOTA-encoded ArrayBuffer into a JavaScript value.
This function deserializes a WOTA-formatted ArrayBuffer into its corresponding JavaScript representation, such as a number, string, boolean, array, object, or ArrayBuffer. If the input is invalid or empty, it returns null.
:param buffer: An ArrayBuffer containing WOTA-encoded data to decode.
:param reviver: An optional function that transforms the decoded values.
:return: The decoded JavaScript value (e.g., number, string, boolean, array, object, or ArrayBuffer), or null if no argument is provided.
`
return wota

View File

@@ -1,51 +0,0 @@
#ifndef CIRCBUF_H
#define CIRCBUF_H
#include <stdlib.h>
#include <stdint.h>
static inline unsigned int powof2(unsigned int x)
{
x = x-1;
x |= (x >> 1);
x |= (x >> 2);
x |= (x >> 4);
x |= (x >> 8);
x |= (x >> 16);
return x+1;
}
struct rheader
{
unsigned int read;
unsigned int write;
int len;
};
#define ringheader(r) ((struct rheader *)r-1)
static inline void *ringmake(void *ring, size_t elemsize, unsigned int n)
{
n = powof2(n);
if (ring) {
struct rheader *h = ringheader(ring);
if (n <= h->len) return h+1;
h = realloc(h, elemsize*n+sizeof(struct rheader));
return h+1;
}
struct rheader *h = malloc(elemsize*n+sizeof(struct rheader));
h->len = n; h->read = 0; h->write = 0;
return h+1;
}
#define ringnew(r,n) (r = ringmake(r, sizeof(*r),n))
#define ringfree(r) ((r) ? free(ringheader(r)) : 0)
#define ringmask(r,v) (v & (ringheader(r)->len-1))
#define ringpush(r,v) (r[ringmask(r,ringheader(r)->write++)] = v)
#define ringshift(r) (r[ringmask(r,ringheader(r)->read++)])
#define ringsize(r) ((r) ? ringheader(r)->write - ringheader(r)->read : 0)
#define ringfull(r) ((r) ? ringsize(r) == ringheader(r)->len : 0)
#define ringempty(r) ((r) ? ringheader(r)->read == ringheader(r)->write : 0)
#endif

View File

@@ -110,6 +110,9 @@ JSValue number2js(JSContext *js, double g);
double cell_random();
void *js2wota(JSContext *js, JSValue v);
JSValue wota2js(JSContext *js, void *v);
#ifdef __cplusplus
}
#endif

View File

@@ -66,8 +66,6 @@ void nota_write_sym (NotaBuffer *nb, int sym);
#include <math.h>
#include <limits.h>
#include "kim.h"
static inline char *nota_skip(char *nota)
{
while (CONTINUE(*nota))

View File

@@ -193,6 +193,9 @@ JSValue NAME##2js(JSContext *js, int enumval) { \
return JS_NULL; \
}
#define CELL_USE_INIT(c) \
JSValue CELL_USE_NAME(JSContext *js) { c }
#define CELL_USE_FUNCS(FUNCS) \
JSValue CELL_USE_NAME(JSContext *js) { \
JSValue mod = JS_NewObject(js); \