From 35c033783725f26d774a3f4af6423e68aeaa6a2a Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Mon, 11 Sep 2023 07:46:12 +0000 Subject: [PATCH] proper CPU timing; ur type extensions --- Makefile | 2 +- docs/game.md | 105 ++++++++++++++++++++++++++++++----- source/engine/debug/log.c | 4 +- source/engine/ffi.c | 49 ++++++++++++++++ source/engine/resources.c | 17 ++++-- source/engine/sound.c | 12 +--- source/scripts/base.js | 13 ++++- source/scripts/components.js | 3 +- source/scripts/debug.js | 13 +++-- source/scripts/editor.js | 6 +- source/scripts/engine.js | 89 ++++++++++++++++------------- source/scripts/entity.js | 18 ++---- source/scripts/std.js | 2 +- 13 files changed, 235 insertions(+), 98 deletions(-) diff --git a/Makefile b/Makefile index 336f1fb0..8bda352d 100755 --- a/Makefile +++ b/Makefile @@ -68,7 +68,7 @@ ifeq ($(OS), Windows_NT) ZIP = .zip UNZIP = unzip -o -q $(DISTDIR)/$(DIST) -d $(DESTDIR) else ifeq ($(CC), emcc) - LDFLAGS += -sMIN_WEBGL_VERSION=2 -sMAX_WEBGL_VERSION=2 -pthread + LDFLAGS += -sMIN_WEBGL_VERSION=2 -sMAX_WEBGL_VERSION=2 -pthread -sALLOW_MEMORY_GROWTH -sTOTAL_MEMORY=450MB --embed-file $(BIN)@ CFLAGS += -pthread LDLIBS += pthread quickjs GL openal c m dl CC = emcc diff --git a/docs/game.md b/docs/game.md index 52294dc7..1455eda3 100644 --- a/docs/game.md +++ b/docs/game.md @@ -53,13 +53,10 @@ Components cannot be scripted; they are essentially a hardwired thing that you s ### Entity Entities are holders of components. Anything that needs a component will be an entity. Components rely on entites to render correctly. For example, the engine knows where to draw a sprite wherever its associated entity is. -Entities can be composed of other entities. When that is the case, an entity "under" a different entity will move when the above entity moves. +Entities can be composed of other entities. When that is the case, an entity "under" a different entity will move when the above entity moves, as if it were attached. The outermost entity that all other entities must exist in is the Primum. It always exists and cannot be removed. -## Traits -It is better to think of Primum as trait-based, intead of object-based. Any thing in your game that has particular properties can be used as a particular sort of object. - ## Prototyping model All objects follow the prototyping model of inheritence. This makes it trivial to change huge swathes of the game, or make tiny adjustments to single objects, in a natural and intuitive way. @@ -76,21 +73,103 @@ entity.push() -> push changes to this entity to its Ur-type to it matches. ### Ur-types An Ur-type is a thing which cannot be seen but which can stamp out copies of itself. Objects can be promoted to an ur-type, so if it is deleted, another one can later be made. -Levels can be subtyped, sidetyped, and urtyped, just like entities. +Ur-types have a lineage going back to the original gameobject. The ur-type lineage can be as deep as you want it to be; but it should probably be shallow. + +Only first Ur-types can have components. Every inherited thing after it can only edit the original's components, not add or subtract. Original Ur-types must currently be defined in code. + +Ur-types also remember the list of entities that compose the given Ur. + +### Loading traits +Traits are defined by code and a data file. When an Ur-type is extended with a trait, the code is run, and then the data file contains modifications and + +### Creating entities +Entities are like real world representations of an Ur-type. Ur-types exist only theoretically, but can then be spawned into a true entity in the game world. + +An entity can diverge from its ur-type. When this happens, you can either revert the entity, copy how it's changed to its ur-type, or promote it to its own ur-type. + +### Efficiency of all this +It is extremely cheap and fast to create entities. Ur-types work as a defined way for the engine to make an object, and can even cache deleted copies of them. ## Resources Assets can generally be used just with their filename. It will be loaded with default values. However, how the engine interprets it can be altered with a sidecar file, named "filename.asset", so "ball.png" will be modified via "ball.png.asset". These are typical JSON files. For images, specify gamma, if it's a sprite or texture, etc, for sound, specify its gain, etc. -## Level model -The game world is made up of objects. Levels are collections of -objects. The topmost level is called "World". Objects are spawned into -a specific level. If none are explicitly given, objects are spawned -into World. Objects in turn are made up of components - sprites, -colliders, and so on. Accessing an object might go like this: +Ur-types are directly related to your file directory hierarchy. In a pinball game where you have a flipper, and then a flipper that is left ... -World.level1.enemy1.sprite.path = "brick.png"; +@/ + /bumper + hit.wav + bumper.js + /ball + hit.wav + ball.js + /flipper + flipper.js + flipper.json <-- Modifications applied to the flipper ur-type + t1.json <-- A variant of flipper.js. Cannot be subtyped. + flip.wav + flipper.img + left/ + flip.wav + left.js <-- This will load as an extension to flipper.js -To set the image of enemy1 in level 1's sprite. +This is how resources are loaded in any given ur-type. Relative file paths work. So, in 'ball.js', it can reference 'hit.wav', and will play that file when it does; when bumper.js loads 'hit.wav', it will load the one located in its folder. + +The left flipper can use the root flipper flip sound by loading "../flip.wav". + +Absolute paths can be specified using a leading slash. The absolute path the bumper's hit sound is "/bumper/hit.wav". + +When you attempt to load the "flipper.left" ur-type, if flipper is not loaded, the engine will load it first, and then load left. + +Unloading an ur-type unloads everything below it, necessarily. flipper.left means nothing without flipper. + +Computer systems have a user configuration folder specified, where you are allowed to write data to and from. This is good for save games and configurations. It is specified with a leading "@" sign. So "@1.save" will load the file "1.save" from the folder allotted to your game by the platform. + +Links can be specified using the "#" sign. This is simply defined as, for example, with "trees:/world/assets/nature/trees" specified, you can easily make the ur-type "fern" with "Primum.spawn("#trees/fern")", instead of "Primum.spawn('#trees.fern')" + +Primum will attempt to solve most resolution ambiguities automatically. There are numerous valid directory layouts you can have. Examining flipper.left ... + +@/ + flipper.js + flipper/ + left.js + +@/ + flipper/ + _.js + left.js + +@/ + flipper/ + flipper.js + left/ + left.js + +In sum, a file that is a single underscore _.js is assumed to be the given folder's ur-type. When populating the ur-type hierarchy, the _ is replaced with the name of the containing folder. if there is a folder with the same name as *.js present, the items in the folder are assumed to be ur-types of the *.js. + +Asset links always follow the directory hierarchy, however, so if you want to reference an asset with a relative path, the .js file must actually be present in the same path as the asset. + +prototypes.generate_ur(path) will generate all ur-types for a given path. You can preload specific levels this way, or the entire game if it is small enough. + +### Spawning +The outmost sphere of the game is the Primum, the first entity. Your first entity must be spawned in the Primum. Subsequent entities can be spawned in any entity in the game. + +Ur-types can remember configurations of entities spawned inside of them. + +Once entities are created in the world, they are mostly interested now in addressing actual other objects in the world. Let's look at an example. + +Primum + Level 1 + Orc + Goblin + Human + Sword + UI + +When a script is running, it is completely contained. If "Human" has a "health" parameter, it can only be access through "self.health", not directly through "health". This makes it easy to run a script without fear of overwriting any parameters on accident. + +The "$" is populated with an object's children. $.sword.damage will properly get the damage of the human's sword, and will be undefined for Goblin and Orc. + +To access the entity's owner, it is through _. For example, the human can access the orc via _.Orc. ### Level functions |name| description| diff --git a/source/engine/debug/log.c b/source/engine/debug/log.c index 7ac86c92..5a878852 100644 --- a/source/engine/debug/log.c +++ b/source/engine/debug/log.c @@ -51,8 +51,8 @@ void mYughLog(int category, int priority, int line, const char *file, const char snprintf(buffer, ERROR_BUFFER, "%s:%d: %s, %s: %s\n", file, line, logstr[priority], catstr[category], msgbuffer); log_print(buffer); - if (category == LOG_SCRIPT && priority >= 2) - js_stacktrace(); +// if (category == LOG_SCRIPT && priority >= 2) +// js_stacktrace(); } #endif } diff --git a/source/engine/ffi.c b/source/engine/ffi.c index e6ae1990..058af9b7 100644 --- a/source/engine/ffi.c +++ b/source/engine/ffi.c @@ -22,6 +22,9 @@ #include "yugine.h" #include #include "resources.h" +#include + +#include #include "render.h" @@ -56,6 +59,14 @@ JSValue js_getpropidx(JSValue v, uint32_t i) return p; } +uint64_t js2uint64(JSValue v) +{ + int64_t i; + JS_ToInt64(js, &i, v); + uint64_t n = i; + return n; +} + int js2int(JSValue v) { int32_t i; JS_ToInt32(js, &i, v); @@ -70,6 +81,24 @@ JSValue str2js(const char *c) { return JS_NewString(js, c); } +JSValue strarr2js(const char **c, int len) +{ + JSValue arr = JS_NewArray(js); + for (int i = 0; i < len; i++) + JS_SetPropertyUint32(js, arr, i, JS_NewString(js, c[i])); + + return arr; +} + +JSValue glob2js(char *pat) +{ + glob_t mglob; + glob(pat, GLOB_NOSORT, NULL, &mglob); + JSValue arr = strarr2js(mglob.gl_pathv, mglob.gl_pathc); + globfree(&mglob); + return arr; +} + double js2number(JSValue v) { double g; JS_ToFloat64(js, &g, v); @@ -1050,6 +1079,9 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv) return num2js(get_timescale()); break; case 122: + str = JS_ToCString(js, argv[1]); + ret = glob2js(str); + break; case 123: @@ -1070,6 +1102,23 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv) case 126: mainwin.height = js2int(argv[2]); break; + + case 127: + ret = JS_NewInt64(js, stm_now()); + break; + + case 128: + YughWarn("%g",stm_ms(9737310)); + ret = JS_NewFloat64(js, stm_ns(js2uint64(argv[1]))); + break; + + case 129: + ret = JS_NewFloat64(js, stm_us(js2uint64(argv[1]))); + break; + + case 130: + ret = JS_NewFloat64(js, stm_ms(js2uint64(argv[1]))); + break; } if (str) diff --git a/source/engine/resources.c b/source/engine/resources.c index 0ebabfb4..7e09dd1c 100644 --- a/source/engine/resources.c +++ b/source/engine/resources.c @@ -13,6 +13,8 @@ #include #include "font.h" +#include + #include #include "cdb.h" @@ -155,7 +157,7 @@ unsigned char *slurp_file(const char *filename, size_t *size) char *data = malloc(vlen); cdb_read(&game_cdb, data, vlen, vpos); if (size) *size = vlen; - return strdup(data); + return data; } FILE *f; @@ -183,7 +185,7 @@ char *slurp_text(const char *filename, size_t *size) char *str = slurp_file(filename, &len); char *retstr = malloc(len+1); memcpy(retstr, str, len); - retstr[len] = 0; + retstr[len] = '\0'; free(str); if (size) *size = len; return retstr; @@ -198,23 +200,26 @@ int slurp_write(const char *txt, const char *filename) { return 0; } +static int glob_err(const char *epath, int errno) +{ + return 0; +} + #ifndef __EMSCRIPTEN__ static struct cdb_make cdbm; -static const char *pack_ext[] = {".qoi", ".qoa", ".js", ".wav", ".mp3", ".png", ".sf2", ".midi", ".lvl", ".glsl"}; +static const char *pack_ext[] = {".qoi", ".qoa", ".js", ".wav", ".mp3", ".png", ".sf2", ".midi", ".lvl", ".glsl", ".ttf"}; static int ftw_pack(const char *path, const struct stat *sb, int flag) { if (flag != FTW_F) return 0; - int pack = 0; - char *ext = strrchr(path, '.'); if (!ext) return 0; - for (int i = 0; i < 6; i++) { + for (int i = 0; i < 11; i++) { if (!strcmp(ext, pack_ext[i])) { pack = 1; break; diff --git a/source/engine/sound.c b/source/engine/sound.c index 086628f0..d957eea8 100644 --- a/source/engine/sound.c +++ b/source/engine/sound.c @@ -169,17 +169,11 @@ struct wav *make_sound(const char *wav) { return NULL; } - YughWarn("%s opened with %d ch, %d samplerate, %d frames", ext, mwav.ch, mwav.samplerate, mwav.frames); - - if (mwav.samplerate != SAMPLERATE) { - YughWarn("Changing samplerate of %s from %d to %d.", wav, mwav.samplerate, SAMPLERATE); + if (mwav.samplerate != SAMPLERATE) mwav = change_samplerate(mwav, SAMPLERATE); - } - if (mwav.ch != CHANNELS) { - YughWarn("Changing channels of %s from %d to %d.", wav, mwav.ch, CHANNELS); + if (mwav.ch != CHANNELS) mwav = change_channels(mwav, CHANNELS); - } mwav.gain = 1.f; struct wav *newwav = malloc(sizeof(*newwav)); @@ -187,8 +181,6 @@ struct wav *make_sound(const char *wav) { if (shlen(wavhash) == 0) sh_new_arena(wavhash); shput(wavhash, wav, newwav); - YughWarn("Channels %d, sr %d", newwav->ch,newwav->samplerate); - return newwav; } diff --git a/source/scripts/base.js b/source/scripts/base.js index 7a47bb22..545bfdb9 100644 --- a/source/scripts/base.js +++ b/source/scripts/base.js @@ -200,7 +200,15 @@ Object.defineProperty(String.prototype, 'shift', { Object.defineProperty(String.prototype, 'ext', { value: function() { - return this.slice(this.lastIndexOf('.')); + var idx = this.lastIndexOf('.'); + if (idx === -1) return undefined; + return this.slice(idx); + } +}); + +Object.defineProperty(String.prototype, 'set_ext', { + value: function(val) { + return this.name() + val; } }); @@ -208,6 +216,7 @@ Object.defineProperty(String.prototype, 'name', { value: function() { var s = this.lastIndexOf('/'); var e = this.lastIndexOf('.'); + if (e === -1) e = this.length; return this.slice(s+1,e); } }); @@ -505,6 +514,8 @@ Object.defineProperty(Array.prototype, 'lerp', { } }); +Math.lerp = function(s,f,t) { return (f-s)*t + s; }; + Object.defineProperty(Object.prototype, 'lerp',{ value: function(to, t) { var self = this; diff --git a/source/scripts/components.js b/source/scripts/components.js index 848ea075..00175720 100644 --- a/source/scripts/components.js +++ b/source/scripts/components.js @@ -35,7 +35,8 @@ var sprite = clone(component, { rect: {s0:0, s1: 1, t0: 0, t1: 1}, get dimensions() { return cmd(64,this.path); }, - set dimensions(x) {}, + get width() { return cmd(64,this.path).x; }, + get height() { return cmd(64,this.path).y; }, make(go) { var sprite = Object.create(this); diff --git a/source/scripts/debug.js b/source/scripts/debug.js index bf744fb0..312a5671 100644 --- a/source/scripts/debug.js +++ b/source/scripts/debug.js @@ -124,13 +124,18 @@ var Gizmos = { }; var Profile = { - cpu(fn, times) { + tick_now() { return cmd(127); }, + ns(ticks) { return cmd(128, ticks); }, + us(ticks) { return cmd(129, ticks); }, + ms(ticks) { return cmd(130, ticks); }, + cpu(fn, times, q) { times ??= 1; - var start = Date.now(); + q ??= "ns"; + var start = Profile.tick_now(); for (var i = 0; i < times; i++) fn(); - - Log.warn(`Profiled in ${(Date.now()-start)/1000} seconds.`); + var elapsed = Profile.tick_now() - start; + Log.say(`Profiled in ${Profile[q](elapsed)/times} avg ${q}.`); }, get fps() { return sys_cmd(8); }, diff --git a/source/scripts/editor.js b/source/scripts/editor.js index 002068e3..586ddcb4 100644 --- a/source/scripts/editor.js +++ b/source/scripts/editor.js @@ -3,11 +3,7 @@ selectable */ -var required_files = ["proto.json"]; - -required_files.forEach(x => { - if (!IO.exists(x)) IO.slurpwrite("", x); -}); +prototypes.generate_ur('.'); /* This is the editor level & camera - NOT the currently edited level, but a level to hold editor things */ var editor_level = gameobject.make(Primum); diff --git a/source/scripts/engine.js b/source/scripts/engine.js index 60e2b662..fd6de93b 100644 --- a/source/scripts/engine.js +++ b/source/scripts/engine.js @@ -13,9 +13,6 @@ load("scripts/std.js"); function initialize() { - if (IO.exists("config.js")) - load("config.js"); - if (Cmdline.play) run("scripts/play.js"); else @@ -629,24 +626,6 @@ World.unparent = function() { } World.name = "World"; World.fullpath = function() { return World.name; }; -World.load = function(lvl) { - if (World.loaded) - World.loaded.kill(); - - World.loaded = World.spawn(lvl); - return World.loaded; -}; - -World.run = function(file) -{ - var newobject = {}; - newobject.kill = function() { - Register.unregister_obj(newobject); - } - var script = IO.slurp(file); - compile_env(`var self = this;${script}`, newobject, file); -} - /* Load configs */ function load_configs(file) { @@ -734,7 +713,6 @@ Game.view_camera(camera2d.make(World)); win_make(Game.title, Game.resolution[0], Game.resolution[1]); /* Default objects */ - var prototypes = {}; prototypes.ur = {}; prototypes.load_all = function() @@ -771,37 +749,52 @@ prototypes.from_file = function(file) var newobj = gameobject.clone(file, {}); var script = IO.slurp(file); - compile_env(`var self = this;${script}`, newobj, file); - prototypes.ur[file.name()] = newobj; - return newobj; + + newobj.$ = {}; + var json = {}; + if (IO.exists(file.name() + ".json")) { + json = JSON.parse(IO.slurp(file.name() + ".json")); + Object.assign(newobj.$, json.$); + delete json.$; + } + + compile_env(`var self = this; var $ = self.$; ${script}`, newobj, file); + dainty_assign(newobj, json); + + var path = file.replaceAll('/', '.'); + path = path.name().split('.'); + var nested_access = function(base, names) { + for (var i = 0; i < names.length; i++) + base = base[names[i]] = base[names[i]] || {}; + + return base; + }; + var a = nested_access(ur, path); + + a.tag = path.at(-1); + a.type = newobj; + a.instances = []; + + return a; } prototypes.from_file.doc = "Create a new ur-type from a given script file."; prototypes.from_obj = function(name, obj) { var newobj = gameobject.clone(name, obj); - prototypes.ur[name] = newobj; - return newobj; + prototypes.ur[name] = { + tag: name, + type: newobj + }; + return prototypes.ur[name]; } prototypes.load_config = function(name) { - if (!prototypes.config) { - prototypes.config = {}; - - if (IO.exists("proto.json")) - prototypes.config = JSON.parse(IO.slurp("proto.json")); - } - - Log.warn(`Loading a config for ${name}`); - if (!prototypes.ur[name]) prototypes.ur[name] = gameobject.clone(name); - if (prototypes.config[name]) { - Log.warn(`Assigning ${name} from config.`); - dainty_assign(prototypes.config[name], prototypes.ur[name]); - } + Log.warn(`Made new ur of name ${name}`); return prototypes.ur[name]; } @@ -831,3 +824,19 @@ prototypes.from_obj("edge2d", { prototypes.from_obj("sprite", { sprite: sprite.clone(), }); + +prototypes.generate_ur = function(path) +{ + var ob = IO.glob("*.js"); + ob = ob.concat(IO.glob("**/*.js")); + ob = ob.filter(function(str) { return !str.startsWith("scripts"); }); + + ob.forEach(function(name) { + if (name === "game.js") return; + if (name === "play.js") return; + + prototypes.from_file(name); + }); +} + +var ur = prototypes.ur; diff --git a/source/scripts/entity.js b/source/scripts/entity.js index 71ec6527..8f6c283f 100644 --- a/source/scripts/entity.js +++ b/source/scripts/entity.js @@ -1,6 +1,3 @@ -var gameobjects = {}; -var Prefabs = gameobjects; - function grab_from_points(pos, points, slop) { var shortest = slop; var idx = -1; @@ -15,14 +12,13 @@ function grab_from_points(pos, points, slop) { var gameobject = { scale: 1.0, - save: true, - selectable: true, spawn(ur) { - Log.warn("DEPRECIATED"); - return ur.make(this); + if (typeof ur === 'string') + ur = prototypes.get_ur(ur); + return ur.type.make(this); }, layer: 0, /* Collision layer; should probably have been called "mask" */ @@ -54,12 +50,6 @@ var gameobject = { clone(name, ext) { var obj = Object.create(this); complete_assign(obj, ext); - gameobjects[name] = obj; - obj.defc('name', name); - obj.from = this.name; - obj.defn('instances', []); - obj.obscure('from'); - return obj; }, @@ -430,7 +420,7 @@ var gameobject = { obj.check_registers(obj); - if ('begin' in obj) obj.begin(); + if (typeof obj.start === 'function') obj.start(); return obj; }, diff --git a/source/scripts/std.js b/source/scripts/std.js index 53e36ed4..bfcf3643 100644 --- a/source/scripts/std.js +++ b/source/scripts/std.js @@ -1,4 +1,3 @@ - function compile_env(str, env, file) { file ??= "unknown"; @@ -80,6 +79,7 @@ var IO = { }, slurpwrite(str, file) { return cmd(39, str, file); }, extensions(ext) { return cmd(66, "." + ext); }, + glob(pat) { return cmd(122, pat); }, }; var Cmdline = {};