proper CPU timing; ur type extensions

This commit is contained in:
John Alanbrook 2023-09-11 07:46:12 +00:00
parent b5b7f3f0e2
commit 35c0337837
13 changed files with 235 additions and 98 deletions

View file

@ -68,7 +68,7 @@ ifeq ($(OS), Windows_NT)
ZIP = .zip ZIP = .zip
UNZIP = unzip -o -q $(DISTDIR)/$(DIST) -d $(DESTDIR) UNZIP = unzip -o -q $(DISTDIR)/$(DIST) -d $(DESTDIR)
else ifeq ($(CC), emcc) 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 CFLAGS += -pthread
LDLIBS += pthread quickjs GL openal c m dl LDLIBS += pthread quickjs GL openal c m dl
CC = emcc CC = emcc

View file

@ -53,13 +53,10 @@ Components cannot be scripted; they are essentially a hardwired thing that you s
### Entity ### 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 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. 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 ## 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. 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 ### 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. 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 ## 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. 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 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 ...
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:
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 ### Level functions
|name| description| |name| description|

View file

@ -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); snprintf(buffer, ERROR_BUFFER, "%s:%d: %s, %s: %s\n", file, line, logstr[priority], catstr[category], msgbuffer);
log_print(buffer); log_print(buffer);
if (category == LOG_SCRIPT && priority >= 2) // if (category == LOG_SCRIPT && priority >= 2)
js_stacktrace(); // js_stacktrace();
} }
#endif #endif
} }

View file

