Push window functions into C

This commit is contained in:
John Alanbrook 2024-03-13 16:30:55 -05:00
parent bb24fd2bc0
commit 962fed0022
18 changed files with 194 additions and 190 deletions

View file

@ -229,7 +229,7 @@ CDB_O := $(patsubst %.c, %.o, $(CDB_C))
$(CDB)/libcdb.a:
rm -f $(CDB)/libcdb.a
make -C $(CDB) libcdb.a
tools/libcdb.a: $(CDB)/libcdb.a
cp $(CDB)/libcdb.a tools

View file

@ -1192,7 +1192,7 @@ Object.defineProperty(Array.prototype, 'flat', {
}
});
Object.defineProperty(Array.prototype, 'any', {
Object.defineProperty(Array.prototype, 'anyjs', {
value: function(fn) {
var ev = this.every(function(x) {
return !fn(x);

View file

@ -78,8 +78,10 @@ var Debug = {
function assert(op, str)
{
str ??= `assertion failed [value '${op}']`;
if (!op)
console.critical(`Assertion failed: ${str}`);
if (!op) {
console.error(`Assertion failed: ${str}`);
Game.quit();
}
}
Debug.Options = { };

View file

@ -3,8 +3,7 @@
selectable
*/
global.mixin("config.js");
Window.aspect(Window.mode.full);
Window.mode = Window.modetypes.full;
Game.loadurs();
player[0].control(Debug);
@ -2074,7 +2073,7 @@ if (io.exists("editor.config"))
/* This is the editor level & camera - NOT the currently edited level, but a level to hold editor things */
Game.stop();
Game.editor_mode(true);
Window.editor = true;
Debug.draw_phys(true);
return {

View file

@ -3,7 +3,10 @@ globalThis.global = globalThis;
function use(file)
{
if (use.files[file]) return use.files[file];
if (globalThis.console)
console.warn(`running ${file}`);
var c = io.slurp(file);
@ -17,6 +20,7 @@ use.files = {};
function include(file,that)
{
if (!that) return;
if (globalThis.console) console.warn(`running ${file}`);
var c = io.slurp(file);
eval_env(c, that, file);
}
@ -25,7 +29,8 @@ function eval_env(script, env, file)
{
env ??= {};
file ??= "SCRIPT";
script = `(function() { ${script} }).call(this);`;
if (globalThis.console) console.warn(`eval ${file}`);
script = `(function() { ${script}; }).call(this);\n`;
return cmd(123,script,env,file);
}
@ -120,7 +125,6 @@ global.Game = {
pause() { sys_cmd(3); },
stop() { Game.pause(); },
step() { sys_cmd(4);},
editor_mode(m) { sys_cmd(10, m); },
playing() { return sys_cmd(5); },
paused() { return sys_cmd(6); },
stepping() { return cmd(79); },
@ -140,12 +144,6 @@ global.Game = {
this.wait_fns = [];
},
set width(w) { cmd(125, w); },
set height(h) { cmd(126, h); },
get width() { return cmd(48); },
get height() { return cmd(49); },
dimensions() { return [this.width,this.height]; },
};
Game.gc = function() { cmd(259); }
@ -372,37 +370,16 @@ var Event = {
},
};
// Window
var Window = {
fullscreen(f) { cmd(145, f); },
dimensions() { return cmd(265); },
get width() { return this.dimensions().x; },
get height() { return this.dimensions().y; },
set width(x) { cmd(266, x); },
set height(x) { cmd(267,x); },
mode: {
stretch: 0, // stretch to fill window
keep: 1, // keep exact dimensions
width: 2, // keep width
height: 3, // keep height
expand: 4, // expand width or height
full: 5 // expand out beyond window
},
aspect(x) { cmd(264, x); },
title(str) { cmd(134, str); },
boundingbox() {
return {
t: Window.height,
b: 0,
r: Window.width,
l: 0
};
},
Window.modetypes = {
stretch: 0, // stretch to fill window
keep: 1, // keep exact dimensions
width: 2, // keep width
height: 3, // keep height
expand: 4, // expand width or height
full: 5 // expand out beyond window
};
Game.width = 1920;
Game.height = 1080;
Window.size = [640, 480];
Window.screen2world = function(screenpos) {
if (Game.camera)
@ -465,4 +442,4 @@ Game.view_camera = function(cam)
cmd(61, Game.camera.body);
}
Window.title(`Prosperon v${prosperon.version}`);
Window.title = `Prosperon v${prosperon.version}`;

View file

@ -142,26 +142,27 @@ var console = {
yughlog(lvl, lg, file, line);
},
spam(msg) {
this.print(msg,0);
},
debug(msg) {
this.print(msg,1);
},
info(msg) {
this.print(msg, 0);
this.print(msg, 2);
},
warn(msg) {
this.print(msg, 1);
this.print(msg, 3);
},
error(msg) {
this.print(msg, 2);
this.print(msg, 4);
this.stack(1);
},
critical(msg) {
this.print(msg,3);
this.stack(1);
Game.quit();
},
write(msg) {
if (typeof msg === 'object')
msg = JSON.stringify(msg,null,2);
@ -169,7 +170,6 @@ var console = {
cmd(91,msg);
},
log(msg) { console.say(time.text(time.now(), 'yyyy-m-dd hh:nn:ss') + " " + msg); },
say(msg) { console.write(msg + '\n'); },
repl(msg) { cmd(142, msg + '\n'); },
@ -189,6 +189,8 @@ var console = {
},
};
console.log = console.info;
var say = function(msg) {
console.say(msg);
}
@ -377,10 +379,13 @@ Cmdline.register_order("play", function(argv) {
var project = json.decode(io.slurp(projectfile));
Game.title = project.title;
Window.aspect(Window.mode.expand);
Window.mode = Window.modetypes.expand;
global.mixin("config.js");
if (project.title) Window.title(project.title);
if (project.title) Window.title = project.title;
if (Window.rendersize.equal([0,0])) Window.rendersize = Window.size;
console.info(`Starting game with window size ${Window.size} and render ${Window.rendersize}.`);
Game.engine_start(function() {
global.mixin("scripts/sound.js");
global.game = actor.spawn("game.js");

View file

@ -553,8 +553,8 @@ void draw_grid(float width, float span, struct rgba color)
float ubo[4];
ubo[0] = offset.x;
ubo[1] = offset.y;
ubo[2] = mainwin.width;
ubo[3] = mainwin.height;
ubo[2] = mainwin.size.x;
ubo[3] = mainwin.size.y;
sg_apply_pipeline(grid_pipe);
sg_apply_bindings(&grid_bind);

View file

@ -13,12 +13,7 @@
int logLevel = 0;
#define LOG_INFO 0
#define LOG_WARN 1
#define LOG_ERROR 2
#define LOG_CRIT 3
char *logstr[] = { "info", "warn", "error", "critical" };
char *logstr[] = { "spam", "debug", "info", "warn", "error"};
char *catstr[] = {"engine", "script", "render"};
FILE *logfile = NULL;

View file

@ -4,10 +4,11 @@
#include <stdio.h>
#include <stdint.h>
#define LOG_INFO 0
#define LOG_WARN 1
#define LOG_ERROR 2
#define LOG_CRITICAL 3
#define LOG_SPAM 0
#define LOG_DEBUG 1
#define LOG_INFO 2
#define LOG_WARN 3
#define LOG_ERROR 4
#define LOG_ENGINE 0
#define LOG_SCRIPT 1

View file

@ -130,7 +130,8 @@ static void velocityFn(cpBody *body, cpVect gravity, cpFloat damping, cpFloat dt
return;
}
cpFloat d = isfinite(go->damping) ? go->damping : damping;
// cpFloat d = isfinite(go->damping) ? go->damping : damping;
cpFloat d = damping;
cpBodyUpdateVelocity(body,g.cp,d,dt*go->timescale);

View file

@ -76,6 +76,7 @@ QJSCLASS(warp_gravity)
QJSCLASS(warp_damp)
QJSCLASS(material)
QJSCLASS(mesh)
QJSCLASS(window)
/* qjs class colliders and constraints */
/* constraint works for all constraints - 2d or 3d */
@ -752,13 +753,6 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
draw_grid(js2number(argv[1]), js2number(argv[2]), js2color(argv[3]));
break;
case 48:
ret = JS_NewInt64(js, mainwin.rwidth);
break;
case 49:
ret = JS_NewInt64(js, mainwin.rheight);
break;
case 51:
draw_cppoint(js2vec2(argv[1]), js2number(argv[2]), js2color(argv[3]));
@ -923,13 +917,6 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
pack_engine(str);
break;
case 125:
mainwin.rwidth = js2int(argv[1]);
break;
case 126:
mainwin.rheight = js2int(argv[1]);
break;
case 127:
ret = JS_NewInt64(js, stm_now());
@ -958,11 +945,6 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
ret = JS_NewFloat64(js, apptime());
break;
case 134:
str = JS_ToCString(js,argv[1]);
app_name(str);
break;
case 135:
ret = number2js(cam_zoom());
break;
@ -993,10 +975,6 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
console_print(str);
break;
case 145:
if (js2bool(argv[1])) window_makefullscreen(&mainwin);
else window_unfullscreen(&mainwin);
break;
case 146:
log_clear();
break;
@ -1198,18 +1176,6 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
str = js2str(argv[1]);
font_set(str);
break;
case 264:
aspect_mode = js2int(argv[1]);
break;
case 265:
ret = vec22js((HMM_Vec2){mainwin.width, mainwin.height});
break;
case 266:
mainwin.width = js2number(argv[1]);
break;
case 267:
mainwin.height = js2number(argv[1]);
break;
case 268:
if (js2bool(argv[1]))
ret = JS_GetClassProto(js, js_sprite_id);
@ -1294,10 +1260,6 @@ JSValue duk_sys_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *ar
case 8:
return JS_NewInt64(js, frame_fps());
case 10:
editor_mode = js2bool(argv[1]);
break;
}
return JS_UNDEFINED;
@ -1510,6 +1472,18 @@ JSValue ID##_get_##ENTRY (JSContext *js, JSValue this) { \
return TYPE##2js(js2##ID (this)->ENTRY); \
} \
#define GETSET_PAIR_APPLY(ID, ENTRY, TYPE) \
JSValue ID##_set_##ENTRY (JSContext *js, JSValue this, JSValue val) { \
ID *id = js2##ID (this); \
id->ENTRY = js2##TYPE (val); \
ID##_apply(id); \
return JS_UNDEFINED; \
} \
\
JSValue ID##_get_##ENTRY (JSContext *js, JSValue this) { \
return TYPE##2js(js2##ID (this)->ENTRY); \
} \
#define GETSET_PAIR_HOOK(ID, ENTRY) \
JSValue ID##_set_##ENTRY (JSContext *js, JSValue this, JSValue val) { \
ID *n = js2##ID (this); \
@ -1815,6 +1789,56 @@ static const JSCFunctionListEntry js_sound_funcs[] = {
CGETSET_ADD(sound, hook)
};
static JSValue window_get_size(JSContext *js, JSValueConst this) { return vec22js(js2window(this)->size); }
static JSValue window_set_size(JSContext *js, JSValueConst this, JSValue v) {
window *w = js2window(this);
if (!w->start)
w->size = js2vec2(v);
return JS_UNDEFINED;
}
GETSET_PAIR_APPLY(window, rendersize, vec2)
GETSET_PAIR(window, mode, number)
static JSValue window_get_fullscreen(JSContext *js, JSValueConst this)
{
return number2js(js2window(this)->fullscreen);
}
static JSValue window_set_fullscreen(JSContext *js, JSValueConst this, JSValue v)
{
window_setfullscreen(js2window(this), js2bool(v));
return JS_UNDEFINED;
}
static JSValue window_set_title(JSContext *js, JSValueConst this, JSValue v)
{
window *w = js2window(this);
if (w->title) JS_FreeCString(js, w->title);
w->title = js2str(v);
if (w->start)
sapp_set_window_title(w->title);
return JS_UNDEFINED;
}
static JSValue window_get_title(JSContext *js, JSValueConst this, JSValue v)
{
if (!js2window(this)->title) return JS_UNDEFINED;
return str2js(js2window(this)->title);
}
GETSET_PAIR(window, editor, bool)
static const JSCFunctionListEntry js_window_funcs[] = {
CGETSET_ADD(window, size),
CGETSET_ADD(window, rendersize),
CGETSET_ADD(window, mode),
CGETSET_ADD(window, fullscreen),
CGETSET_ADD(window, title),
CGETSET_ADD(window, editor)
};
#define GETSET_PAIR_BODY(ID, ENTRY, TYPE) \
JSValue ID##_set_##ENTRY (JSContext *js, JSValue this, JSValue val) { \
js2##ID (this)->ENTRY = js2##TYPE (val); \
@ -2065,7 +2089,7 @@ void ffi_load() {
DUK_FUNC(performance, 2)
JS_FreeValue(js,globalThis);
QJSCLASSPREP(ptr);
QJSCLASSPREP_FUNCS(gameobject);
@ -2081,9 +2105,14 @@ void ffi_load() {
QJSCLASSPREP_FUNCS(sprite);
QJSCLASSPREP_FUNCS(texture);
QJSCLASSPREP_FUNCS(constraint);
QJSCLASSPREP_FUNCS(window);
QJSGLOBALCLASS(os);
QJSGLOBALCLASS(io);
JS_SetPropertyStr(js, globalThis, "Window", window2js(&mainwin));
JS_FreeValue(js,globalThis);
}
void ffi_stop()

View file

@ -250,8 +250,7 @@ static sg_trace_hooks hooks = {
};
void render_init() {
mainwin.width = sapp_width();
mainwin.height = sapp_height();
mainwin.size = (HMM_Vec2){sapp_width(), sapp_height()};
sg_setup(&(sg_desc){
.environment = sglue_environment(),
.logger = { .func = sg_logging },
@ -374,14 +373,14 @@ HMM_Vec2 world2screen(HMM_Vec2 pos)
{
pos = HMM_SubV2(pos, HMM_V2(cam_pos().x, cam_pos().y));
pos = HMM_ScaleV2(pos, 1.0/zoom);
pos = HMM_AddV2(pos, HMM_V2(mainwin.width/2.0, mainwin.height/2.0));
pos = HMM_AddV2(pos, HMM_ScaleV2(mainwin.size,0.5));
return pos;
}
HMM_Vec2 screen2world(HMM_Vec2 pos)
{
pos = HMM_ScaleV2(pos, 1/mainwin.dpi);
pos = HMM_SubV2(pos, HMM_V2(mainwin.width/2.0, mainwin.height/2.0));
pos = HMM_SubV2(pos, HMM_ScaleV2(mainwin.size, 0.5));
pos = HMM_ScaleV2(pos, zoom);
pos = HMM_AddV2(pos, HMM_V2(cam_pos().x, cam_pos().y));
return pos;
@ -399,43 +398,31 @@ HMM_Vec3 dirl_pos = {4, 100, 20};
#define MODE_EXPAND 4
#define MODE_FULL 5
int aspect_mode = MODE_FULL;
void full_2d_pass(struct window *window)
{
float aspect = mainwin.width/mainwin.height;
float raspect = mainwin.rwidth/mainwin.rheight;
float pwidth = window->width*raspect/aspect;
float left = (window->width-pwidth)/2;
float pheight = window->height*aspect/raspect;
float top = (window->height-pheight)/2;
HMM_Vec2 usesize = window->rendersize;
float usewidth, useheight;
usewidth = window->rwidth;
useheight = window->rheight;
switch(aspect_mode) {
switch(window->mode) {
case MODE_STRETCH:
sg_apply_viewportf(0,0,window->width,window->height,1);
sg_apply_viewportf(0,0,window->size.x,window->size.y,1);
break;
case MODE_WIDTH:
sg_apply_viewportf(0, top, window->width, pheight,1); // keep width
sg_apply_viewportf(0, window->top, window->size.x, window->psize.y,1); // keep width
break;
case MODE_HEIGHT:
sg_apply_viewportf(left,0,pwidth, window->height,1); // keep height
sg_apply_viewportf(window->left,0,window->psize.x, window->size.y,1); // keep height
break;
case MODE_KEEP:
sg_apply_viewportf(0,0,window->rwidth, window->rheight, 1); // no scaling
sg_apply_viewportf(0,0,window->rendersize.x, window->rendersize.y, 1); // no scaling
break;
case MODE_EXPAND:
if (aspect < raspect)
sg_apply_viewportf(0, top, window->width, pheight,1); // keep width
if (window->aspect < window->raspect)
sg_apply_viewportf(0, window->top, window->size.x, window->psize.y,1); // keep width
else
sg_apply_viewportf(left,0,pwidth, window->height,1); // keep height
sg_apply_viewportf(window->left,0,window->psize.x, window->size.y,1); // keep height
break;
case MODE_FULL:
usewidth = window->width;
useheight = window->height;
usesize = window->size;
break;
}
@ -443,12 +430,12 @@ void full_2d_pass(struct window *window)
cpVect pos = cam_pos();
projection = HMM_Orthographic_LH_NO(
pos.x - zoom * usewidth / 2,
pos.x + zoom * usewidth / 2,
pos.y - zoom * useheight / 2,
pos.y + zoom * useheight / 2, -10000.f, 10000.f);
pos.x - zoom * usesize.x / 2,
pos.x + zoom * usesize.x / 2,
pos.y - zoom * usesize.y / 2,
pos.y + zoom * usesize.y / 2, -10000.f, 10000.f);
hudproj = HMM_Orthographic_LH_ZO(0, usewidth, 0, useheight, -1.f, 1.f);
hudproj = HMM_Orthographic_LH_ZO(0, usesize.x, 0, usesize.y, -1.f, 1.f);
sprite_draw_all();
model_draw_all();

View file

@ -59,8 +59,6 @@ void openglInit3d(struct window *window);
void openglRender3d(struct window *window, camera3d *camera);
void capture_screen(int x, int y, int w, int h, const char *path);
extern int aspect_mode;
void debug_draw_phys(int draw);
void set_cam_body(cpBody *body);

View file

@ -207,7 +207,7 @@ JSValue script_runfile(const char *file)
size_t len;
char *script = slurp_text(file, &len);
if (!script) return JS_UNDEFINED;
YughWarn("Eval %s.", file);
JSValue obj = JS_Eval(js, script, len, file, JS_EVAL_FLAGS);
js_print_exception(obj);

View file

@ -15,7 +15,7 @@
#include "sokol/sokol_app.h"
#include "stb_image_resize2.h"
struct window mainwin;
struct window mainwin = {0};
static struct window *windows = NULL;
@ -23,17 +23,24 @@ struct texture *icon = NULL;
void window_resize(int width, int height)
{
mainwin.dpi = sapp_dpi_scale();
mainwin.width = width;
mainwin.height = height;
float aspect = mainwin.width/mainwin.height;
float raspect = mainwin.rwidth/mainwin.rheight;
mainwin.pheight = mainwin.rheight;
mainwin.pwidth = mainwin.rwidth*aspect/raspect;
window *w = &mainwin;
w->size.x = width;
w->size.y = height;
window_apply(w);
script_evalf("prosperon.resize([%d,%d]);", width,height);
}
void window_apply(window *w)
{
w->aspect = w->size.x/w->size.y;
w->raspect = w->rendersize.x/w->rendersize.y;
w->psize.x = w->size.x*(w->raspect/w->aspect);
w->psize.y = w->size.y*(w->aspect/w->raspect);
w->left = (w->size.x-w->psize.x)/2;
w->top = (w->size.y-w->psize.y)/2;
}
void window_focused(int focus)
{
mainwin.focus = focus;
@ -56,19 +63,15 @@ void window_set_icon(const char *png) {
window_seticon(&mainwin, icon);
}
void window_makefullscreen(struct window *w) {
if (!w->fullscreen)
window_togglefullscreen(w);
}
void window_unfullscreen(struct window *w) {
if (w->fullscreen)
window_togglefullscreen(w);
}
void window_togglefullscreen(struct window *w) {
void window_setfullscreen(window *w, int f)
{
if (w->fullscreen == f) return;
w->fullscreen = f;
if (!w->start) return;
if (sapp_is_fullscreen() == f) return;
sapp_toggle_fullscreen();
mainwin.fullscreen = sapp_is_fullscreen();
}
void window_seticon(struct window *w, struct texture *tex)
@ -105,3 +108,8 @@ void window_seticon(struct window *w, struct texture *tex)
void window_render(struct window *w) {
openglRender(w);
}
void window_free(window *w)
{
}

View file

@ -1,11 +1,15 @@
#ifndef WINDOW_H
#define WINDOW_H
#include <HandmadeMath.h>
struct window {
int id;
float width, height; // The actual width and height of the window
float rwidth, rheight; // The desired rendering resolution, what the assets are at
float pwidth, pheight; // The calculated width and height passed to rendering
HMM_Vec2 size; // Actual width and height of the window
HMM_Vec2 rendersize; // The desired rendering resolution, what the assets are at
HMM_Vec2 psize;
float left;
float top;
double dpi;
int render;
int mouseFocus;
@ -15,7 +19,14 @@ struct window {
int iconified;
int focus;
int shown;
int mode;
int editor; // true if only should redraw on input
float aspect;
float raspect;
char *title;
int start;
};
typedef struct window window;
struct texture;
extern struct window mainwin;
@ -24,10 +35,11 @@ void window_focused(int focus);
void window_iconified(int s);
void window_suspended(int s);
void window_makefullscreen(struct window *w);
void window_togglefullscreen(struct window *w);
void window_unfullscreen(struct window *w);
void window_apply(window *w);
void window_free(window *w);
void window_setfullscreen(window *w, int f);
void window_set_icon(const char *png);
void window_seticon(struct window *w, struct texture *icon);

View file

@ -80,9 +80,6 @@ static float timescale = 1.f;
#define SIM_STEP 2
static int sim_play = SIM_PLAY;
static int SAPP_STARTED = 0;
int editor_mode = 0;
static int argc;
static char **args;
@ -96,7 +93,7 @@ void seghandle()
static JSValue c_init_fn;
void c_init() {
SAPP_STARTED = 1;
mainwin.start = 1;
script_evalf("world_start();");
render_init();
window_set_icon("icons/moon.gif");
@ -150,7 +147,7 @@ static void process_frame()
void c_frame()
{
if (editor_mode) return;
if (mainwin.editor) return;
process_frame();
}
@ -174,7 +171,7 @@ void c_event(const sapp_event *e)
wchar_t wcode;
switch (e->type) {
case SAPP_EVENTTYPE_MOUSE_MOVE:
script_evalf("prosperon.mousemove([%g, %g], [%g, %g]);", e->mouse_x, mainwin.height -e->mouse_y, e->mouse_dx, -e->mouse_dy);
script_evalf("prosperon.mousemove([%g, %g], [%g, %g]);", e->mouse_x, mainwin.size.y -e->mouse_y, e->mouse_dx, -e->mouse_dy);
break;
case SAPP_EVENTTYPE_MOUSE_SCROLL:
@ -260,7 +257,7 @@ void c_event(const sapp_event *e)
break;
}
if (editor_mode)
if (mainwin.editor)
process_frame();
}
@ -279,7 +276,7 @@ static sapp_desc start_desc = {
.high_dpi = 0,
.sample_count = 1,
.fullscreen = 1,
.window_title = "Prosperon",
.window_title = NULL,
.enable_clipboard = false,
.clipboard_size = 0,
.enable_dragndrop = true,
@ -292,11 +289,6 @@ static sapp_desc start_desc = {
.logger.func = sg_logging,
};
void app_name(const char *name) {
start_desc.window_title = strdup(name);
sapp_set_window_title(name);
}
int main(int argc, char **argv) {
setlocale(LC_ALL, "en_US.utf8");
#ifndef NDEBUG
@ -354,16 +346,17 @@ void engine_start(JSValue fn)
sound_init();
phys2d_init();
start_desc.width = mainwin.width;
start_desc.height = mainwin.height;
start_desc.fullscreen = 0;
start_desc.width = mainwin.size.x;
start_desc.height = mainwin.size.y;
start_desc.window_title = mainwin.title;
start_desc.fullscreen = mainwin.fullscreen;
sapp_run(&start_desc);
}
double apptime() { return stm_sec(stm_diff(stm_now(), start_t)); }
void quit() {
if (SAPP_STARTED)
if (mainwin.start)
sapp_quit();
else {
cleanup();

View file

@ -14,8 +14,6 @@ void set_timescale(float val);
void print_stacktrace();
void engine_start(JSValue fn); /* fn runs after the engine starts */
void app_name(const char *name);
int frame_fps();
double get_timescale();
void quit();
@ -24,5 +22,4 @@ double apptime();
extern double renderMS;
extern double physMS;
extern double updateMS;
extern int editor_mode;
#endif