From de74b42be2c3f4798f5b02e16075d883a673bed7 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Tue, 27 Feb 2024 16:09:15 +0000 Subject: [PATCH] Add 'use' functionality; remove most global vars --- Makefile | 10 +-- doc/prosperon.org | 36 ++++++--- scripts/base.js | 64 ++++++++------- scripts/color.js | 6 ++ scripts/components.js | 6 +- scripts/debug.js | 9 ++- scripts/diff.js | 133 +------------------------------ scripts/editor.js | 28 ++++--- scripts/engine.js | 86 ++++++++++++-------- scripts/entity.js | 50 ++++++++---- scripts/gui.js | 51 ++++++------ scripts/input.js | 14 ++-- scripts/physics.js | 4 + scripts/std.js | 56 ++++--------- source/engine/jsffi.c | 161 ++++++++++++++++++++++++++------------ source/engine/resources.c | 1 + source/engine/script.c | 28 ++----- source/engine/script.h | 3 +- source/engine/yugine.c | 1 - 19 files changed, 365 insertions(+), 382 deletions(-) diff --git a/Makefile b/Makefile index f691a13..4a4cc2d 100755 --- a/Makefile +++ b/Makefile @@ -287,7 +287,7 @@ cdb: tools/cdb.c tools/libcdb.a source/engine/core.cdb.h: core.cdb xxd -i $< > $@ -SCRIPTS := $(shell ls scripts/*.js) +SCRIPTS := $(shell ls scripts/*.js*) SCRIPT_O := $(addsuffix o, $(SCRIPTS)) CORE != (ls icons/* fonts/*) CORE := $(CORE) $(SCRIPTS) @@ -301,7 +301,7 @@ CDB_C != find $(CDB) -name *.c packer: tools/packer.c tools/libcdb.a cc $^ -I$(CDB) -o packer -jso: tools/jso.c tools/libquickjs.a +jsc: tools/jso.c tools/libquickjs.a $(CC) $^ -lm -Iquickjs -o $@ tools/libquickjs.a: @@ -309,10 +309,6 @@ tools/libquickjs.a: make -C quickjs OPT=$(OPT) AR=$(AR) libquickjs.a cp -f quickjs/libquickjs.a tools -%.jso: %.js jso - @echo Making $@ from $< - ./jso $< > $@ - WINCC = x86_64-w64-mingw32-gcc #WINCC = i686-w64-mingw32-g++ .PHONY: crosswin @@ -329,7 +325,7 @@ crossmac: clean: @echo Cleaning project @rm -rf bin dist - @rm -f shaders/*.sglsl.h shaders/*.metal core.cdb jso cdb packer scripts/*.jso TAGS + @rm -f shaders/*.sglsl.h shaders/*.metal core.cdb jso cdb packer TAGS @make -C quickjs clean docs: doc/prosperon.org diff --git a/doc/prosperon.org b/doc/prosperon.org index 9188a25..5f6a6a8 100644 --- a/doc/prosperon.org +++ b/doc/prosperon.org @@ -48,20 +48,35 @@ The scripting language used in Prosperon is Javascript, with QuickJS. It is [[ht Javascript is used here mostly to set up objects and tear them down. Although computationally expensive tasks can be done using QuickJS, Prosperon makes it easy to extend with raw C. #+end_scholium -*** How Prosperon games are structued +*** How Prosperon games are structured +Prosperon schews the CommonJS and ES module systems for a custom one suitable for a computer game actor model. It is more restricted than either system, while retaining their full power. + Prosperon games are structured into two types of source files: - scripts - actors -When any source file is executed, it is executed in its own space. Global properties are accessible, but any declared variables and functions are not automatically added to the global space. To add obejcts to the global space, it must be explicitly done, by assigning them to ~global~ or ~globalThis~. +Scripts end with a return statement. A script can return a function, an object of functions, or any other sort of value. +#+begin_scholium +It is a common requirement to add some amount of functionality to an object. It can be easily done with a script file. #+begin_src -global.hellofn = function() { say("Hello, world!"); }; // hellofn is now callable anywhere +*script.js* +function hello() { say("hello"); }; +return hello; #+end_src +#+begin_src +var o = {}; +o.use("hello.js"); +o.hello(); +#+end_src +The ~use~ function on any object loads a module, and ~assign~s its return to the object. +#+end_scholium -When a *script* is loaded, its statements are executed in order, in the context of the containing source. The containing source is accessible via the ~$~ variable. When ~load~ is called, an optional object is permitted to be supplied as the ~$~. This makes writing scripts with mixin functionality simple. +Scripts are loaded into memory only once. Further ~use~ statements only generate references to the statements. Because *scripts* are executed in a lambda environment, any ~var~ declared inside the script files are effectively private variables, persistent variables. -The parameter ~$$~ is given to a persistent object that is available whenever a script is executing, per script. +In a *script*, ~this~ refers to ~undefined~. It is nothng. + +In an *actor* source file, ~this~ refers to the actor. Actors do not end in a ~return~ statement. *actor* source is intended to set up a new agent in your game. Set up the new entity by loading modules and assigning functions to ~this~. *** Script entry points The first way you can customize Prosperon is by adding scripts to the folder you're running it from. Any file ending with *.js* is a *script* which can be ran by Prosperon. @@ -92,9 +107,11 @@ The fundamental tool for building in Prosperon is the actor system. Actors run i The most masterful actor is the *Empyrean*. The first actor you create will have the Empyrean as its master. Subsequent actors can use any other actor as its master. -| fn | description | -|--------------------+----------------------------------------------------------| -| delay(fn, seconds) | Calls 'fn' after 'seconds' with the context of the actor | +| fn | description | +|---------------------+----------------------------------------------------------| +| spawn(text, config) | Creates an actor as the padawan of this one, using text | +| kill() | Kills an actor | +| delay(fn, seconds) | Calls 'fn' after 'seconds' with the context of the actor | *** Actor Lifetime When an actor dies, all of the actors that have it as their master[fn::What a mouthful!] will die as well. @@ -103,7 +120,6 @@ When an actor dies, all of the actors that have it as their master[fn::What a mo Actors get fragments of time called a *turn*. Actors which belong to different systems can have different lengths of turns. - *** Actor files Actor files end with the extension *.jso*[fn::"Javascript object".]. They list a series of functions to call on a newly formed actor. Actors have a number of useful functions which are called as defined. @@ -168,6 +184,8 @@ Game worlds are made of entities. Entities are a type of actor with a number of The first and most masterful entity is the Primum. The Primum has no components, and its rotation and position are zero. It defines the center of the game. #+end_scholium +In editor mode, when an entity moves, all of its *padawans* also move. When the game is actively simulating, this only holds if there are physical constraints between them. + *** Adding Components Entities can have *components*. Components are essentially javascript wrappers over C code into the engine. Scripting is done to set the components up on entities, after which most of the work is done by the C plugin. diff --git a/scripts/base.js b/scripts/base.js index fe7718e..bdffae0 100644 --- a/scripts/base.js +++ b/scripts/base.js @@ -572,6 +572,16 @@ Object.defineProperty(Object.prototype, 'obscure', { } }); +Object.defineProperty(Object.prototype, 'mixin', { + value: function(obj) { + if (typeof obj === 'string') { + obj = use(obj); + if (!obj) return; + } + Object.assign(this, obj); + }, +}); + Object.defineProperty(Object.prototype, 'hasOwn', { value: function(x) { return this.hasOwnProperty(x); } }); @@ -1329,7 +1339,7 @@ Math.randomint = function(max) { return Math.clamp(Math.floor(Math.random() * ma /* BOUNDINGBOXES */ var bbox = {}; -function cwh2bb(c, wh) { +bbox.fromcwh = function(c, wh) { return { t: c.y+(wh.y/2), b: c.y-(wh.y/2), @@ -1338,7 +1348,7 @@ function cwh2bb(c, wh) { }; }; -function points2bb(points) { +bbox.frompoints = function(points) { var b= {t:0,b:0,l:0,r:0}; points.forEach(function(x) { @@ -1351,7 +1361,7 @@ function points2bb(points) { return b; }; -function bb2points(bb) +bbox.topoints = function(bb) { return [ [bb.l,bb.t], @@ -1361,20 +1371,7 @@ function bb2points(bb) ]; } -function points2cwh(start,end) -{ - var c = []; - c[0] = (end[0] - start[0]) / 2; - c[0] += start[0]; - c[1] = (end[1] - start[1]) / 2; - c[1] += start[1]; - var wh = []; - wh[0] = Math.abs(end[0] - start[0]); - wh[1] = Math.abs(end[1] - start[1]); - return {c: c, wh: wh}; -} - -function bb2cwh(bb) { +bbox.tocwh = function(bb) { if (!bb) return undefined; var cwh = {}; @@ -1386,7 +1383,11 @@ function bb2cwh(bb) { return cwh; }; -function pointinbb(bb, p) +bbox.towh = function(bb) { + return [bb.r-bb.l, bb.t-bb.b]; +}; + +bbox.pointin = function(bb, p) { if (bb.t < p.y || bb.b > p.y || bb.l > p.x || bb.r < p.x) return false; @@ -1394,7 +1395,7 @@ function pointinbb(bb, p) return true; } -function movebb(bb, pos) { +bbox.move = function(bb, pos) { var newbb = Object.assign({}, bb); newbb.t += pos.y; newbb.b += pos.y; @@ -1403,7 +1404,7 @@ function movebb(bb, pos) { return newbb; }; -function bb_expand(oldbb, x) { +bbox.expand = function(oldbb, x) { if (!oldbb || !x) return; var bb = {}; Object.assign(bb, oldbb); @@ -1416,7 +1417,7 @@ function bb_expand(oldbb, x) { return bb; }; -function bl2bb(bl, wh) +bbox.blwh = function(bl,wh) { return { b: bl.y, @@ -1426,17 +1427,15 @@ function bl2bb(bl, wh) }; } -function bb_from_objects(objs) { +bbox.blwh.doc = "Bounding box from (bottom left, width height)"; + +bbox.fromobjs = function(objs) +{ var bb = objs[0].boundingbox; - objs.forEach(function(obj) { bb = bb_expand(bb, obj.boundingbox); }); + objs.forEach(function(obj) { bb = bbox.expand(bb, obj.boundingbox); }); return bb; }; -var Boundingbox = {}; -Boundingbox.width = function(bb) { return bb.r - bb.l; }; -Boundingbox.height = function(bb) { return bb.t - bb.b; }; -Boundingbox.bl = function(bb) { return [bb.l, bb.b] }; - /* VECTORS */ var Vector = { length(v) { @@ -1527,3 +1526,12 @@ Math.sortpointsccw = function(points) return ccw.map(function(x) { return x.add(cm); }); } + +return { + convert, + time, + json, + Vector, + bbox +}; + diff --git a/scripts/color.js b/scripts/color.js index d7bd5c6..8c5d997 100644 --- a/scripts/color.js +++ b/scripts/color.js @@ -197,3 +197,9 @@ ColorMap.doc = { }; Object.freeze(ColorMap); + +return { + Color, + esc, + ColorMap +} diff --git a/scripts/components.js b/scripts/components.js index aaf77a5..99bb5aa 100644 --- a/scripts/components.js +++ b/scripts/components.js @@ -367,7 +367,7 @@ component.polygon2d = Object.copy(collider2d, { flipy: false, boundingbox() { - return points2bb(this.spoints()); + return bbox.frompoints(this.spoints()); }, hides: ['id', 'shape', 'gameobject'], @@ -538,7 +538,7 @@ component.edge2d = Object.copy(collider2d, { return Spline.sample_angle(this.type, spoints, this.angle); }, - boundingbox() { return points2bb(this.points.map(x => x.scale(this.gameobject.scale))); }, + boundingbox() { return bbox.frompoints(this.points.map(x => x.scale(this.gameobject.scale))); }, hides: ['gameobject', 'id', 'shape'], _enghook: make_edge2d, @@ -822,3 +822,5 @@ component.circle2d.impl = Object.mix({ set pos(x) { this.offset = x; }, }, collider2d.impl); + +return {component}; diff --git a/scripts/debug.js b/scripts/debug.js index 3fc16d9..126b603 100644 --- a/scripts/debug.js +++ b/scripts/debug.js @@ -93,7 +93,7 @@ var Debug = { boundingbox(bb, color) { color ??= Color.white; - cmd_points(0, bb2points(bb), color); + cmd_points(0, bbox.topoints(bb), color); }, numbered_point(pos, n, color) { @@ -211,8 +211,6 @@ Object.assign(profile, { get fps() { return sys_cmd(8); }, }); - - profile.test = { barecall() { profile(0); }, unpack_num(n) { profile(1,n); }, @@ -419,3 +417,8 @@ Debug.api.print_doc = function(name) return mdoc; } + +return { + Debug, + Time, +} diff --git a/scripts/diff.js b/scripts/diff.js index b310997..410136a 100644 --- a/scripts/diff.js +++ b/scripts/diff.js @@ -1,114 +1,5 @@ function deep_copy(from) { return json.decode(json.encode(from)); } -var walk_up_get_prop = function(obj, prop, endobj) { - var props = []; - var cur = obj; - while (cur !== Object.prototype) { - if (cur.hasOwn(prop)) - props.push(cur[prop]); - - cur = cur.__proto__; - } - - return props; -}; - -/* Deeply remove source keys from target, not removing objects */ -function unmerge(target, source) { - for (var key in source) { - if (typeof source[key] === 'object' && !Array.isArray(source[key])) - unmerge(target[key], source[key]); - else - delete target[key]; - } -}; - -/* Deeply merge two objects, not clobbering objects on target with objects on source */ -function deep_merge(target, source) -{ - console.warn("Doing a deep merge ..."); - for (var key in source) { - if (typeof source[key] === 'object' && !Array.isArray(source[key])) { - console.warn(`Deeper merge on ${key}`); - deep_merge(target[key], source[key]); - } - else { - console.warn(`Setting key ${key}`); - target[key] = source[key]; - } - } -}; - - -function equal(x,y) { - if (typeof x === 'object') - return json.encode(x) === json.encode(y); - - return x === y; -}; - -function diffassign(target, from) { - if (Object.empty(from)) return; - - for (var e in from) { - if (typeof from[e] === 'object') { - if (!target.hasOwnProperty(e)) - target[e] = from[e]; - else - diffassign(target[e], from[e]); - } else { - if (from[e] === "DELETE") { - delete target[e]; - } else { - target[e] = from[e]; - } - } - } -}; - -function objdiff(from,to) -{ - var ret = {}; - - if (!to) - return ediff(from,{}); - - Object.entries(from).forEach(function([key,v]) { - if (typeof v === 'function') return; - if (typeof v === 'undefined') return; - - if (Array.isArray(v)) { - if (!Array.isArray(to[key]) || v.length !== to[key].length) - ret[key] = Object.values(ediff(v, [])); - - var diff = ediff(from[key], to[key]); - if (diff && !Object.empty(diff)) - ret[key] = Object.values(ediff(v,[])); - - return; - } - - if (typeof v === 'object') { - var diff = ediff(v, to[key]); - if (diff && !Object.empty(diff)) - ret[key] = diff; - return; - } - - if (typeof v === 'number') { - if (!to || v !== to[key]) - ret[key] = v; - return; - } - - if (!to || v !== to[key]) - ret[key] = v; - }); - if (Object.empty(ret)) return undefined; - - return ret; -} - function valdiff(from,to) { if (typeof from !== typeof to) return from; @@ -129,17 +20,6 @@ function valdiff(from,to) return undefined; } -/* Returns the json encoded object, assuming it has an implementation it must check through */ -function impl_json(obj) -{ - -} - -function ndiff(from,to) -{ - -} - function ediff(from,to) { var ret = {}; @@ -189,13 +69,6 @@ function ediff(from,to) ediff.doc = "Given a from and to object, returns an object that, if applied to from, will make it the same as to. Does not include deletion; it is only additive."; -function subdiff(from,to) -{ - -} - -subdiff.doc = "Given a from and to object, returns a list of properties that must be deleted from the 'from' object to make it like the 'to' object."; - function samediff(from, to) { var same = []; @@ -225,6 +98,8 @@ function samediff(from, to) samediff.doc = "Given a from and to object, returns an array of keys that are the same on from as on to."; -function cleandiff(from, to) -{ +return { + deep_copy, + ediff, + samediff, } diff --git a/scripts/editor.js b/scripts/editor.js index 9f39604..927d0ee 100644 --- a/scripts/editor.js +++ b/scripts/editor.js @@ -2,7 +2,7 @@ Editor-only variables on objects selectable */ -prototypes.generate_ur('.'); +//prototypes.generate_ur('.'); var editor = { toString() { return "editor"; }, @@ -166,7 +166,7 @@ var editor = { }, zoom_to_bb(bb) { - var cwh = bb2cwh(bb); + var cwh = bbox.tocwh(bb); var xscale = cwh.wh.x / Window.width; var yscale = cwh.wh.y / Window.height; @@ -221,7 +221,7 @@ var editor = { editor.cbs.push(Register.gui.register(editor.gui.bind(editor))); editor.cbs.push(Register.draw.register(editor.draw.bind(editor))); editor.cbs.push(Register.debug.register(editor.ed_debug.bind(editor))); - editor.cbs.push(Register.update.register(gui_controls.update, gui_controls)); + editor.cbs.push(Register.update.register(GUI.controls.update, GUI.controls)); this.desktop = Primum.spawn(ur.arena); Primum.rename_obj(this.desktop.toString(), "desktop"); @@ -377,7 +377,7 @@ var editor = { x.gizmo(); }); - render.line(bb2points(cwh2bb([0,0],[Game.native.x,Game.native.y])).wrapped(1), Color.yellow); + render.line(bbox.topoints(bbox.fromcwh([0,0],[Game.native.x,Game.native.y])).wrapped(1), Color.yellow); /* Draw selection box */ if (this.sel_start) { @@ -390,9 +390,9 @@ var editor = { var wh = []; wh[0] = Math.abs(endpos[0] - this.sel_start[0]); wh[1] = Math.abs(endpos[1] - this.sel_start[1]); - var bb = cwh2bb(c,wh); + var bb = bbox.fromcwh(c,wh); Debug.boundingbox(bb, Color.Editor.select.alpha(0.1)); - render.line(bb2points(bb).wrapped(1), Color.white); + render.line(bbox.topoints(bb).wrapped(1), Color.white); } }, @@ -718,7 +718,7 @@ editor.inputs.q.doc = "Toggle help for the selected component."; editor.inputs.f = function() { if (editor.selectlist.length === 0) return; var bb = editor.selectlist[0].boundingbox(); - editor.selectlist.forEach(function(obj) { bb = bb_expand(bb, obj.boundingbox()); }); + editor.selectlist.forEach(function(obj) { bb = bbox.expand(bb, obj.boundingbox()); }); editor.zoom_to_bb(bb); }; editor.inputs.f.doc = "Find the selected objects."; @@ -972,11 +972,9 @@ editor.inputs.lm.released = function() { var sel = editor.try_select(); if (sel) selects.push(sel); } else { - var box = points2cwh(editor.sel_start, Mouse.worldpos); + var box = bbox.frompoints([editor.sel_start, Mouse.worldpos]); - box.pos = box.c; - - var hits = physics.box_query(box); + var hits = physics.box_query(bbox.tocwh(box)); hits.forEach(function(x, i) { var obj = editor.do_select(x); @@ -1466,10 +1464,10 @@ inputpanel.inputs['C-k'] = function() { inputpanel.inputs.lm = function() { - gui_controls.check_submit(); + GUI.controls.check_submit(); } -load("scripts/textedit.js"); +//load("scripts/textedit.js"); var replpanel = Object.copy(inputpanel, { title: "", @@ -1998,3 +1996,7 @@ if (io.exists("editor.config")) Game.stop(); Game.editor_mode(true); Debug.draw_phys(true); + +return { + editor +} diff --git a/scripts/engine.js b/scripts/engine.js index 8cf268d..48f407a 100644 --- a/scripts/engine.js +++ b/scripts/engine.js @@ -1,44 +1,54 @@ "use math"; globalThis.global = globalThis; -function eval_env(script, env) +function use(file) +{ + if (use.files[file]) return use.files[file]; + + var c = io.slurp(file); + + var script = `(function() { ${c} })();`; + use.files[file] = cmd(123,script,global,file); + + return use.files[file]; +} +use.files = {}; + +function eval_env(script, env, file) { env ??= {}; -// script = `function() { ${script} }.call();`; + file ??= "SCRIPT"; +// script = `(function() { ${script} })();`; + eval(`(function() { ${script} }).call(env);`); +// cmd(123,script,global,file); // return eval(script); - return function(str) { return eval(str); }.call(env, script); +// return function(str) { return eval(str); }.call(env, script); } eval_env.dov = `Counterpart to /load_env/, but with a string.`; function load_env(file,env) { env ??= global; +// var script = io.slurp(file); var script = io.slurp(file); - eval_env(script, env); + eval_env(script, env, file); // cmd(16, file, env); // var script = io.slurp(file); // cmd(123, script, env, file); } load_env.doc = `Load a given file with 'env' as **this**. Does not add to the global namespace.`; -function load(file) { return load_env(file);} -load.doc = `Load a given script file into the global namespace.`; +var load = use; +Object.assign(global, use("scripts/base.js")); +global.obscure('global'); +global.mixin(use("scripts/std.js")); +global.mixin(use("scripts/diff.js")); -load("scripts/base.js"); -load("scripts/std.js"); - - -load("scripts/diff.js"); console.level = 1; -load("scripts/color.js"); - -function bb2wh(bb) { - return [bb.r-bb.l, bb.t-bb.b]; -}; - +global.mixin(use("scripts/color.js")); var prosperon = {}; prosperon.version = cmd(255); @@ -94,7 +104,7 @@ Range is given by a semantic versioning number, prefixed with nothing, a ~, or a ^ means that MAJOR must match exactly, but any MINOR and PATCH greater or equal is valid.`; -load("scripts/gui.js"); +global.mixin(use("scripts/gui.js")); var timer = { update(dt) { @@ -170,11 +180,11 @@ render.device = { render.device.doc = `Device resolutions given as [x,y,inches diagonal].`; -load("scripts/physics.js"); -load("scripts/input.js"); -load("scripts/sound.js"); -load("scripts/ai.js"); -load("scripts/geometry.js"); +global.mixin(use("scripts/physics.js")); +global.mixin(use("scripts/input.js")); +global.mixin(use("scripts/sound.js")); +global.mixin(use("scripts/ai.js")); +global.mixin(use("scripts/geometry.js")); var Register = { kbm_input(mode, btn, state, ...args) { @@ -310,18 +320,21 @@ var Window = { }; Window.screen2world = function(screenpos) { - if (Game.camera) - return Game.camera.view2world(screenpos); +// if (Game.camera) +// return Game.camera.view2world(screenpos); return screenpos; } -Window.world2screen = function(worldpos) { return Game.camera.world2view(worldpos); } +Window.world2screen = function(worldpos) { + return worldpos; + return Game.camera.world2view(worldpos); +} Window.icon = function(path) { cmd(90, path); }; Window.icon.doc = "Set the icon of the window using the PNG image at path."; -load("scripts/debug.js"); -load('scripts/spline.js'); -load("scripts/components.js"); +global.mixin(use("scripts/debug.js")); +global.mixin(use("scripts/spline.js")); +global.mixin(use("scripts/components.js")); var Game = { engine_start(fn) { @@ -355,7 +368,10 @@ var Game = { }, - quit() { sys_cmd(0); }, + quit() { + sys_cmd(0); + return; + }, pause() { sys_cmd(3); }, stop() { Game.pause(); }, step() { sys_cmd(4);}, @@ -404,7 +420,7 @@ Window.doc.boundingbox = "Boundingbox of the window, with top and right being it Register.update.register(Game.exec, Game); -load("scripts/entity.js"); +global.mixin(use("scripts/entity.js")); function world_start() { globalThis.Primum = Object.create(gameobject); @@ -424,9 +440,11 @@ function world_start() { gameobject.body = make_gameobject(); cmd(113,gameobject.body, gameobject); Object.hide(gameobject, 'timescale'); + + global.world = Primum; } -load("scripts/physics.js"); +global.mixin(use("scripts/physics.js")); Game.view_camera = function(cam) { @@ -434,6 +452,10 @@ Game.view_camera = function(cam) cmd(61, Game.camera.body); } +prototypes.generate_ur('scripts/camera.jso'); + Window.title(`Prosperon v${prosperon.version}`); Window.width = 1280; Window.height = 720; + + diff --git a/scripts/entity.js b/scripts/entity.js index 875fff2..42b1594 100644 --- a/scripts/entity.js +++ b/scripts/entity.js @@ -12,34 +12,37 @@ prosperon.obj_unique_name = function(name, obj) } var actor = {}; +var a_db = {}; + actor.spawn = function(script, config){ - if (typeof script !== 'string') return; + if (typeof script !== 'string') return undefined; + if (!a_db[script]) a_db[script] = io.slurp(script); var padawan = Object.create(actor); - eval_env(script, padawan); + eval_env(a_db[script], padawan); if (typeof config === 'object') - Object.merge(padawan, config); + Object.merge(padawan,config); padawan.padawans = []; padawan.timers = []; padawan.master = this; - Object.hide(padawan, "master","timers"); + Object.hide(padawan, "master","timers", "padawans"); this.padawans.push(padawan); return padawan; }; actor.spawn.doc = `Create a new actor, using this actor as the master, initializing it with 'script' and with data (as a JSON or Nota file) from 'config'.`; -actor.die = function(e){ - e.kill(); -}; - actor.timers = []; actor.kill = function(){ + if (this.__dead__) return; this.timers.forEach(t => t.kill()); - delete this.master[this.toString()]; + if (this.master) + delete this.master[this.toString()]; this.padawans.forEach(p => p.kill()); + this.padawans = []; this.__dead__ = true; + if (typeof this.die === 'function') this.die(); }; actor.kill.doc = `Remove this actor and all its padawans from existence.`; @@ -73,6 +76,13 @@ actor.remaster = function(to){ to.padawans.push(this); }; +global.app = Object.create(actor); + +app.die = function() +{ + Game.quit(); +} + var gameobject_impl = { get pos() { Debug.assert(this.level, `Entity ${this.toString()} has no level.`); @@ -88,6 +98,7 @@ var gameobject_impl = { Debug.assert(this.level, `No level set on ${this.toString()}`); return this.worldangle() - this.level.worldangle(); }, + set angle(x) { var diff = x - this.angle; @@ -329,8 +340,10 @@ var gameobject = { spawn(ur, data) { ur ??= gameobject; - if (typeof ur === 'string') - ur = prototypes.get_ur(ur); + if (typeof ur === 'string') { + //ur = prototypes.get_ur(ur); + + } var go = ur.make(this, data); Object.hide(this, go.toString()); @@ -502,11 +515,11 @@ var gameobject = { var bb = boxes.shift(); - boxes.forEach(function(x) { bb = bb_expand(bb, x); }); + boxes.forEach(function(x) { bb = bbox.expand(bb, x); }); - bb = movebb(bb, this.pos); + bb = bbox.move(bb, this.pos); - return bb ? bb : cwh2bb([0,0], [0,0]); + return bb ? bb : bbox.fromcwh([0,0], [0,0]); }, /* The unique components of this object. Its diff. */ @@ -606,7 +619,7 @@ var gameobject = { right() { return [1,0].rotate(this.angle);}, left() { return [-1,0].rotate(this.angle); }, - make(level, data) { + make() { var obj = Object.create(this); obj.make = undefined; @@ -998,3 +1011,10 @@ prototypes.ur_pullout_folder = function(ur) if (io.exists(p)) */ } + +return { + gameobject, + actor, + prototypes, + ur +} diff --git a/scripts/gui.js b/scripts/gui.js index 8af5e98..7ff1d63 100644 --- a/scripts/gui.js +++ b/scripts/gui.js @@ -38,7 +38,7 @@ var GUI = { color ??= Color.black; var wh = cmd(64,path); gui_img(path,pos, [1.0,1.0], 0.0, 0.0, [0.0,0.0], 0.0, Color.black); - return cwh2bb([0,0], wh); + return bbox.fromcwh([0,0], wh); }, input_lmouse_pressed() { @@ -69,11 +69,11 @@ var GUI = { } }; -var gui_controls = {}; -gui_controls.toString = function() { return "GUI controls"; }; -gui_controls.update = function() { }; +GUI.controls = {}; +GUI.controls.toString = function() { return "GUI controls"; }; +GUI.controls.update = function() { }; -gui_controls.set_mum = function(mum) +GUI.controls.set_mum = function(mum) { mum.selected = true; @@ -82,22 +82,22 @@ gui_controls.set_mum = function(mum) this.selected = mum; } -gui_controls.check_bb = function(mum) +GUI.controls.check_bb = function(mum) { - if (pointinbb(mum.bb, Mouse.pos)) - gui_controls.set_mum(mum); + if (bbox.pointin(mum.bb, Mouse.pos)) + GUI.controls.set_mum(mum); } -gui_controls.inputs = {}; -gui_controls.inputs.fallthru = false; -gui_controls.inputs.mouse = {}; -gui_controls.inputs.mouse.move = function(pos,dpos) +GUI.controls.inputs = {}; +GUI.controls.inputs.fallthru = false; +GUI.controls.inputs.mouse = {}; +GUI.controls.inputs.mouse.move = function(pos,dpos) { } -gui_controls.inputs.mouse.scroll = function(scroll) +GUI.controls.inputs.mouse.scroll = function(scroll) { } -gui_controls.check_submit = function() { +GUI.controls.check_submit = function() { if (this.selected && this.selected.action) this.selected.action(this.selected); } @@ -159,7 +159,7 @@ Mum.text = Mum.extend({ draw(cursor, cnt) { cnt ??= Mum; if (this.hide) return; - if (this.selectable) gui_controls.check_bb(this); + if (this.selectable) GUI.controls.check_bb(this); this.caret ??= -1; /* if (!this.bb) @@ -179,14 +179,14 @@ Mum.text = Mum.extend({ }, update_bb(cursor) { - this.bb = movebb(this.bb, cursor.sub(this.wh.scale(this.anchor))); + this.bb = bbox.move(this.bb, cursor.sub(this.wh.scale(this.anchor))); }, calc_bb(cursor) { var bb = cmd(118,this.str, this.font_size, this.width); - this.wh = bb2wh(bb); + this.wh = bbox.towh(bb); var pos = cursor.add(this.wh.scale([0,1].sub(this.anchor))).add(this.offset); - this.bb = movebb(bb,pos.add([this.wh.x/2,0])); + this.bb = bbox.move(bb,pos.add([this.wh.x/2,0])); }, start() { this.calc_bb([0,0]); @@ -205,17 +205,17 @@ Mum.button = Mum.text._int.extend({ Mum.window = Mum.extend({ start() { this.wh = [this.width, this.height]; - this.bb = cwh2bb([0,0], this.wh); + this.bb = bbox.fromcwh([0,0], this.wh); }, draw(cursor, cnt) { cnt ??= Mum; var p = cursor.sub(this.wh.scale(this.anchor)).add(this.padding); GUI.window(p,this.wh, this.color); - this.bb = bl2bb(p, this.wh); + this.bb = bbox.blwh(p, this.wh); GUI.flush(); GUI.scissor(p.x,p.y,this.wh.x,this.wh.y); this.max_width = this.width; - if (this.selectable) gui_controls.check_bb(this); + if (this.selectable) GUI.controls.check_bb(this); var pos = [this.bb.l, this.bb.t].add(this.padding); this.items.forEach(function(item) { if (item.hide) return; @@ -249,8 +249,8 @@ Mum.image = Mum.extend({ }, calc_bb(pos) { - this.bb = cwh2bb(this.wh.scale([0.5,0.5]), wh); - this.bb = movebb(this.bb, pos.sub(this.wh.scale(this.anchor))); + this.bb = bbox.fromcwh(this.wh.scale([0.5,0.5]), wh); + this.bb = bbox.move(this.bb, pos.sub(this.wh.scale(this.anchor))); } }); @@ -287,3 +287,8 @@ Mum.debug_colors = { }; Object.values(Mum.debug_colors).forEach(function(v) { v.a = 100; }); + +return { + GUI, + Mum +}; diff --git a/scripts/input.js b/scripts/input.js index 2d9d1be..960b7d9 100644 --- a/scripts/input.js +++ b/scripts/input.js @@ -1,8 +1,3 @@ -var input = { - setgame() { cmd(77); }, - setnuke() { cmd(78); }, -}; - var Mouse = { get pos() { return cmd(45); }, screenpos() { return cmd(45); }, @@ -55,6 +50,8 @@ var Keys = { super() { return cmd(50, 343); }, }; +var input = {}; + input.state2str = function(state) { if (typeof state === 'string') return state; switch (state) { @@ -229,3 +226,10 @@ Player.uncontrol.doc = "Uncontrol a previously controlled object."; Player.print_pawns.doc = "Print out a list of the current pawn control stack."; Player.doc = {}; Player.doc.players = "A list of current players."; + +return { + Mouse, + Keys, + input, + Player, +}; diff --git a/scripts/physics.js b/scripts/physics.js index 223bc6e..02c6a74 100644 --- a/scripts/physics.js +++ b/scripts/physics.js @@ -71,3 +71,7 @@ physics.warp.damp = function() { return cmd(254); } physics.gravity = physics.warp.gravity(); physics.damp = physics.warp.damp(); + +return { + physics +} diff --git a/scripts/std.js b/scripts/std.js index 33f98b8..0187d20 100644 --- a/scripts/std.js +++ b/scripts/std.js @@ -144,30 +144,11 @@ console.doc = { /* io path rules. Starts with, meaning: - "@": playerpath - "/": game room - "#": Force look locally (instead of in db first) - - This is combined with others. #/, #@, etc - "": Local path relative to script defined in + "@": user path + "/": game path */ -var io = { - exists(file) { return cmd(65, file);}, - slurp(file) { - if (io.exists(file)) - return cmd(38,file); - else - throw new Error(`File ${file} does not exist; can't slurp`); - }, - slurpbytes(file) { - return cmd(81, file); - }, - slurpwrite(file, data) { - if (data.byteLength) - cmd(60, data, file); - else - return cmd(39, data, file); - }, +io.mixin({ extensions(ext) { var paths = io.ls(); paths = paths.filter(function(str) { return str.ext() === ext; }); @@ -179,20 +160,7 @@ var io = { run_bytecode(byte_file) { return cmd(261, byte_file); }, - ls() { return cmd(66); }, - /* Only works on text files currently */ - cp(f1, f2) { - cmd(166, f1, f2); - }, - mv(f1, f2) { - return cmd(163, f1, f2); - }, - rm(f) { - return cmd(f); - }, - mkdir(dir) { - cmd(258, dir); - }, + glob(pat) { var paths = io.ls(); pat = pat.replaceAll(/([\[\]\(\)\^\$\.\|\+])/g, "\\$1"); @@ -203,7 +171,7 @@ var io = { var regex = new RegExp("^"+pat+"$", ""); return paths.filter(str => str.match(regex)); }, -}; +}); io.doc = { doc: "Functions for filesystem input/output commands.", @@ -245,8 +213,8 @@ Cmdline.register_order("edit", function() { } Game.engine_start(function() { - load("scripts/editor.js"); - load("editorconfig.js"); + global.mixin(use("scripts/editor.js")); + use("editorconfig.js"); editor.enter_editor(); }); }, "Edit the project in this folder. Give it the name of an UR to edit that specific object.", "?UR?"); @@ -283,7 +251,6 @@ Cmdline.register_order("play", function() { load("game.js"); if (project.icon) Window.icon(project.icon); if (project.title) Window.title(project.title); - say(project.title); }); }, "Play the game present in this folder."); @@ -446,3 +413,12 @@ Cmdline.register_order("clean", function(argv) { Cmdline.register_cmd("l", function(n) { console.level = n; }, "Set log level."); + +return { + console, + Resources, + say, + io, + Cmdline, + cmd_args +}; diff --git a/source/engine/jsffi.c b/source/engine/jsffi.c index 3f428f9..e09d0d0 100644 --- a/source/engine/jsffi.c +++ b/source/engine/jsffi.c @@ -828,18 +828,6 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv) id2sprite(js2int(argv[1]))->t.pos = js2vec2(argv[2]); break; - case 38: - str = JS_ToCString(js, argv[1]); - d1 = slurp_text(str,NULL); - ret = JS_NewString(js, d1); - break; - - case 39: - str = JS_ToCStringLen(js, &plen, argv[1]); - str2 = JS_ToCString(js, argv[2]); - ret = JS_NewInt64(js, slurp_write(str, str2, plen)); - break; - case 40: js2gameobject(argv[1])->filter.categories = js2bitmask(argv[2]); gameobject_apply(js2gameobject(argv[1])); @@ -910,13 +898,6 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv) ret = JS_NewInt64(js, point2segindex(js2vec2(argv[1]), v1, js2number(argv[3]))); break; - case 60: - str = JS_GetArrayBuffer(js, &plen, argv[1]); - str2 = JS_ToCString(js, argv[2]); - ret = JS_NewInt64(js, slurp_write(str, str2, plen)); - str = NULL; - break; - case 61: set_cam_body(js2gameobject(argv[1])->body); break; @@ -934,11 +915,6 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv) ret = vec2js(tex_get_dimensions(texture_pullfromfile(str))); break; - case 65: - str = JS_ToCString(js, argv[1]); - ret = JS_NewBool(js, fexists(str)); - break; - case 66: ret = strarr2js(ls(",")); break; @@ -993,12 +969,6 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv) arrfree(ids); break; - case 81: - str = JS_ToCString(js, argv[1]); - d1 = slurp_file(str, &plen); - ret = JS_NewArrayBufferCopy(js, d1, plen); - break; - case 82: gameobject_draw_debug(js2gameobject(argv[1])); break; @@ -1114,13 +1084,11 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv) case 121: ret = number2js(get_timescale()); break; - case 122: - break; case 123: str = JS_ToCString(js, argv[1]); str2 = JS_ToCString(js, argv[3]); - script_eval_w_env(str, argv[2], str2); + ret = eval_file_env(str, str2, argv[2]); break; case 124: @@ -1264,21 +1232,11 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv) str = JS_ToCString(js, argv[1]); ret = int2js(remove(str)); break; - case 163: - str = JS_ToCString(js,argv[1]); - str2 = JS_ToCString(js,argv[2]); - ret = int2js(rename(str, str2)); - break; + case 164: unplug_node(js2ptr(argv[1])); break; - case 165: - break; - case 166: - str = js2str(argv[1]); - str2 = js2str(argv[2]); - ret = int2js(cp(str, str2)); - break; + case 168: js2gameobject(argv[1])->timescale = js2number(argv[2]); break; @@ -1470,10 +1428,7 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv) case 257: engine_start(argv[1]); break; - case 258: - str = js2str(argv[1]); - ret = int2js(mkdir(str, 0777)); - break; + case 259: script_gc(); break; @@ -1942,7 +1897,112 @@ JSValue js_os_env(JSContext *js, JSValueConst this, int argc, JSValue *argv) static const JSCFunctionListEntry js_os_funcs[] = { MIST_CFUNC_DEF("cwd", 0, js_os_cwd), - MIST_CFUNC_DEF("env", 1, js_os_env), + MIST_CFUNC_DEF("env", 1, js_os_env), +}; + +JSValue js_io_exists(JSContext *js, JSValueConst this, int argc, JSValue *argv) +{ + char *file = JS_ToCString(js, argv[0]); + JSValue ret = JS_NewBool(js, fexists(file)); + JS_FreeCString(js,file); + return ret; +} + +JSValue js_io_ls(JSContext *js, JSValueConst this) +{ + return strarr2js(ls(",")); +} + +JSValue js_io_cp(JSContext *js, JSValueConst this, int argc, JSValue *argv) +{ + char *f1, f2; + f1 = JS_ToCString(js, argv[0]); + f2 = JS_ToCString(js, argv[1]); + JSValue ret = int2js(cp(f1,f2)); + JS_FreeCString(js,f1); + JS_FreeCString(js,f2); + return ret; +} + +JSValue js_io_mv(JSContext *js, JSValueConst this, int argc, JSValue *argv) +{ + char *f1, f2; + f1 = JS_ToCString(js, argv[0]); + f2 = JS_ToCString(js, argv[1]); + JSValue ret = int2js(rename(f1,f2)); + JS_FreeCString(js,f1); + JS_FreeCString(js,f2); + return ret; +} + +JSValue js_io_rm(JSContext *js, JSValueConst this, int argc, JSValue *argv) +{ + char *file = JS_ToCString(js, argv[0]); + JS_FreeCString(js,file); + return JS_UNDEFINED; +} + +JSValue js_io_mkdir(JSContext *js, JSValueConst this, int argc, JSValue *argv) +{ + char *f = js2str(argv[0]); + JSValue ret = int2js(mkdir(f,0777)); + JS_FreeCString(js,f); + return ret; +} + +JSValue js_io_slurpbytes(JSContext *js, JSValueConst this, int argc, JSValue *argv) +{ + char *f = js2str(argv[0]); + size_t len; + char *d = slurp_file(f,&len); + JSValue ret = JS_NewArrayBufferCopy(js,d,len); + JS_FreeCString(js,f); + free(d); + return ret; +} + +JSValue js_io_slurp(JSContext *js, JSValueConst this, int argc, JSValue *argv) +{ + char *f = js2str(argv[0]); + size_t len; + char *s = slurp_text(f,&len); + JS_FreeCString(js,f); + + if (!s) return JS_UNDEFINED; + + JSValue ret = JS_NewStringLen(js, s, len); + free(s); + return ret; +} + +JSValue js_io_slurpwrite(JSContext *js, JSValueConst this, int argc, JSValue *argv) +{ + char *f = js2str(argv[0]); + size_t len; + char *data; + JSValue ret; + if (JS_IsString(argv[1])) { + data = JS_ToCStringLen(js, &len, argv[1]); + ret = int2js(slurp_write(data, f, len)); + JS_FreeCString(js,data); + } else { + data = JS_GetArrayBuffer(js, &len, argv[1]); + ret = int2js(slurp_write(data, f, len)); + } + + return ret; +} + +static const JSCFunctionListEntry js_io_funcs[] = { + MIST_CFUNC_DEF("exists", 1, js_io_exists), + MIST_CFUNC_DEF("ls", 0, js_io_ls), + MIST_CFUNC_DEF("cp", 2, js_io_cp), + MIST_CFUNC_DEF("mv", 2, js_io_mv), + MIST_CFUNC_DEF("rm", 1, js_io_rm), + MIST_CFUNC_DEF("mkdir", 1, js_io_mkdir), + MIST_CFUNC_DEF("slurp", 1, js_io_slurp), + MIST_CFUNC_DEF("slurpbytes", 1, js_io_slurpbytes), + MIST_CFUNC_DEF("slurpwrite", 2, js_io_slurpwrite), }; static const JSCFunctionListEntry js_emitter_funcs[] = { @@ -2202,6 +2262,7 @@ void ffi_load() { QJSCLASSPREP_FUNCS(constraint); QJSGLOBALCLASS(os); + QJSGLOBALCLASS(io); } void ffi_stop() diff --git a/source/engine/resources.c b/source/engine/resources.c index 137e7a7..bf57e99 100644 --- a/source/engine/resources.c +++ b/source/engine/resources.c @@ -97,6 +97,7 @@ static int ls_ftw(const char *path, const struct stat *sb, int typeflag) return 0; } +// TODO: Not reentrant char **ls(const char *path) { if (ls_paths) { diff --git a/source/engine/script.c b/source/engine/script.c index 6d6b85c..90037b0 100644 --- a/source/engine/script.c +++ b/source/engine/script.c @@ -105,6 +105,7 @@ JSValue num_cache[100] = {0}; const char *msg = JS_ToCString(js, JS_GetPropertyStr(js, exception, "message")); const char *stack = JS_ToCString(js, val); YughLog(LOG_SCRIPT, LOG_ERROR, "%s :: %s\n%s", name, msg,stack); + js_stacktrace(); JS_FreeCString(js, name); JS_FreeCString(js, msg); @@ -223,30 +224,11 @@ void script_eval_w_env(const char *s, JSValue env, const char *file) { JS_FreeValue(js, v); } -struct callenv { - JSValue v; - const char *eval; -}; - -struct callenv *calls; - -void call_stack() +JSValue eval_file_env(const char *script, const char *file, JSValue env) { - for (int i = 0; i < arrlen(calls); i++) { - JSValue v = JS_EvalThis(js, calls[i].v, calls[i].eval, strlen(calls[i].eval), calls[i].eval, JS_EVAL_FLAGS); - js_print_exception(v); - JS_FreeValue(js, v); - } - arrfree(calls); -} - -void call_env(JSValue env, const char *eval) -{ - if (!JS_IsObject(env)) { YughWarn("NOT AN ENV"); return; }; - struct callenv c; - c.v = env; - c.eval = eval; - arrpush(calls, c); + JSValue v = JS_EvalThis(js, env, script, strlen(script), file, JS_EVAL_FLAGS); + js_print_exception(v); + return v; } void file_eval_env(const char *file, JSValue env) diff --git a/source/engine/script.h b/source/engine/script.h index c04013e..6e3907c 100644 --- a/source/engine/script.h +++ b/source/engine/script.h @@ -23,7 +23,6 @@ extern struct callee stacktrace_callee; extern JSValue num_cache[100]; JSValue jstr(const char *str); -void call_stack(); void js_stacktrace(); void script_startup(); void script_stop(); @@ -32,6 +31,7 @@ void script_evalf(const char *format, ...); int script_dofile(const char *file); time_t jso_file(const char *file); JSValue script_runfile(const char *file); +JSValue eval_file_env(const char *script, const char *file, JSValue env); void script_update(double dt); void script_draw(); void free_callee(struct callee c); @@ -48,7 +48,6 @@ void call_callee(struct callee *c); void script_callee(struct callee c, int argc, JSValue *argv); int script_has_sym(void *sym); void script_eval_w_env(const char *s, JSValue env, const char *file); -void call_env(JSValue env, const char *eval); void file_eval_env(const char *file, JSValue env); time_t file_mod_secs(const char *file); diff --git a/source/engine/yugine.c b/source/engine/yugine.c index e95e7fc..c1ab45a 100644 --- a/source/engine/yugine.c +++ b/source/engine/yugine.c @@ -110,7 +110,6 @@ static void process_frame() { double elapsed = stm_sec(stm_laptime(&frame_t)); script_evalf("Register.appupdate.broadcast(%g);", elapsed); - call_stack(); input_poll(0); /* Timers all update every frame - once per monitor refresh */ timer_update(elapsed, timescale);