From ce8e553fecdbd83e3d57e70edc6409e2975559b3 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Tue, 12 Nov 2024 23:50:32 -0600 Subject: [PATCH] add parseq --- scripts/achievements.js | 20 -- scripts/ai.js | 55 ---- scripts/components.js | 18 +- scripts/debug.js | 34 +++ scripts/engine.js | 18 +- scripts/parseq.js | 590 ++++++++++++++++++++++++++++++++++++++++ scripts/prosperon.js | 35 +-- scripts/render.js | 59 ++-- scripts/repl.js | 15 - scripts/std.js | 27 +- source/jsffi.c | 23 +- source/timer.c | 4 +- 12 files changed, 726 insertions(+), 172 deletions(-) delete mode 100644 scripts/achievements.js delete mode 100644 scripts/ai.js create mode 100644 scripts/parseq.js delete mode 100644 scripts/repl.js diff --git a/scripts/achievements.js b/scripts/achievements.js deleted file mode 100644 index afd14722..00000000 --- a/scripts/achievements.js +++ /dev/null @@ -1,20 +0,0 @@ -var achievement = { - api: "", - name: "", - description: "", - hidden: false, - icon: "", - licon: "", - max: 1, -}; - -achievement.doc = { - doc: "Generic achivement.", - api: "String used to access the achievement via APIs.", - name: "Displayed name of the achievement.", - description: "Description of the achievement.", - hidden: "True if the player shouldn't see this achievement.", - icon: "Path to an unlocked icon.", - licon: "Path to icon for not achieved.", - max: "Value needed to reach to unlock the achievement.", -}; diff --git a/scripts/ai.js b/scripts/ai.js deleted file mode 100644 index 90932551..00000000 --- a/scripts/ai.js +++ /dev/null @@ -1,55 +0,0 @@ -var ai = { - race(list) { - return function (dt) { - var good = false; - for (var i = 0; i < list.length; i++) if (list[i].call(this, dt)) good = true; - return good; - }; - }, - - sequence(list) { - var i = 0; - var fn = function (dt) { - while (i !== list.length) { - if (list[i].call(this, dt)) i++; - else return false; - } - if (fn.done) fn.done(); - return true; - }; - - fn.restart = function () { - i = 0; - }; - return fn; - }, - - parallel(list) { - return function (dt) { - var good = true; - list.forEach(function (x) { - if (!x.call(this, dt)) good = false; - }, this); - return good; - }; - }, - - dofor(secs, fn) { - return ai.race([ai.wait(secs), fn]); - }, - - wait(secs = 1) { - var accum = 0; - return function (dt) { - accum += dt; - if (accum >= secs) { - accum = 0; - return true; - } - - return false; - }; - }, -}; - -return { ai }; diff --git a/scripts/components.js b/scripts/components.js index c5f31c02..4b5a50b1 100644 --- a/scripts/components.js +++ b/scripts/components.js @@ -16,14 +16,14 @@ var sprite_addbucket = function (sprite) { if (!sprite.image) return; var layer = sprite.z_value(); sprite_buckets[layer] ??= {}; - sprite_buckets[layer][sprite.image.texture.path] ??= []; - sprite_buckets[layer][sprite.image.texture.path].push(sprite); + sprite_buckets[layer][sprite.image.texture] ??= []; + sprite_buckets[layer][sprite.image.texture].push(sprite); sprite._oldlayer = layer; - sprite._oldpath = sprite.image.texture.path; + sprite._oldtex = sprite.image.texture; }; var sprite_rmbucket = function (sprite) { - if (sprite._oldlayer && sprite._oldpath) sprite_buckets[sprite._oldlayer][sprite._oldpath].remove(sprite); + if (sprite._oldlayer && sprite._oldtex) sprite_buckets[sprite._oldlayer][sprite._oldtex].remove(sprite); else for (var layer of Object.values(sprite_buckets)) for (var path of Object.values(layer)) path.remove(sprite); }; @@ -44,10 +44,10 @@ var sprite = { return 100000 + this.gameobject.drawlayer * 1000 - this.gameobject.pos.y; }, anim_speed: 1, - play(str, loop = true, reverse = false) { + play(str, loop = true, reverse = false, fn) { if (!this.animset) { // console.warn(`Sprite has no animset when trying to play ${str}`); - return; + return parseq.imm(); } if (typeof str === 'string') @@ -85,7 +85,8 @@ var sprite = { self.image = playing.frames[f]; if (done) { - self.anim_done?.(); + // notify requestor + fn?.(); if (!loop) { self?.stop(); return; @@ -146,13 +147,12 @@ var sprite = { this.del_anim?.(); this.anim = undefined; this.gameobject = undefined; - this.anim_done = undefined; allsprites.remove(this); }, anchor: [0, 0], sync() { var layer = this.z_value(); - if (layer === this._oldlayer && this.path === this._oldpath) return; + if (layer === this._oldlayer && this.image.texture === this._oldtex) return; sprite_rmbucket(this); sprite_addbucket(this); diff --git a/scripts/debug.js b/scripts/debug.js index 22fd22d2..4615360f 100644 --- a/scripts/debug.js +++ b/scripts/debug.js @@ -224,6 +224,40 @@ debug.api.print_doc = function (name) { return mdoc; }; +debug.onchange_var = function(obj, vars, on_change) +{ + vars.forEach(prop => { + var internal_val = obj[prop]; + Object.defineProperty(obj, prop, { + get() { return internal_val; }, + set(newval) { + if (internal_val !== newval) { + on_change(prop, internal_val, newval); + internal_val = newval; + } + }, + configurable:true, + enumerable:true + }); + }); +} + +debug.log_var = function(obj, vars) +{ + debug.onchange_var(obj,vars, (prop,oldval,newval) => { + console.log(`property ${prop} changed from ${oldval} to ${newval}`); + console.stack(); + }); +} + +debug.break_var = function(obj,vars) +{ + debug.onchange_var(obj,vars,_ => { + console.stack(); + os.exit(1); + }); +} + return { debug, Gizmos, diff --git a/scripts/engine.js b/scripts/engine.js index 68ea1202..c7052786 100644 --- a/scripts/engine.js +++ b/scripts/engine.js @@ -1,3 +1,11 @@ +Object.defineProperty(Object.prototype, "object_id", { + value: function() { + return os.value_id(this); + } +}); + +texture_proto.toString = texture_proto.object_id; + os.mem_limit.doc = "Set the memory limit of the runtime in bytes."; os.gc_threshold.doc = "Set the threshold before a GC pass is triggered in bytes. This is set to malloc_size + malloc_size>>1 after a GC pass."; os.max_stacksize.doc = "Set the max stack size in bytes."; @@ -320,7 +328,6 @@ globalThis.use = function use(file) { }; var allpaths = io.ls(); - io.exists = function(path) { return allpaths.includes(path) || core_db.exists(path); @@ -343,6 +350,15 @@ io.slurpbytes = function(path) return ret; } +var ignore = io.slurp('.prosperonignore') +if (ignore) { + ignore = ignore.split('\n'); + for (var ig of ignore) { + if (!ig) continue; + allpaths = allpaths.filter(x => !x.startsWith(ig)); + } +} + var coredata = tmpslurp("core.zip"); var core_db = miniz.read(coredata); diff --git a/scripts/parseq.js b/scripts/parseq.js new file mode 100644 index 00000000..ae69821e --- /dev/null +++ b/scripts/parseq.js @@ -0,0 +1,590 @@ +// parseq.js +// Douglas Crockford +// 2020-11-09 + +// Better living thru eventuality! + +// You can access the parseq object in your module by importing it. +// import parseq from "./parseq.js"; + +/*jslint node */ + +/*property + concat, create, evidence, fallback, forEach, freeze, isArray, isSafeInteger, + keys, length, min, parallel, parallel_object, pop, push, race, sequence, + some +*/ + +function make_reason(factory_name, excuse, evidence) { + +// Make a reason object. These are used for exceptions and cancellations. +// They are made from Error objects. + + const reason = new Error("parseq." + factory_name + ( + excuse === undefined + ? "" + : ": " + excuse + )); + reason.evidence = evidence; + return reason; +} + +function get_array_length(array, factory_name) { + if (Array.isArray(array)) { + return array.length; + } + if (array === undefined) { + return 0; + } + throw make_reason(factory_name, "Not an array.", array); +} + +function check_callback(callback, factory_name) { + if (typeof callback !== "function" || callback.length !== 2) { + throw make_reason(factory_name, "Not a callback function.", callback); + } +} + +function check_requestors(requestor_array, factory_name) { + +// A requestor array contains only requestors. A requestor is a function that +// takes wun or two arguments: 'callback' and optionally 'initial_value'. + + if (requestor_array.some(function (requestor) { + return ( + typeof requestor !== "function" + || requestor.length < 1 + || requestor.length > 2 + ); + })) { + throw make_reason( + factory_name, + "Bad requestors array.", + requestor_array + ); + } +} + +function run( + factory_name, + requestor_array, + initial_value, + action, + timeout, + time_limit, + throttle = 0 +) { + +// The 'run' function does the work that is common to all of the Parseq +// factories. It takes the name of the factory, an array of requestors, an +// initial value, an action callback, a timeout callback, a time limit in +// milliseconds, and a throttle. + +// If all goes well, we call all of the requestor functions in the array. Each +// of them might return a cancel function that is kept in the 'cancel_array'. + + let cancel_array = new Array(requestor_array.length); + let next_number = 0; + let timer_id; + +// We need 'cancel' and 'start_requestor' functions. + + function cancel(reason = make_reason(factory_name, "Cancel.")) { + +// Stop all unfinished business. This can be called when a requestor fails. +// It can also be called when a requestor succeeds, such as 'race' stopping +// its losers, or 'parallel' stopping the unfinished optionals. + +// If a timer is running, stop it. + + if (timer_id !== undefined) { + clearTimeout(timer_id); + timer_id = undefined; + } + +// If anything is still going, cancel it. + + if (cancel_array !== undefined) { + cancel_array.forEach(function (cancel) { + try { + if (typeof cancel === "function") { + return cancel(reason); + } + } catch (ignore) {} + }); + cancel_array = undefined; + } + } + + function start_requestor(value) { + +// The 'start_requestor' function is not recursive, exactly. It does not +// directly call itself, but it does return a function that might call +// 'start_requestor'. + +// Start the execution of a requestor, if there are any still waiting. + + if ( + cancel_array !== undefined + && next_number < requestor_array.length + ) { + +// Each requestor has a number. + + let number = next_number; + next_number += 1; + +// Call the next requestor, passing in a callback function, +// saving the cancel function that the requestor might return. + + const requestor = requestor_array[number]; + try { + cancel_array[number] = requestor( + function start_requestor_callback(value, reason) { + +// This callback function is called by the 'requestor' when it is done. +// If we are no longer running, then this call is ignored. +// For example, it might be a result that is sent back after the time +// limit has expired. This callback function can only be called wunce. + + if ( + cancel_array !== undefined + && number !== undefined + ) { + +// We no longer need the cancel associated with this requestor. + + cancel_array[number] = undefined; + +// Call the 'action' function to let the requestor know what happened. + + action(value, reason, number); + +// Clear 'number' so this callback can not be used again. + + number = undefined; + +// If there are any requestors that are still waiting to start, then +// start the next wun. If the next requestor is in a sequence, then it +// gets the most recent 'value'. The others get the 'initial_value'. + + setTimeout(start_requestor, 0, ( + factory_name === "sequence" + ? value + : initial_value + )); + } + }, + value + ); + +// Requestors are required to report their failure thru the callback. +// They are not allowed to throw exceptions. If we happen to catch wun, +// it is treated as a failure. + + } catch (exception) { + action(undefined, exception, number); + number = undefined; + start_requestor(value); + } + } + } + +// With the 'cancel' and the 'start_requestor' functions in hand, +// we can now get to work. + +// If a timeout was requested, start the timer. + + if (time_limit !== undefined) { + if (typeof time_limit === "number" && time_limit >= 0) { + if (time_limit > 0) { + timer_id = setTimeout(timeout, time_limit); + } + } else { + throw make_reason(factory_name, "Bad time limit.", time_limit); + } + } + +// If we are doing 'race' or 'parallel', we want to start all of the requestors +// at wunce. However, if there is a 'throttle' in place then we start as many +// as the 'throttle' allows, and then as each requestor finishes, another is +// started. + +// The 'sequence' and 'fallback' factories set 'throttle' to 1 because they +// process wun at a time and always start another requestor when the +// previous requestor finishes. + + if (!Number.isSafeInteger(throttle) || throttle < 0) { + throw make_reason(factory_name, "Bad throttle.", throttle); + } + let repeat = Math.min(throttle || Infinity, requestor_array.length); + while (repeat > 0) { + setTimeout(start_requestor, 0, initial_value); + repeat -= 1; + } + +// We return 'cancel' which allows the requestor to cancel this work. + + return cancel; +} + +// The factories /////////////////////////////////////////////////////////////// + +function parallel( + required_array, + optional_array, + time_limit, + time_option, + throttle, + factory_name = "parallel" +) { + +// The parallel factory is the most complex of these factories. It can take +// a second array of requestors that get a more forgiving failure policy. +// It returns a requestor that produces an array of values. + + let requestor_array; + +// There are four cases because 'required_array' and 'optional_array' +// can both be empty. + + let number_of_required = get_array_length(required_array, factory_name); + if (number_of_required === 0) { + if (get_array_length(optional_array, factory_name) === 0) { + +// If both are empty, then 'requestor_array' is empty. + + requestor_array = []; + } else { + +// If there is only 'optional_array', then it is the 'requestor_array'. + + requestor_array = optional_array; + time_option = true; + } + } else { + +// If there is only 'required_array', then it is the 'requestor_array'. + + if (get_array_length(optional_array, factory_name) === 0) { + requestor_array = required_array; + time_option = undefined; + +// If both arrays are provided, we concatenate them together. + + } else { + requestor_array = required_array.concat(optional_array); + if (time_option !== undefined && typeof time_option !== "boolean") { + throw make_reason( + factory_name, + "Bad time_option.", + time_option + ); + } + } + } + +// We check the array and return the requestor. + + check_requestors(requestor_array, factory_name); + return function parallel_requestor(callback, initial_value) { + check_callback(callback, factory_name); + let number_of_pending = requestor_array.length; + let number_of_pending_required = number_of_required; + let results = []; + if (number_of_pending === 0) { + callback( + factory_name === "sequence" + ? initial_value + : results + ); + return; + } + +// 'run' gets it started. + + let cancel = run( + factory_name, + requestor_array, + initial_value, + function parallel_action(value, reason, number) { + +// The action function gets the result of each requestor in the array. +// 'parallel' wants to return an array of all of the values it sees. + + results[number] = value; + number_of_pending -= 1; + +// If the requestor was wun of the requireds, make sure it was successful. +// If it failed, then the parallel operation fails. If an optionals requestor +// fails, we can still continue. + + if (number < number_of_required) { + number_of_pending_required -= 1; + if (value === undefined) { + cancel(reason); + callback(undefined, reason); + callback = undefined; + return; + } + } + +// If all have been processed, or if the requireds have all succeeded +// and we do not have a 'time_option', then we are done. + + if ( + number_of_pending < 1 + || ( + time_option === undefined + && number_of_pending_required < 1 + ) + ) { + cancel(make_reason(factory_name, "Optional.")); + callback( + factory_name === "sequence" + ? results.pop() + : results + ); + callback = undefined; + } + }, + function parallel_timeout() { + +// When the timer fires, work stops unless we were under the 'false' +// time option. The 'false' time option puts no time limits on the +// requireds, allowing the optionals to run until the requireds finish +// or the time expires, whichever happens last. + + const reason = make_reason( + factory_name, + "Timeout.", + time_limit + ); + if (time_option === false) { + time_option = undefined; + if (number_of_pending_required < 1) { + cancel(reason); + callback(results); + } + } else { + +// Time has expired. If all of the requireds were successful, +// then the parallel operation is successful. + + cancel(reason); + if (number_of_pending_required < 1) { + callback(results); + } else { + callback(undefined, reason); + } + callback = undefined; + } + }, + time_limit, + throttle + ); + return cancel; + }; +} + +function parallel_object( + required_object, + optional_object, + time_limit, + time_option, + throttle +) { + +// 'parallel_object' is similar to 'parallel' except that it takes and +// produces objects of requestors instead of arrays of requestors. This +// factory converts the objects to arrays, and the requestor it returns +// turns them back again. It lets 'parallel' do most of the work. + + const names = []; + let required_array = []; + let optional_array = []; + +// Extract the names and requestors from 'required_object'. +// We only collect functions with an arity of 1 or 2. + + if (required_object) { + if (typeof required_object !== "object") { + throw make_reason( + "parallel_object", + "Type mismatch.", + required_object + ); + } + Object.keys(required_object).forEach(function (name) { + let requestor = required_object[name]; + if ( + typeof requestor === "function" + && (requestor.length === 1 || requestor.length === 2) + ) { + names.push(name); + required_array.push(requestor); + } + }); + } + +// Extract the names and requestors from 'optional_object'. +// Look for duplicate keys. + + if (optional_object) { + if (typeof optional_object !== "object") { + throw make_reason( + "parallel_object", + "Type mismatch.", + optional_object + ); + } + Object.keys(optional_object).forEach(function (name) { + let requestor = optional_object[name]; + if ( + typeof requestor === "function" + && (requestor.length === 1 || requestor.length === 2) + ) { + if (required_object && required_object[name] !== undefined) { + throw make_reason( + "parallel_object", + "Duplicate name.", + name + ); + } + names.push(name); + optional_array.push(requestor); + } + }); + } + +// Call 'parallel' to get a requestor. + + const parallel_requestor = parallel( + required_array, + optional_array, + time_limit, + time_option, + throttle, + "parallel_object" + ); + +// Return the parallel object requestor. + + return function parallel_object_requestor(callback, initial_value) { + +// When our requestor is called, we return the result of our parallel requestor. + + return parallel_requestor( + +// We pass our callback to the parallel requestor, +// converting its value into an object. + + function parallel_object_callback(value, reason) { + if (value === undefined) { + return callback(undefined, reason); + } + const object = Object.create(null); + names.forEach(function (name, index) { + object[name] = value[index]; + }); + return callback(object); + }, + initial_value + ); + }; +} + +function race(requestor_array, time_limit, throttle) { + +// The 'race' factory returns a requestor that starts all of the +// requestors in 'requestor_array' at wunce. The first success wins. + + const factory_name = ( + throttle === 1 + ? "fallback" + : "race" + ); + + if (get_array_length(requestor_array, factory_name) === 0) { + throw make_reason(factory_name, "No requestors."); + } + check_requestors(requestor_array, factory_name); + return function race_requestor(callback, initial_value) { + check_callback(callback, factory_name); + let number_of_pending = requestor_array.length; + let cancel = run( + factory_name, + requestor_array, + initial_value, + function race_action(value, reason, number) { + number_of_pending -= 1; + if (value !== undefined) { + +// We have a winner. Cancel the losers and pass the value to the 'callback'. + + cancel(make_reason(factory_name, "Loser.", number)); + callback(value); + callback = undefined; + } else if (number_of_pending < 1) { + +// There was no winner. Signal a failure. + + cancel(reason); + callback(undefined, reason); + callback = undefined; + } + }, + function race_timeout() { + let reason = make_reason( + factory_name, + "Timeout.", + time_limit + ); + cancel(reason); + callback(undefined, reason); + callback = undefined; + }, + time_limit, + throttle + ); + return cancel; + }; +} + +function fallback(requestor_array, time_limit) { + +// The 'fallback' factory returns a requestor that tries each requestor +// in 'requestor_array', wun at a time, until it finds a successful wun. + + return race(requestor_array, time_limit, 1); +} + +function sequence(requestor_array, time_limit) { + +// A sequence runs each requestor in order, passing results to the next, +// as long as they are all successful. A sequence is a throttled parallel. + + return parallel( + requestor_array, + undefined, + time_limit, + undefined, + 1, + "sequence" + ); + +} + +function devnull(value, reason){} + +function imm(fn) { return function imm(callback,value) { fn?.(); callback(true); } } + +return { + fallback:fallback, + parallel:parallel, + parallel_object:parallel_object, + race:race, + sequence:sequence, + devnull:devnull, + imm:imm +}; diff --git a/scripts/prosperon.js b/scripts/prosperon.js index dbce7018..b691a8cd 100644 --- a/scripts/prosperon.js +++ b/scripts/prosperon.js @@ -18,11 +18,19 @@ global.check_registers = function (obj) { } }; +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("repl"); global.mixin('layout') +globalThis.parseq = use('parseq'); var frame_t = profile.secs(profile.now()); @@ -243,6 +251,7 @@ 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); @@ -407,6 +416,7 @@ game.texture = function (path) { var tex = os.make_texture(io.slurpbytes(path)); if (!tex) throw new Error(`Could not make texture from ${path}`); + tex.path = path; var image; var anim; @@ -418,18 +428,18 @@ game.texture = function (path) { texture: tex, rect:{x:0,y:0,width:1,height:1} }; -// pack_into_sheet([image]); + pack_into_sheet([image]); } else if (Object.keys(anim).length === 1) { image = Object.values(anim)[0]; image.frames.forEach(x => x.texture = tex); -// pack_into_sheet(image.frames); + pack_into_sheet(image.frames); } else { var allframes = []; for (var a in anim) allframes = allframes.concat(anim[a].frames); for (var frame of allframes) frame.texture = tex; -// pack_into_sheet(allframes); + pack_into_sheet(allframes); image = anim; } @@ -503,10 +513,6 @@ Range is given by a semantic versioning number, prefixed with nothing, a ~, or a prosperon.iconified = function (icon) {}; prosperon.focus = function (focus) {}; -prosperon.resize = function (dimensions) { - window.size.x = dimensions.x; - window.size.y = dimensions.y; -}; prosperon.suspended = function (sus) {}; prosperon.mouseenter = function () {}; prosperon.mouseleave = function () {}; @@ -515,24 +521,11 @@ prosperon.touchrelease = function (touches) {}; prosperon.touchmove = function (touches) {}; prosperon.clipboardpaste = function (str) {}; -globalThis.window = {}; -window.size = [640, 480]; -console.log(`window size set to ${window.size.slice()}`) -window.mode = "keep"; -window.toggle_fullscreen = function () { - window.fullscreen = !window.fullscreen; -}; - -window.doc = {}; -window.doc.dimensions = "Window width and height packaged in an array [width,height]"; -window.doc.title = "Name in the title bar of the window."; -window.doc.boundingbox = "Boundingbox of the window, with top and right being its height and width."; global.mixin("input"); global.mixin("std"); global.mixin("diff"); global.mixin("color"); global.mixin("tween"); -global.mixin("ai"); global.mixin("particle"); //global.mixin("physics"); global.mixin("geometry"); diff --git a/scripts/render.js b/scripts/render.js index 45cae2dc..c6e9ec7c 100644 --- a/scripts/render.js +++ b/scripts/render.js @@ -893,10 +893,6 @@ render.rectangle = function render_rectangle(rect, color = Color.white, shader = check_flush(flush_poly); }; -render.window = function render_window(pos, wh, color) { - render.box(pos.add(wh.scale(0.5)), wh, color); -}; - render.text = function (str, rect, font = cur_font, size = 0, color = Color.white, wrap = -1, ) { if (typeof font === 'string') font = render.get_font(font); @@ -1158,15 +1154,15 @@ render.draw = function render_draw(mesh, ssbo, inst = 1, e_start = 0) { // Camera viewport is a rectangle with the bottom left corner defined as x,y. Units are pixels on the window. function camviewport() { - var aspect = (((this.viewport[2] - this.viewport[0]) / (this.viewport[3] - this.viewport[1])) * window.size.x) / window.size.y; + var aspect = (((this.viewport[2] - this.viewport[0]) / (this.viewport[3] - this.viewport[1])) * prosperon.size.x) / prosperon.size.y; var raspect = this.size.x / this.size.y; - var left = this.viewport[0] * window.size.x; - var bottom = this.viewport[1] * window.size.y; + var left = this.viewport[0] * prosperon.size.x; + var bottom = this.viewport[1] * prosperon.size.y; var usemode = this.mode; - if (this.break && this.size.x > window.size.x && this.size.y > window.size.y) usemode = this.break; + if (this.break && this.size.x > prosperon.size.x && this.size.y > prosperon.size.y) usemode = this.break; if (usemode === "fit") if (raspect < aspect) usemode = "height"; @@ -1178,8 +1174,8 @@ function camviewport() { return { x: 0, y: 0, - width: window.size.x, - height: window.size.y + width: prosperon.size.x, + height: prosperon.size.y }; case "keep": return { @@ -1192,27 +1188,27 @@ function camviewport() { var ret = { x:left, y:0, - width:this.size.x*(window.size.y/this.size.y), - height:window.size.y + width:this.size.x*(prosperon.size.y/this.size.y), + height:prosperon.size.y }; - ret.x = (window.size.x - (ret.width-ret.x))/2; + ret.x = (prosperon.size.x - (ret.width-ret.x))/2; return ret; case "width": var ret = { x:0, y:bottom, - width:window.size.x, - height:this.size.y*(window.size.x/this.size.x) + width:prosperon.size.x, + height:this.size.y*(prosperon.size.x/this.size.x) }; - ret.y = (window.size.y - (ret.height-ret.y))/2; + ret.y = (prosperon.size.y - (ret.height-ret.y))/2; return ret; } return { x:0, y:0, - width:window.size.x, - height:window.size.y + width:prosperon.size.x, + height:prosperon.size.y }; } @@ -1236,7 +1232,7 @@ camscreen2world.doc = "Convert a view position for a camera to world."; // return camera coordinates given a screen position function screen2cam(pos) { - var winsize = window.size.slice(); + var winsize = prosperon.size.slice(); var viewport = this.view(); var viewpos = pos.sub([viewport.x,viewport.y]); viewpos = viewpos.div([viewport.width,viewport.height]); @@ -1309,7 +1305,7 @@ var imdebug = function () { }; var imgui_fn = function () { - imgui.newframe(window.size.x, window.size.y, 0.01); + imgui.newframe(prosperon.size.x, prosperon.size.y, 0.01); if (debug.console) debug.console = imgui.window("console", _ => { imgui.text(console.transcript); @@ -1323,10 +1319,10 @@ var imgui_fn = function () { imgui.mainmenubar(_ => { imgui.menu("File", _ => { imgui.menu("Game settings", _ => { - window.title = imgui.textinput("Title", window.title); - window.icon = imgui.textinput("Icon", window.icon); + prosperon.title = imgui.textinput("Title", prosperon.title); + prosperon.icon = imgui.textinput("Icon", prosperon.icon); imgui.button("Refresh window", _ => { - window.set_icon(game.texture(window.icon)); + prosperon.set_icon(game.texture(prosperon.icon)); }); }); imgui.button("quit", os.exit); @@ -1350,13 +1346,10 @@ var imgui_fn = function () { imtoggle("Draw gizmos", render, "draw_gizmos"); imgui.menu("Window", _ => { - window.fullscreen = imgui.checkbox("fullscreen", window.fullscreen); - // window.vsync = imgui.checkbox("vsync", window.vsync); + prosperon.fullscreen = imgui.checkbox("fullscreen", prosperon.fullscreen); + // prosperon.vsync = imgui.checkbox("vsync", prosperon.vsync); imgui.menu("MSAA", _ => { - for (var msaa of gamestate.msaa) imgui.button(msaa + "x", _ => (window.sample_count = msaa)); - }); - imgui.menu("Resolution", _ => { - for (var res of gamestate.resolutions) imgui.button(res, _ => (window.resolution = res)); + for (var msaa of gamestate.msaa) imgui.button(msaa + "x", _ => (prosperon.sample_count = msaa)); }); }); }); @@ -1415,14 +1408,14 @@ try{ render.set_projection_ortho({ l:0, - r:window.size.x, - b:-window.size.y, + r:prosperon.size.x, + b:-prosperon.size.y, t:0 },-1,1); render.viewport({ t:0, - height:window.size.y, - width:window.size.x, + height:prosperon.size.y, + width:prosperon.size.x, l:0 }, false); prosperon.app(); diff --git a/scripts/repl.js b/scripts/repl.js deleted file mode 100644 index ba8a53ed..00000000 --- a/scripts/repl.js +++ /dev/null @@ -1,15 +0,0 @@ -var repl = {}; - -var file = "repl.jj"; -var last = 0; - -repl.hotreload = function () { - if (io.mod(file) > last) { - say("REPL:::"); - last = io.mod(file); - var script = io.slurp(file); - eval(script); - } -}; - -return { repl: repl }; diff --git a/scripts/std.js b/scripts/std.js index ea590d01..123cb70e 100644 --- a/scripts/std.js +++ b/scripts/std.js @@ -32,7 +32,15 @@ appy.inputs.f9 = function () { appy.inputs.f10 = function () { debug.show = !debug.show; }; -appy.inputs.f11 = window.toggle_fullscreen; + +prosperon.toggle_fullscreen = function() +{ + prosperon.fullscreen = !prosperon.fullscreen; + game.fullscreen(); + console.log(`fullscreen is now ${prosperon.fullscreen}`) +} + +appy.inputs.f11 = prosperon.toggle_fullscreen; appy.inputs.f11.doc = "Toggle window fullscreen."; appy.inputs.f11.title = "Toggle Fullscreen"; appy.inputs["M-f4"] = os.exit; @@ -216,12 +224,11 @@ Cmdline.register_order( var project = json.decode(io.slurp(projectfile)); game.title = project.title; game.size = [1280, 720]; - if (io.exists("config.js")) global.mixin("config.js"); - else console.warn("No config.js file found. Starting with default parameters."); prosperon.title = project.title; prosperon.width = 1280; prosperon.height = 720; + prosperon.size = [1280,720]; prosperon.cleanup = function(){} prosperon.event = function(e){ switch(e.type) { @@ -247,7 +254,8 @@ Cmdline.register_order( prosperon.textinput(e.char_code); break; case "resized": - prosperon.resize([e.window_width, e.window_height]); + prosperon.size = e.window_size; + prosperon.resize?.(e.window_size); break; case "iconified": prosperon.iconified(false); @@ -273,18 +281,27 @@ Cmdline.register_order( case "mouse_leave": prosperon.mouseleave(); break; + case "files_dropped": + console.log(json.encode(e)); + break; + } } - } + prosperon.frame = prosperon.process; prosperon.icon = os.make_texture(io.slurpbytes('moon.gif')); prosperon.high_dpi = 0; prosperon.alpha = 1; prosperon.fullscreen = 0; + prosperon.sample_count = 1; prosperon.enable_clipboard = true; prosperon.enable_dragndrop=true; prosperon.max_dropped_files=1; prosperon.swap_interval = 1; + prosperon.title = "Prosperon"; + if (io.exists("config.js")) global.mixin("config.js"); + else console.warn("No config.js file found. Starting with default parameters."); + game.engine_start(prosperon); }, "Play the game present in this folder.", diff --git a/source/jsffi.c b/source/jsffi.c index 1bcdf62e..f9356ea3 100644 --- a/source/jsffi.c +++ b/source/jsffi.c @@ -116,11 +116,9 @@ JSValue sapp_event2js(JSContext *js, sapp_event *e) JS_SetPropertyStr(js, event, "frame_count", JS_NewInt64(js, e->frame_count)); JS_SetPropertyStr(js, event, "type", JS_NewString(js, sapp_str[e->type])); JS_SetPropertyStr(js,event,"mouse", vec22js(js, (HMM_Vec2){e->mouse_x,e->mouse_y})); - JS_SetPropertyStr(js,event,"mouse_d", vec22js(js, (HMM_Vec2){e->mouse_dx,e->mouse_dy})); - JS_SetPropertyStr(js, event, "window_width", JS_NewFloat64(js, e->window_width)); - JS_SetPropertyStr(js, event, "window_height", JS_NewFloat64(js, e->window_height)); - JS_SetPropertyStr(js, event, "framebuffer_height", JS_NewFloat64(js, e->framebuffer_height)); - JS_SetPropertyStr(js, event, "framebuffer_height", JS_NewFloat64(js, e->framebuffer_height)); + JS_SetPropertyStr(js,event,"mouse_d", vec22js(js, (HMM_Vec2){e->mouse_dx,e->mouse_dy})); + JS_SetPropertyStr(js,event, "window_size", vec22js(js, (HMM_Vec2){e->window_width,e->window_height})); + JS_SetPropertyStr(js,event, "framebuffer", vec22js(js,(HMM_Vec2){e->framebuffer_width,e->framebuffer_height})); switch(e->type) { case SAPP_EVENTTYPE_MOUSE_SCROLL: @@ -1540,23 +1538,28 @@ JSC_CCALL(game_engine_start, start_desc.alpha = js2number(js, JS_GetPropertyStr(js, argv[0], "alpha")); start_desc.fullscreen = js2number(js, JS_GetPropertyStr(js, argv[0], "fullscreen")); start_desc.swap_interval = js2number(js, JS_GetPropertyStr(js, argv[0], "swap_interval")); - start_desc.window_title = "PROSPERON WINDOW";//js2number(js, JS_GetPropertyStr(js, argv[0], "width")); - start_desc.enable_clipboard = js2number(js, JS_GetPropertyStr(js, argv[0], "enable_clipboard")); - start_desc.enable_dragndrop = js2number(js, JS_GetPropertyStr(js, argv[0], "enable_dragndrop")); + start_desc.window_title = JS_ToCString(js, JS_GetPropertyStr(js, argv[0], "title")); + start_desc.enable_clipboard = JS_ToBool(js, JS_GetPropertyStr(js, argv[0], "enable_clipboard")); + start_desc.enable_dragndrop = JS_ToBool(js, JS_GetPropertyStr(js, argv[0], "enable_dragndrop")); start_desc.max_dropped_files = js2number(js, JS_GetPropertyStr(js, argv[0], "max_dropped_files")); + start_desc.max_dropped_file_path_length = 2048; + start_desc.max_dropped_files = 4; start_desc.logger.func = slog_func; texture *tex = js2texture(js, JS_GetPropertyStr(js, argv[0], "icon")); if (tex) start_desc.icon = texture2icon(tex); sapp_run(&start_desc); ) +JSC_CCALL(game_fullscreen, sapp_toggle_fullscreen()); + static const JSCFunctionListEntry js_game_funcs[] = { MIST_FUNC_DEF(game, engine_start, 1), + MIST_FUNC_DEF(game, fullscreen, 0), }; JSC_CCALL(input_show_keyboard, sapp_show_keyboard(JS_ToBool(js,argv[0]))) JSValue js_input_keyboard_shown(JSContext *js, JSValue self) { return JS_NewBool(js,sapp_keyboard_shown()); } -JSC_CCALL(input_mouse_lock, sapp_lock_mouse(js2number(js,argv[0]))) +JSC_CCALL(input_mouse_lock, sapp_lock_mouse(JS_ToBool(js,argv[0]))) JSC_CCALL(input_mouse_cursor, sapp_set_mouse_cursor(js2number(js,argv[0]))) JSC_CCALL(input_mouse_show, sapp_show_mouse(JS_ToBool(js,argv[0]))) @@ -2276,7 +2279,6 @@ JSC_CCALL(os_make_texture, if (!tex) return JS_ThrowReferenceError(js, "unable to make texture from the given array buffer"); ret = texture2js(js,tex); - JS_SetPropertyStr(js, ret, "path", JS_DupValue(js,argv[0])); ) JSC_CCALL(os_make_gif, @@ -2294,7 +2296,6 @@ JSC_CCALL(os_make_gif, JSValue gif = JS_NewObject(js); JSValue delay_arr = JS_NewArray(js); JSValue jstex = texture2js(js,tex); - JS_SetPropertyStr(js, jstex, "path", JS_DupValue(js, argv[0])); float yslice = 1.0/frames; for (int i = 0; i < frames; i++) { diff --git a/source/timer.c b/source/timer.c index 5416d269..32798234 100644 --- a/source/timer.c +++ b/source/timer.c @@ -29,10 +29,10 @@ void timer_free(JSRuntime *rt, timer *t) void timer_update(JSContext *js, double dt) { for (int i = 0; i < arrlen(timers); i++) { - if (timers[i]->remain <= 0) continue; + if (timers[i]->remain <= -10000) continue; timers[i]->remain -= dt; if (timers[i]->remain <= 0) { - timers[i]->remain = 0; + timers[i]->remain = -10000; JSValue fn = JS_DupValue(js, timers[i]->fn); script_call_sym(timers[i]->fn, 0, NULL); JS_FreeValue(js, fn);