Files
cell/scripts/prosperon.js
2025-01-18 18:15:15 -06:00

562 lines
13 KiB
JavaScript

globalThis.gamestate = {};
global.pull_registers = function(obj)
{
var reggies = [];
for (var reg in Register.registries) {
if (!Register.registries[reg].register) return;
if (typeof obj[reg] === "function")
reggies.push(reg);
}
return reggies;
}
function register_obj(obj, reg)
{
var fn = obj[reg].bind(obj);
fn.layer = obj[reg].layer;
var name = obj.ur ? obj.ur.name : obj.toString();
obj.timers.push(Register.registries[reg].register(fn, name));
if (!obj[reg].name) Object.defineProperty(obj[reg], 'name', {value:`${obj._file}_${reg}`});
}
global.check_registers = function check_registers(obj) {
if (obj.__reggies) {
if (obj.__reggies.length == 0) return;
// fast path
for (var reg of obj.__reggies)
register_obj(obj,reg);
return;
}
for (var reg in Register.registries) {
if (!Register.registries[reg].register) return;
if (typeof obj[reg] === "function")
register_obj(obj,reg);
}
/* for (var k in obj) {
if (!k.startsWith("on_")) continue;
var signal = k.fromfirst("on_");
Event.observe(signal, obj, obj[k]);
}*/
};
globalThis.timers = []
global.setTimeout = function(fn,seconds, ...args) {
return prosperon.add_timer(globalThis, _ => fn.apply(undefined,args), seconds);
}
global.clearTimeout = function(id) { id(); }
prosperon.delay = setTimeout;
global.obscure("global");
global.mixin("render");
global.mixin("debug");
global.mixin('layout')
globalThis.parseq = use('parseq');
globalThis.sim = {};
sim.mode = "play";
sim.play = function () {
this.mode = "play";
// os.reindex_static();
game.all_objects(o => {
if (!o._started) {
o._started = true;
o.start?.();
}
});
};
sim.playing = function () {
return this.mode === "play";
};
sim.pause = function () {
this.mode = "pause";
};
sim.paused = function () {
return this.mode === "pause";
};
sim.step = function () {
this.mode = "step";
};
sim.stepping = function () {
return this.mode === "step";
};
var frame_t = profile.now();
var physlag = 0;
prosperon.SIGABRT = function()
{
console.error(new Error('SIGABRT'));
os.exit(1);
}
prosperon.SIGSEGV = function()
{
console.error(new Error('SIGSEGV'));
os.exit(1);
}
prosperon.exit = function()
{
}
prosperon.init = function () {
render.init();
// imgui.init(render._main);
tracy.gpu_init();
globalThis.audio = use("sound.js");
world_start();
/* shape.quad = {
pos: os.make_buffer([
0, 0, 0,
0, 1, 0,
1, 0, 0,
1, 1, 0], 0),
verts: 4,
uv: os.make_buffer([
0, 1,
0, 0,
1, 1,
1, 0], 2),
index: os.make_buffer([0, 1, 2, 2, 1, 3], 1),
count: 6,
};
shape.triangle = {
pos: os.make_buffer([0, 0, 0, 0.5, 1, 0, 1, 0, 0], 0),
uv: os.make_buffer([0, 0, 0.5, 1, 1, 0], 2),
verts: 3,
count: 3,
index: os.make_buffer([0, 1, 2], 1),
};*/
if (io.exists("main.js")) global.app = actor.spawn("main.js");
else global.app = actor.spawn("nogame.js");
};
prosperon.release_mode = function () {
prosperon.debug = false;
debug.kill();
};
prosperon.debug = true;
game.timescale = 1;
var eachobj = function (obj, fn) {
var val = fn(obj);
if (val) return val;
for (var o in obj.objects) {
if (obj.objects[o] === obj) console.error(`Object ${obj.toString()} is referenced by itself.`);
val = eachobj(obj.objects[o], fn);
if (val) return val;
}
};
game.all_objects = function (fn, startobj = world) {
return eachobj(startobj, fn);
};
game.find_object = function (fn, startobj = world) {};
game.tags = {};
game.tag_add = function (tag, obj) {
game.tags[tag] ??= {};
game.tags[tag][obj.guid] = obj;
};
game.tag_rm = function (tag, obj) {
delete game.tags[tag][obj.guid];
};
game.tag_clear_guid = function (guid) {
for (var tag in game.tags) delete game.tags[tag][guid];
};
game.objects_with_tag = function (tag) {
if (!game.tags[tag]) return [];
return Object.values(game.tags[tag]);
};
game.doc = {};
game.doc.object = "Returns the entity belonging to a given id.";
game.doc.pause = "Pause game simulation.";
game.doc.play = "Resume or start game simulation.";
function calc_image_size(img)
{
if (!img.texture || !img.rect) return;
return [img.texture.width*img.rect.width, img.texture.height*img.rect.height];
}
function create_image(path)
{
var data = io.slurpbytes(path);
var newimg;
switch(path.ext()) {
case 'gif':
newimg = os.make_gif(data);
if (newimg.surface)
newimg.texture = render._main.load_texture(newimg.surface);
else
for (var frame of newimg.frames)
frame.texture = render._main.load_texture(frame.surface);
break;
case 'ase':
case 'aseprite':
newimg = os.make_aseprite(data);
if (newimg.surface)
newimg.texture = render._main.load_texture(newimg.surface);
else {
for (var anim in newimg) {
var a = newimg[anim];
for (var frame of a.frames)
frame.texture = render._main.load_texture(frame.surface);
}
}
break;
default:
newimg = {
surface: os.make_texture(data)
};
newimg.texture = render._main.load_texture(newimg.surface);
break;
}
return newimg;
}
function merge_objects(oldobj,newobj, properties) {
function recursive_merge(target,src) {
for (var key of Object.keys(src)) {
if (properties.includes(key)) {
target[key] = src[key];
continue;
}
if (src[key] && typeof src[key] === 'object' && target[key] && typeof target[key] === 'object')
recursive_merge(target[key],src[key])
}
}
recursive_merge(oldobj,newobj);
}
game.tex_hotreload = function tex_hotreload(file) {
if (!(file in game.texture.cache)) return;
var img = create_image(file);
var oldimg = game.texture.cache[file];
console.log(json.encode(img))
merge_objects(oldimg,img, ['surface', 'texture', 'loop', 'time']);
game.texture.cache[file] = img;
};
var image = {};
image.dimensions = function()
{
return [this.texture.width, this.texture.height].scale([this.rect[2], this.rect[3]]);
}
var spritesheet;
var sheet_frames = [];
var sheetsize = 1024;
function pack_into_sheet(images)
{
return;
if (!Array.isArray(images)) images = [images];
if (images[0].texture.width > 300 && images[0].texture.height > 300) return;
sheet_frames = sheet_frames.concat(images);
var sizes = sheet_frames.map(x => [x.rect.width*x.texture.width, x.rect.height*x.texture.height]);
var pos = os.rectpack(sheetsize, sheetsize, sizes);
if (!pos) {
console.error(`did not make spritesheet properly from images ${images}`);
console.info(sizes);
return;
}
var newsheet = os.make_tex_data(sheetsize,sheetsize);
for (var i = 0; i < pos.length; i++) {
// Copy the texture to the new sheet
newsheet.copy(sheet_frames[i].texture, pos[i], sheet_frames[i].rect);
// Update the frame's rect to the new position in normalized coordinates
sheet_frames[i].rect.x = pos[i][0] / newsheet.width;
sheet_frames[i].rect.y = pos[i][1] / newsheet.height;
sheet_frames[i].rect.width = sizes[i][0] / newsheet.width;
sheet_frames[i].rect.height = sizes[i][1] / newsheet.height;
sheet_frames[i].texture = newsheet;
}
newsheet.load_gpu();
spritesheet = newsheet;
return spritesheet;
}
game.is_image = function(obj)
{
if (obj.texture && obj.rect) return true;
}
// Any request to it returns an image, which is a texture and rect.
game.texture = function texture(path) {
if (typeof path !== 'string') {
return path;
throw new Error('need a string for game.texture')
}
var parts = path.split(':');
var ipath = Resources.find_image(parts[0]);
game.texture.cache[ipath] ??= create_image(ipath);
return game.texture.cache[ipath];
}
game.texture.cache = {};
game.texture.time_cache = {};
game.texture.total_size = function()
{
var size = 0;
// Object.values(game.texture.cache).forEach(x => size += x.texture.inram() ? x..texture.width*x.texture.height*4 : 0);
return size;
}
game.texture.total_vram = function()
{
var vram = 0;
// Object.values(game.texture.cache).forEach(x => vram += x.vram);
return vram;
}
prosperon.semver = {};
prosperon.semver.valid = function (v, range) {
v = v.split(".");
range = range.split(".");
if (v.length !== 3) return undefined;
if (range.length !== 3) return undefined;
if (range[0][0] === "^") {
range[0] = range[0].slice(1);
if (parseInt(v[0]) >= parseInt(range[0])) return true;
return false;
}
if (range[0] === "~") {
range[0] = range[0].slice(1);
for (var i = 0; i < 2; i++) if (parseInt(v[i]) < parseInt(range[i])) return false;
return true;
}
return prosperon.semver.cmp(v.join("."), range.join(".")) === 0;
};
prosperon.semver.cmp = function (v1, v2) {
var ver1 = v1.split(".");
var ver2 = v2.split(".");
for (var i = 0; i < 3; i++) {
var n1 = parseInt(ver1[i]);
var n2 = parseInt(ver2[i]);
if (n1 > n2) return 1;
else if (n1 < n2) return -1;
}
return 0;
};
prosperon.semver.cmp.doc = "Compare two semantic version numbers, given like X.X.X.";
prosperon.semver.valid.doc = `Test if semantic version v is valid, given a range.
Range is given by a semantic versioning number, prefixed with nothing, a ~, or a ^.
~ means that MAJOR and MINOR must match exactly, but any PATCH greater or equal is valid.
^ means that MAJOR must match exactly, but any MINOR and PATCH greater or equal is valid.`;
global.mixin("input");
global.mixin("std");
global.mixin("diff");
global.mixin("color");
global.mixin("tween");
global.mixin("particle");
//global.mixin("physics");
global.mixin("geometry");
/*
Factory for creating registries. Register one with 'X.register',
which returns a function that, when invoked, cancels the registry.
*/
globalThis.Register = {
registries: [],
add_cb(name) {
var n = {};
var fns = [];
n.register = function (fn, oname) {
if (!(fn instanceof Function)) return;
var guid = prosperon.guid();
var dofn = function (...args) {
fn(...args);
};
Object.defineProperty(dofn, 'name', {value:`do_${oname}`});
var left = 0;
var right = fns.length - 1;
dofn.layer = fn.layer;
dofn.layer ??= 0;
while (left <= right) {
var mid = Math.floor((left + right) / 2);
if (fns[mid] === dofn.layer) {
left = mid;
break;
} else if (fns[mid].layer < dofn.layer) left = mid + 1;
else right = mid - 1;
}
fns.splice(left, 0, dofn);
return function () {
fns.remove(dofn);
};
};
prosperon[name] = function (...args) {
// tracy.fiber_enter(vector.fib);
fns.forEach(fn => fn(...args));
// tracy.fiber_leave(vector.fib);
};
Object.defineProperty(prosperon[name], 'name', {value:name});
prosperon[name].fns = fns;
n.clear = function () {
fns = [];
};
Register[name] = n;
Register.registries[name] = n;
return n;
},
};
Register.add_cb("appupdate");
Register.add_cb("update").doc = "Called once per frame.";
Register.add_cb("physupdate");
Register.add_cb("gui");
Register.add_cb("hud");
Register.add_cb("draw");
Register.add_cb("imgui");
Register.add_cb("app");
Register.add_cb("prerender");
global.mixin("components");
var Event = {
events: {},
observe(name, obj, fn) {
this.events[name] ??= [];
this.events[name].push([obj, fn]);
},
unobserve(name, obj) {
this.events[name] = this.events[name].filter(x => x[0] !== obj);
},
rm_obj(obj) {
Object.keys(this.events).forEach(name => Event.unobserve(name, obj));
},
notify(name, ...args) {
if (!this.events[name]) return;
this.events[name].forEach(function (x) {
x[1].call(x[0], ...args);
});
},
};
prosperon.add_timer = function(obj, fn, seconds)
{
var timers = obj.timers;
var stop = function () {
timers.remove(stop);
timer.fn = undefined;
timer = undefined;
};
function execute() {
if (fn)
timer.remain = fn(stop.seconds);
if (!timer.remain)
stop();
else
stop.seconds = timer.remain;
}
var timer = os.make_timer(execute);
timer.remain = seconds;
stop.remain = seconds;
stop.seconds = seconds;
timers.push(stop);
return stop;
}
global.mixin("spline");
global.mixin("actor");
global.mixin("entity");
function world_start() {
globalThis.world = Object.create(entity);
world.transform = os.make_transform();
world.objects = {};
world.toString = function () {
return "world";
};
world.ur = "world";
world.kill = function () {
this.clear();
};
world.phys = 2;
world.zoom = 1;
world._ed = { selectable: false };
world.ur = {};
world.ur.fresh = {};
game.cam = world;
}
function make_spritesheet(paths, width, height)
{
var textures = paths.map(path => game.texture(path));
var sizes = textures.map(tex => [tex.width, tex.height]);
var pos = os.rectpack(width, height, sizes);
if (!pos) return;
var sheet = os.make_tex_data(width,height);
var st = profile.now();
for (var i = 0; i < pos.length; i++)
sheet.copy(textures[i], pos[i].x, pos[i].y);
sheet.save("spritesheet.qoi");
gamestate.spritess = sheet;
sheet.load_gpu();
}
return {
sim,
frame_t,
physlag,
Event,
};