var ex = this var input = use('input') var DEAD = Symbol() var GARBAGE = Symbol() var FILE = Symbol() var TIMERS = Symbol() var REGGIES = Symbol() var UNDERLINGS = Symbol() var OVERLING = Symbol() 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 } 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") var actor = {} actor.toString = function() { return this[FILE] } actor.spawn = function spawn(script, config, actor_context) { 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() // Make $_ available to the actor (either passed context or the engine's $_) var actor_dollar = actor_context || $_ Object.defineProperty(underling, '$_', { value: actor_dollar, writable:false, enumerable:false, configurable:false }) 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: function(script, config) { return actor.spawn.call(this, script, config, actor_dollar) }, 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 }) try { // Pass $_ as a parameter to actor scripts prog.prog_fn.call(underling, actor_dollar) } 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() } 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.` actor[UNDERLINGS] = new Set() ex[prosperon.DOC] = ` A set of utilities for iterating over a hierarchy of actor-like objects, as well as managing tag-based lookups. Objects are assumed to have a "objects" property, pointing to children or sub-objects, forming a tree. ` function eachobj(obj, fn) { var val = fn(obj) if (val) return val for (var o in obj.objects) { if (obj.objects[o] === obj) console.error(`Object ${obj.toString()} is referenced by itself.`) val = eachobj(obj.objects[o], fn) if (val) return val } } ex.all_objects = function (fn, startobj = world) { return eachobj(startobj, fn) } ex.all_objects[prosperon.DOC] = ` :param fn: A callback function that receives each object. If it returns a truthy value, iteration stops and that value is returned. :param startobj: The root object at which iteration begins, default is the global "world". :return: The first truthy value returned by fn, or undefined if none. Iterate over each object (and its sub-objects) in the hierarchy, calling fn for each one. ` ex.find_object = function (fn, startobj = world) {} ex.find_object[prosperon.DOC] = ` :param fn: A callback or criteria to locate a particular object. :param startobj: The root object at which search begins, default "world". :return: Not yet implemented. Intended to find a matching object within the hierarchy. ` var gtags = {} ex.tag_add = function (tag, obj) { gtags[tag] ??= new Set() gtags[tag].add(obj) } ex.tag_add[prosperon.DOC] = ` :param tag: A string tag to associate with the object. :param obj: The object to add under this tag. :return: None Associate the given object with the specified tag. Creates a new tag set if it does not exist. ` ex.tag_rm = function (tag, obj) { delete gtags[tag].delete(obj) } ex.tag_rm[prosperon.DOC] = ` :param tag: The tag to remove the object from. :param obj: The object to remove from the tag set. :return: None Remove the given object from the specified tag’s set, if it exists. ` ex.tag_clear_guid = function (obj) { for (var tag in gtags) gtags[tag].delete(obj) } ex.tag_clear_guid[prosperon.DOC] = ` :param obj: The object whose tags should be cleared. :return: None Remove the object from all tag sets. ` ex.objects_with_tag = function (tag) { if (!gtags[tag]) return [] return Array.from(gtags[tag]) } ex.objects_with_tag[prosperon.DOC] = ` :param tag: A string tag to look up. :return: An array of objects associated with the given tag. Retrieve all objects currently tagged with the specified tag. ` function parse_file(content, file) { if (!content) return {} if (content.match() if (!/^\s*---\s*$/m.test(content)) { var part = content.trim() if (part.match(/return\s+[^;]+;?\s*$/)) { return { module: part } } return { program: part } } var parts = content.split(/\n\s*---\s*\n/) 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] } } // path is the path of a module or script to resolve var script_fn = function script_fn(path, args) { 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) { // Create a context object with args var context = Object.create(parsed.module_ret) context.__args__ = args || [] var mod_script = `(function setup_${module_name}_module(){ var self = this; var $ = this; var exports = {}; var module = {exports: exports}; var define = undefined; var arg = this.__args__; ${parsed.module}})` var module_fn = js.eval(file, mod_script) parsed.module_ret = module_fn.call(context) 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() return ex