add parseq
This commit is contained in:
@@ -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.",
|
||||
};
|
||||
@@ -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 };
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
590
scripts/parseq.js
Normal file
590
scripts/parseq.js
Normal file
@@ -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
|
||||
};
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 };
|
||||
@@ -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.",
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user