Files
cell/scripts/engine.js
2025-01-25 17:15:25 -06:00

504 lines
12 KiB
JavaScript

globalThis.prosperon = {}
var os = use_embed('os')
prosperon.SIGINT = function() {
os.exit();
}
var use_cache = {}
Object.defineProperty(Function.prototype, "hashify", {
value: function () {
var hash = new Map();
var fn = this;
function hashified(...args) {
var key = args[0];
if (!hash.has(key)) hash.set(key, fn(...args));
return hash.get(key);
}
return hashified;
},
});
var io = use_embed('io')
io.mount(io.basedir() + "core/scripts/")
io.mount(io.basedir() + "core/")
var canonical = io.realdir('resources.js') + 'resources.js'
var content = io.slurp('resources.js')
var resources = os.eval('resources.js', `(function setup_resources(){${content}})`).call({})
console.print(resources.canonical('resources.js'))
use_cache[resources.canonical('resources.js')] = resources
// path is the path of a module or script to resolve
var script_fn = function script_fn(path) {
var parsed = {}
var file = resources.find_script(path);
if (!file) {
// attempt to bare load
parsed.module_ret = bare_load(path);
if (!parsed.module_ret) throw new Error(`Module ${path} could not be created`)
return parsed
}
var content = io.slurp(file)
var parsed = parse_file(content);
var module_name = file.name();
parsed.module_ret = bare_load(path)
parsed.module_ret ??= {}
if (parsed.module) {
var mod_script = `(function setup_${module_name}_module(){ var self = this; var $ = this; var exports = {}; var module = {exports: exports}; var define = undefined; ${parsed.module}})`;
var module_fn = os.eval(file, mod_script);
parsed.module_ret = module_fn.call(parsed.module_ret);
if (parsed.module_ret === undefined || parsed.module_ret === null)
throw new Error(`Module ${module_name} must return a value`);
parsed.module_fn = module_fn;
}
if (parsed.program) {
var prog_script = `(function use_${module_name}() { var self = this; var $ = this.__proto__; ${parsed.program}})`;
parsed.prog_fn = os.eval(file, prog_script);
}
return parsed;
}.hashify();
function bare_load(file) {
try {
return use_embed(file)
} catch (e) { }
try {
return use_dyn(file + so_ext)
} catch(e) { }
return undefined
}
var res_cache = {};
function console_rec(category, priority, line, file, msg)
{
return `${file}:${line}: [${category} ${priority}]: ${msg}` + "\n";
}
var logfile = io.open('.prosperon/log.txt')
function pprint(msg, lvl = 0) {
if (!logfile) return;
if (typeof msg === "object") msg = JSON.stringify(msg, null, 2);
var file = "nofile";
var line = 0;
var caller = new Error().stack.split("\n")[2];
if (caller) {
var md = caller.match(/\((.*)\:/);
var m = md ? md[1] : "SCRIPT";
if (m) file = m;
md = caller.match(/\:(\d*)\)/);
m = md ? md[1] : 0;
if (m) line = m;
}
var fmt = console_rec("script", lvl, line,file, msg);
console.print(fmt)
if (logfile)
logfile.write(fmt)
if (tracy) tracy.message(fmt);
};
console.spam = function spam(msg) {
pprint(msg, 0);
};
console.debug = function debug(msg) {
pprint(msg, 1);
};
console.info = function info(msg) {
pprint(msg, 2);
};
console.warn = function warn(msg) {
pprint(msg, 3);
};
console.log = function(msg)
{
pprint(msg, 2)
}
console.log(io.searchpath())
console.error = function(e) {
if (!e)
e = new Error();
pprint(`${e.name} : ${e.message}
${e.stack}`, 4)
};
console.panic = function (e) {
pprint(e , 5)
os.quit();
};
console.assert = function (op, str = `assertion failed [value '${op}']`) {
if (!op) console.panic(str);
};
os.on('uncaught_exception', function(e) { console.error(e); });
console.doc = {
log: "Output directly to in game console.",
level: "Set level to output logging to console.",
info: "Output info level message.",
warn: "Output warn level message.",
error: "Output error level message, and print stacktrace.",
critical: "Output critical level message, and exit game immediately.",
write: "Write raw text to console.",
say: "Write raw text to console, plus a newline.",
stack: "Output a stacktrace to console.",
clear: "Clear console.",
};
var script = io.slurp("core/scripts/base.js")
var fnname = "base"
script = `(function ${fnname}() { ${script}; })`
os.eval('core/scripts/base.js', script)()
prosperon.SIGABRT = function()
{
console.error(new Error('SIGABRT'));
os.exit(1);
}
prosperon.SIGSEGV = function()
{
console.error(new Error('SIGSEGV'));
os.exit(1);
}
function add_timer(obj, fn, seconds)
{
var timers = obj[TIMERS]
var stop = function () {
if (!timer) return
timers.delete(stop);
timer.fn = undefined;
timer = undefined;
};
function execute() {
if (fn)
timer.remain = fn(stop.seconds);
if (!timer) return
if (!timer.remain)
stop();
else
stop.seconds = timer.remain;
}
var timer = os.make_timer(execute);
timer.remain = seconds;
stop.remain = seconds;
stop.seconds = seconds;
timers.push(stop);
return stop;
}
var DEAD = Symbol()
var GARBAGE = Symbol()
var FILE = Symbol()
var TIMERS = Symbol()
var REGGIES = Symbol()
var UNDERLINGS = Symbol()
var OVERLING = Symbol()
var actor = {};
var so_ext;
switch(os.sys()) {
case 'Windows':
so_ext = '.dll';
break;
default:
so_ext = '.so';
break;
}
var use = function use(file) {
if (use_cache[file]) return use_cache[file];
var mod = script_fn(file)
use_cache[file] = mod.module_ret
return use_cache[file]
}
globalThis.json = use('json')
function parse_file(content) {
if (!content) return {}
var parts = content.split(/\n\s*---\s*\n/)
if (parts.length === 1) {
var part = parts[0]
if (part.match(/return\s+[^;]+;?\s*$/))
return { module: part }
return { program: part }
}
var module = parts[0]
if (!module.match(/return\s+[^;]+;?\s*$/))
throw new Error("Module section must end with a return statement")
var pad = '\n'.repeat(module.split('\n').length+2) // add 2 from the split search
return {
module,
program: pad+parts[1]
}
}
//////////////////
///////REGISTRANT
/////////////////
/*
Factory for creating registries. Register one with 'X.register',
which returns a function that, when invoked, cancels the registry.
*/
var Register = {
registries: [],
add_cb(name) {
var n = {};
var fns = [];
n.register = function (fn, oname) {
if (!(fn instanceof Function)) return;
var dofn = function (...args) {
fn(...args);
};
Object.defineProperty(dofn, 'name', {value:`do_${oname}`});
var left = 0;
var right = fns.length - 1;
dofn.layer = fn.layer;
dofn.layer ??= 0;
while (left <= right) {
var mid = Math.floor((left + right) / 2);
if (fns[mid] === dofn.layer) {
left = mid;
break;
} else if (fns[mid].layer < dofn.layer) left = mid + 1;
else right = mid - 1;
}
fns.splice(left, 0, dofn);
return function () {
fns.delete(dofn)
};
};
prosperon[name] = function (...args) {
// tracy.fiber_enter(vector.fib);
fns.forEach(fn => {
fn(...args)
});
// tracy.fiber_leave(vector.fib);
};
Object.defineProperty(prosperon[name], 'name', {value:name});
prosperon[name].fns = fns;
n.clear = function () {
fns = [];
};
Register[name] = n;
Register.registries[name] = n;
return n;
},
};
Register.pull_registers = function pull_registers(obj)
{
var reggies = [];
for (var reg in Register.registries) {
if (typeof obj[reg] === "function")
reggies.push(reg);
}
return reggies;
}
Register.register_obj = function register_obj(obj, reg)
{
var fn = obj[reg].bind(obj);
fn.layer = obj[reg].layer;
var name = obj.ur ? obj.ur.name : obj.toString();
obj[TIMERS].push(Register.registries[reg].register(fn, name));
if (!obj[reg].name) Object.defineProperty(obj[reg], 'name', {value:`${obj._file}_${reg}`});
}
Register.check_registers = function check_registers(obj) {
if (obj[REGGIES]) {
if (obj[REGGIES].length == 0) return;
// fast path
for (var reg of obj[REGGIES])
Register.register_obj(obj,reg);
return;
}
for (var reg in Register.registries) {
if (typeof obj[reg] === "function")
Register.register_obj(obj,reg);
}
};
Register.add_cb("appupdate");
Register.add_cb("update").doc = "Called once per frame.";
Register.add_cb("physupdate");
Register.add_cb("gui");
Register.add_cb("hud");
Register.add_cb("draw");
Register.add_cb("imgui");
Register.add_cb("app");
Register.add_cb("prerender");
function cant_kill()
{
throw Error("Can't kill an object in its spawning code. Move the kill command to awake.");
}
actor.toString = function() { return this[FILE] }
actor.spawn = function spawn(script, config, callback) {
if (this[DEAD]) throw Error("Attempting to spawn on a dead actor")
var prog
if (!script) {
prog = {}
prog.module_ret = {}
prog.prog_fn = function() {}
} else {
prog = script_fn(script);
if (!prog.prog_fn) throw new Error(`Script ${script} is not an actor script or has no actor component`)
}
var underling;
prog.module_ret.__proto__ = actor;
underling = Object.create(prog.module_ret);
underling[OVERLING] = this;
underling[FILE] = script
underling[TIMERS] = []
underling[UNDERLINGS] = new Set()
Object.defineProperty(underling, 'overling', {
get() { return this[OVERLING] },
enumerable:true,
configurable:false
})
Object.defineProperty(underling, 'underlings', {
get() { return new Set(this[UNDERLINGS]) },
enumerable:true,
configurable:false
})
Object.defineProperty(underling, 'spawn', {
value: actor.spawn,
writable:false,
enumerable:true,
configurable:false
})
Object.defineProperty(underling, 'kill', {
value: actor.kill,
writable:false,
enumerable:true,
configurable:false
})
Object.defineProperty(underling, 'delay', {
value: actor.delay,
writable:false,
enumerable:true,
configurable:false
})
if (callback) callback(underling, {
message:"created"
})
try{
prog.prog_fn.call(underling)
} catch(e) {throw e}
if (underling[DEAD])
return undefined
if (typeof config === 'object') Object.assign(underling,config);
if (!underling[REGGIES])
underling.__proto__[REGGIES] = Register.pull_registers(underling)
Register.check_registers(underling);
if (underling.awake) underling.awake();
this[UNDERLINGS].add(underling);
if (underling.tag)
search.tag_add(underling.tag, underling)
underling[GARBAGE] = underling.garbage
return underling;
};
actor.spawn.doc = `Create a new actor, using this actor as the overling, initializing it with 'script' and with data (as a JSON or Nota file) from 'config'.`;
actor.clear = function actor_clear()
{
this[UNDERLINGS].forEach(p => {
p.kill();
})
this[UNDERLINGS].clear()
}
var input = use('input')
actor.kill = function kill() {
if (this[DEAD]) return
this[DEAD] = true
this[TIMERS].slice().forEach(t => t()) // slice in case something is removed from timers while running
delete this[TIMERS]
input.do_uncontrol(this);
this.clear()
this[OVERLING][UNDERLINGS].delete(this)
delete this[UNDERLINGS]
if (typeof this.garbage === "function") this.garbage();
if (typeof this.then === "function") this.then();
search.tag_clear_guid(this)
};
actor.kill.doc = `Remove this actor and all its underlings from existence.`;
actor.delay = function (fn, seconds) { add_timer(this, fn, seconds) }
actor.delay.doc = `Call 'fn' after 'seconds' with 'this' set to the actor.`;
var search = use('search')
actor[UNDERLINGS] = new Set()
globalThis.mixin("color");
globalThis.mixin("std")