@ -22,6 +22,9 @@
#include "yugine.h" #include "yugine.h"
#include <assert.h> #include <assert.h>
#include "resources.h" #include "resources.h"
#include <sokol/sokol_time.h>
#include <glob.h>
#include "render.h" #include "render.h"
@ -56,6 +59,14 @@ JSValue js_getpropidx(JSValue v, uint32_t i)
return p; 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) { int js2int(JSValue v) {
int32_t i; int32_t i;
JS_ToInt32(js, &i, v); JS_ToInt32(js, &i, v);
@ -70,6 +81,24 @@ JSValue str2js(const char *c) {
return JS_NewString(js, 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 js2number(JSValue v) {
double g; double g;
JS_ToFloat64(js, &g, v); JS_ToFloat64(js, &g, v);
@ -1050,6 +1079,9 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
return num2js(get_timescale()); return num2js(get_timescale());
break; break;
case 122: case 122:
str = JS_ToCString(js, argv[1]);
ret = glob2js(str);
break; break;
case 123: case 123:
@ -1070,6 +1102,23 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
case 126: case 126:
mainwin.height = js2int(argv[2]); mainwin.height = js2int(argv[2]);
break; 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) if (str)

View file

@ -13,6 +13,8 @@
#include <unistd.h> #include <unistd.h>
#include "font.h" #include "font.h"
#include <glob.h>
#include <fcntl.h> #include <fcntl.h>
#include "cdb.h" #include "cdb.h"
@ -155,7 +157,7 @@ unsigned char *slurp_file(const char *filename, size_t *size)
char *data = malloc(vlen); char *data = malloc(vlen);
cdb_read(&game_cdb, data, vlen, vpos); cdb_read(&game_cdb, data, vlen, vpos);
if (size) *size = vlen; if (size) *size = vlen;
return strdup(data); return data;
} }
FILE *f; FILE *f;
@ -183,7 +185,7 @@ char *slurp_text(const char *filename, size_t *size)
char *str = slurp_file(filename, &len); char *str = slurp_file(filename, &len);
char *retstr = malloc(len+1); char *retstr = malloc(len+1);
memcpy(retstr, str, len); memcpy(retstr, str, len);
retstr[len] = 0; retstr[len] = '\0';
free(str); free(str);
if (size) *size = len; if (size) *size = len;
return retstr; return retstr;
@ -198,23 +200,26 @@ int slurp_write(const char *txt, const char *filename) {
return 0; return 0;
} }
static int glob_err(const char *epath, int errno)
{
return 0;
}
#ifndef __EMSCRIPTEN__ #ifndef __EMSCRIPTEN__
static struct cdb_make cdbm; 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) static int ftw_pack(const char *path, const struct stat *sb, int flag)
{ {
if (flag != FTW_F) return 0; if (flag != FTW_F) return 0;
int pack = 0; int pack = 0;
char *ext = strrchr(path, '.'); char *ext = strrchr(path, '.');
if (!ext) if (!ext)
return 0; return 0;
for (int i = 0; i < 6; i++) { for (int i = 0; i < 11; i++) {
if (!strcmp(ext, pack_ext[i])) { if (!strcmp(ext, pack_ext[i])) {
pack = 1; pack = 1;
break; break;

View file

@ -169,17 +169,11 @@ struct wav *make_sound(const char *wav) {
return NULL; return NULL;
} }
YughWarn("%s opened with %d ch, %d samplerate, %d frames", ext, mwav.ch, mwav.samplerate, mwav.frames); if (mwav.samplerate != SAMPLERATE)
if (mwav.samplerate != SAMPLERATE) {
YughWarn("Changing samplerate of %s from %d to %d.", wav, mwav.samplerate, SAMPLERATE);
mwav = change_samplerate(mwav, SAMPLERATE); mwav = change_samplerate(mwav, SAMPLERATE);
}
if (mwav.ch != CHANNELS) { if (mwav.ch != CHANNELS)
YughWarn("Changing channels of %s from %d to %d.", wav, mwav.ch, CHANNELS);
mwav = change_channels(mwav, CHANNELS); mwav = change_channels(mwav, CHANNELS);
}
mwav.gain = 1.f; mwav.gain = 1.f;
struct wav *newwav = malloc(sizeof(*newwav)); 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); if (shlen(wavhash) == 0) sh_new_arena(wavhash);
shput(wavhash, wav, newwav); shput(wavhash, wav, newwav);
YughWarn("Channels %d, sr %d", newwav->ch,newwav->samplerate);
return newwav; return newwav;
} }

View file

@ -200,7 +200,15 @@ Object.defineProperty(String.prototype, 'shift', {
Object.defineProperty(String.prototype, 'ext', { Object.defineProperty(String.prototype, 'ext', {
value: function() { 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() { value: function() {
var s = this.lastIndexOf('/'); var s = this.lastIndexOf('/');
var e = this.lastIndexOf('.'); var e = this.lastIndexOf('.');
if (e === -1) e = this.length;
return this.slice(s+1,e); 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',{ Object.defineProperty(Object.prototype, 'lerp',{
value: function(to, t) { value: function(to, t) {
var self = this; var self = this;

View file

@ -35,7 +35,8 @@ var sprite = clone(component, {
rect: {s0:0, s1: 1, t0: 0, t1: 1}, rect: {s0:0, s1: 1, t0: 0, t1: 1},
get dimensions() { return cmd(64,this.path); }, 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) { make(go) {
var sprite = Object.create(this); var sprite = Object.create(this);

View file

@ -124,13 +124,18 @@ var Gizmos = {
}; };
var Profile = { 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; times ??= 1;
var start = Date.now(); q ??= "ns";
var start = Profile.tick_now();
for (var i = 0; i < times; i++) for (var i = 0; i < times; i++)
fn(); fn();
var elapsed = Profile.tick_now() - start;
Log.warn(`Profiled in ${(Date.now()-start)/1000} seconds.`); Log.say(`Profiled in ${Profile[q](elapsed)/times} avg ${q}.`);
}, },
get fps() { return sys_cmd(8); }, get fps() { return sys_cmd(8); },

View file

@ -3,11 +3,7 @@
selectable selectable
*/ */
var required_files = ["proto.json"]; prototypes.generate_ur('.');
required_files.forEach(x => {
if (!IO.exists(x)) IO.slurpwrite("", x);
});
/* This is the editor level & camera - NOT the currently edited level, but a level to hold editor things */ /* This is the editor level & camera - NOT the currently edited level, but a level to hold editor things */
var editor_level = gameobject.make(Primum); var editor_level = gameobject.make(Primum);

View file

@ -13,9 +13,6 @@ load("scripts/std.js");
function initialize() function initialize()
{ {
if (IO.exists("config.js"))
load("config.js");
if (Cmdline.play) if (Cmdline.play)
run("scripts/play.js"); run("scripts/play.js");
else else
@ -629,24 +626,6 @@ World.unparent = function() { }
World.name = "World"; World.name = "World";
World.fullpath = function() { return World.name; }; 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 */ /* Load configs */
function load_configs(file) { function load_configs(file) {
@ -734,7 +713,6 @@ Game.view_camera(camera2d.make(World));
win_make(Game.title, Game.resolution[0], Game.resolution[1]); win_make(Game.title, Game.resolution[0], Game.resolution[1]);
/* Default objects */ /* Default objects */
var prototypes = {}; var prototypes = {};
prototypes.ur = {}; prototypes.ur = {};
prototypes.load_all = function() prototypes.load_all = function()
@ -771,37 +749,52 @@ prototypes.from_file = function(file)
var newobj = gameobject.clone(file, {}); var newobj = gameobject.clone(file, {});
var script = IO.slurp(file); var script = IO.slurp(file);
compile_env(`var self = this;${script}`, newobj, file);
prototypes.ur[file.name()] = newobj; newobj.$ = {};
return 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_file.doc = "Create a new ur-type from a given script file.";
prototypes.from_obj = function(name, obj) prototypes.from_obj = function(name, obj)
{ {
var newobj = gameobject.clone(name, obj); var newobj = gameobject.clone(name, obj);
prototypes.ur[name] = newobj; prototypes.ur[name] = {
return newobj; tag: name,
type: newobj
};
return prototypes.ur[name];
} }
prototypes.load_config = function(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]) if (!prototypes.ur[name])
prototypes.ur[name] = gameobject.clone(name); prototypes.ur[name] = gameobject.clone(name);
if (prototypes.config[name]) { Log.warn(`Made new ur of name ${name}`);
Log.warn(`Assigning ${name} from config.`);
dainty_assign(prototypes.config[name], prototypes.ur[name]);
}
return prototypes.ur[name]; return prototypes.ur[name];
} }
@ -831,3 +824,19 @@ prototypes.from_obj("edge2d", {
prototypes.from_obj("sprite", { prototypes.from_obj("sprite", {
sprite: sprite.clone(), 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;

View file

@ -1,6 +1,3 @@
var gameobjects = {};
var Prefabs = gameobjects;
function grab_from_points(pos, points, slop) { function grab_from_points(pos, points, slop) {
var shortest = slop; var shortest = slop;
var idx = -1; var idx = -1;
@ -15,14 +12,13 @@ function grab_from_points(pos, points, slop) {
var gameobject = { var gameobject = {
scale: 1.0, scale: 1.0,
save: true, save: true,
selectable: true, selectable: true,
spawn(ur) { spawn(ur) {
Log.warn("DEPRECIATED"); if (typeof ur === 'string')
return ur.make(this); ur = prototypes.get_ur(ur);
return ur.type.make(this);
}, },
layer: 0, /* Collision layer; should probably have been called "mask" */ layer: 0, /* Collision layer; should probably have been called "mask" */
@ -54,12 +50,6 @@ var gameobject = {
clone(name, ext) { clone(name, ext) {
var obj = Object.create(this); var obj = Object.create(this);
complete_assign(obj, ext); complete_assign(obj, ext);
gameobjects[name] = obj;
obj.defc('name', name);
obj.from = this.name;
obj.defn('instances', []);
obj.obscure('from');
return obj; return obj;
}, },
@ -430,7 +420,7 @@ var gameobject = {
obj.check_registers(obj); obj.check_registers(obj);
if ('begin' in obj) obj.begin(); if (typeof obj.start === 'function') obj.start();
return obj; return obj;
}, },

View file

@ -1,4 +1,3 @@
function compile_env(str, env, file) function compile_env(str, env, file)
{ {
file ??= "unknown"; file ??= "unknown";
@ -80,6 +79,7 @@ var IO = {
}, },
slurpwrite(str, file) { return cmd(39, str, file); }, slurpwrite(str, file) { return cmd(39, str, file); },
extensions(ext) { return cmd(66, "." + ext); }, extensions(ext) { return cmd(66, "." + ext); },
glob(pat) { return cmd(122, pat); },
}; };
var Cmdline = {}; var Cmdline = {};