(function engine() { prosperon.DOC = Symbol('+documentation+') // Symbol for documentation references 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(1) }) 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 tracy = use_embed('tracy') var RESPATH = 'scripts/modules/resources.js' var canonical = io.realdir(RESPATH) + 'resources.js' var content = io.slurp(RESPATH) var resources = js.eval(RESPATH, `(function setup_resources(){${content}})`).call({}) 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/modules/", "scripts/modules/ext/", ] // 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) { 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, file) 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 } 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` } io.mkdir('.prosperon') 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(msg) { pprint(msg, 0) } console.debug = function(msg) { pprint(msg, 1) } console.info = function(msg) { pprint(msg, 2) } console.warn = function(msg) { pprint(msg, 3) } console.log = function(msg) { pprint(msg, 2) } console.error = function(e) { if (!e) e = new Error() if (e instanceof Error) pprint(`${e.name} : ${e.message} ${e.stack}`, 4) else pprint(e,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[prosperon.DOC] = { doc: "The console object provides various logging, debugging, and output methods.", spam: "Output a spam-level message for very verbose logging.", debug: "Output a debug-level message.", info: "Output info level message.", warn: "Output warn level message.", error: "Output error level message, and print stacktrace.", panic: "Output a panic-level message and exit the program.", assert: "If the condition is false, print an error and panic.", critical: "Output critical level message, and exit game immediately.", write: "Write raw text to console.", say: "Write raw text to console, plus a newline.", log: "Output directly to in game console.", level: "Set level to output logging to console.", stack: "Output a stacktrace to console.", clear: "Clear console." } var BASEPATH = 'scripts/core/base.js' var script = io.slurp(BASEPATH) var fnname = "base" script = `(function ${fnname}() { ${script}; })` js.eval(BASEPATH, 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 = {} globalThis.actor = actor var so_ext switch(os.platform()) { case 'Windows': so_ext = '.dll' break default: so_ext = '.so' break } globalThis.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, file) { 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 (!/\breturn\b/.test(module)) throw new Error(`Malformed file: ${file}. Module section must end with a return statement.`); try { new Function(module)() } catch (e) { throw new Error(`Malformed file: ${file}. Module section must end with a return statement.\n` + e.message); } var pad = '\n'.repeat(module.split('\n').length+4) return { module, program: pad+parts[1] } } globalThis.Register = { registries: [], add_cb(name) { var n = {} var fns = [] n.register = function (fn, oname) { if (typeof fn !== '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) { fns.forEach(fn => { fn(...args) }) } 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 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") 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 new 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()) 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) { if (this[DEAD]) return 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") var DOCPATH = 'scripts/core/doc.js' var script = io.slurp(DOCPATH) var fnname = "doc" script = `(function ${fnname}() { ${script}; })` js.eval(DOCPATH, script)() var enet = use('enet') var util = use('util') var $_ = {} var host = enet.create_host() $_.host = host console.log(`made a host with port ${host.port()}`) globalThis.$_ = $_ var portal = undefined var receive_fn = undefined; $_.contact = function(callback, record) { } $_.connection = function(callback, actor, config) { } $_.portal = function(fn, port) { } $_.receiver = function(fn) { receive_fn = fn; } var underlings = {} $_.start = function(cb, prg, arg) { var guid = util.guid() underlings[guid] = cb os.createprocess(["./prosperon", "spawn", "--program", prg, "--overling", $_.host.port(), "--guid", guid]) } $_.stop = function(actor) { if (!actor) os.exit(0) actor.peer.send({ type:"stop", }) } $_.unneeded = function(fn, seconds) { } $_.delay = function(fn, seconds) { var id = os.addtimer(fn, seconds); return function() { os.removetimer(id); } } use('cmd')(prosperon.argv) function handle_receive(e) { var data = e.data switch(data.type) { case "greet": if (underlings[data.guid]) underlings[data.guid]({ type: "greet", data: {peer:e.peer} }) break case "stop": console.log("STOPPING!") os.exit(0) } } var hang = 0.016 while (1) { os.waitevent(_ => {}, hang) host.service(e => { switch(e.type) { case "connect": console.log(`connected. sending greet with guid ${prosperon.guid} to peer ${e.peer}`) e.peer.send({ type: "greet", guid: prosperon.guid }); break; case "receive": handle_receive(e); break; case "disconnect": console.log(`this peer left: ${e.peer}`) break } }, hang); } })()