554 lines
13 KiB
JavaScript
554 lines
13 KiB
JavaScript
// set up events on prosperon
|
|
var listeners = new Map()
|
|
prosperon.on = function(type, callback)
|
|
{
|
|
if (!listeners.has(type)) listeners.set(type, [])
|
|
listeners.get(type).push(callback)
|
|
|
|
return function() {
|
|
var arr = listeners.get(type)
|
|
if (!arr) return
|
|
var idx = arr.indexOf(callback)
|
|
if (idx >= 0) arr.splice(idx,1)
|
|
}
|
|
}
|
|
|
|
prosperon.dispatch = function(type, data)
|
|
{
|
|
var arr = listeners.get(type)
|
|
if (!arr) return
|
|
for (var callback of arr) callback(data)
|
|
}
|
|
|
|
var os = use_embed('os')
|
|
var js = use_embed('js')
|
|
|
|
prosperon.on('SIGINT', function() {
|
|
os.exit();
|
|
})
|
|
|
|
prosperon.on('SIGABRT', function() {
|
|
console.error(new Error('SIGABRT'));
|
|
os.exit(1);
|
|
})
|
|
|
|
prosperon.on('SIGSEGV', function() {
|
|
console.error(new Error('SIGSEGV'));
|
|
os.exit(1);
|
|
})
|
|
|
|
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')
|
|
|
|
var canonical = io.realdir('scripts/resources.js') + 'resources.js'
|
|
var content = io.slurp('scripts/resources.js')
|
|
var resources = js.eval('scripts/resources.js', `(function setup_resources(){${content}})`).call({})
|
|
console.print(resources.canonical('resources.js'))
|
|
use_cache[resources.canonical('resources.js')] = resources
|
|
|
|
function print_api(obj) {
|
|
for (var prop in obj) {
|
|
if (!obj.hasOwnProperty(prop)) continue
|
|
var val = obj[prop]
|
|
console.log(prop)
|
|
if (typeof val === 'function') {
|
|
var m = val.toString().match(/\(([^)]*)\)/)
|
|
if (m) console.log(' function: ' + prop + '(' + m[1].trim() + ')')
|
|
}
|
|
}
|
|
}
|
|
|
|
prosperon.PATH = [
|
|
"/",
|
|
"scripts/",
|
|
"scripts/modules/"
|
|
]
|
|
|
|
function find_in_path(filename, exts = []) {
|
|
for (var dir of prosperon.PATH) {
|
|
var candidate = dir + filename;
|
|
if (io.exists(candidate)) return candidate;
|
|
|
|
for (var ext of exts) {
|
|
candidate = dir + filename + ext;
|
|
if (io.exists(candidate)) return candidate;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
// 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(find_in_path(path, ['.js', '.jsc']));
|
|
|
|
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 = js.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;
|
|
// console.log(`api for ${path}`)
|
|
// print_api(parsed.module_ret)
|
|
}
|
|
|
|
if (parsed.program) {
|
|
var prog_script = `(function use_${module_name}() { var self = this; var $ = this.__proto__; ${parsed.program}})`;
|
|
parsed.prog_fn = js.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.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("scripts/base.js")
|
|
var fnname = "base"
|
|
script = `(function ${fnname}() { ${script}; })`
|
|
js.eval('scripts/base.js', script)()
|
|
|
|
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.platform()) {
|
|
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)
|
|
act.tag_add(underling.tag, underling)
|
|
|
|
underling[GARBAGE] = underling.garbage
|
|
return underling;
|
|
};
|
|
|
|
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();
|
|
|
|
act.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 act = use('actor')
|
|
|
|
actor[UNDERLINGS] = new Set()
|
|
|
|
globalThis.mixin("color");
|
|
|
|
use('cmd')(prosperon.argv)
|