Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This C++ library provides base classes for creating 2D virtual worlds.

*A larger demo world (`--world meadow`): sheep graze and wander, wolves stalk the nearest sheep, flowers stay put. The grass under each location is the demo's own state, shown as the floor colour.*

The frontend reads an `Environment` and draws it with the [Strata](https://github.com/Preponderous-Software/strata) engine (v0.6.0). It never changes the world: CI runs each demo with and without drawing and compares the results. A consumer writes one callback that turns an `Entity` into a shape, colour and size; everything else (floor, camera, tweening, picking a location with the mouse, a HUD) is provided. It is opt-in and built separately with CMake, so the library above still needs nothing but a C++ compiler. See [docs/STRATA.md](docs/STRATA.md).
The frontend reads an `Environment` and draws it with the [Strata](https://github.com/Preponderous-Software/strata) engine (v0.7.0). It never changes the world: CI runs each demo with and without drawing and compares the results. A consumer writes one callback that turns an `Entity` into a shape, colour and size; everything else (floor, camera, tweening, picking a location with the mouse, a HUD) is provided. It is opt-in and built separately with CMake, so the library above still needs nothing but a C++ compiler. See [docs/STRATA.md](docs/STRATA.md).

```
cmake -S strata -B build-strata -DCMAKE_BUILD_TYPE=Release
Expand Down
2 changes: 1 addition & 1 deletion docs/STRATA.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Nothing in the library depends on it. `make`, `run_tests.sh` and the top-level

CMake 3.24 or newer (Strata's minimum), a C++17 compiler, and the SDL3 build
dependencies Strata needs on Linux (X11 development headers, EGL). Strata is
fetched at the commit in `strata/strata.ref`, which is the `v0.6.0` release.
fetched at the commit in `strata/strata.ref`, which is the `v0.7.0` release.

```
cmake -S strata -B build-strata -DCMAKE_BUILD_TYPE=Release
Expand Down
2 changes: 1 addition & 1 deletion strata/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# cmake --build build-strata -j4
# ctest --test-dir build-strata --output-on-failure
#
# Strata is fetched at the commit in strata.ref (the v0.6.0 release), or taken
# Strata is fetched at the commit in strata.ref (the v0.7.0 release), or taken
# from a local checkout with -DFETCHCONTENT_SOURCE_DIR_STRATA=<path>.
# See docs/STRATA.md.
#
Expand Down
2 changes: 1 addition & 1 deletion strata/strata.ref
Original file line number Diff line number Diff line change
@@ -1 +1 @@
c2d7ee4c1102726c654c90d8361383d9ba3590b3
20827c3d67e71c1f942c29e7cf8adfa4801188a8
2 changes: 1 addition & 1 deletion strata/web/strata-kit/PIN
Original file line number Diff line number Diff line change
@@ -1 +1 @@
901e6c97bc70c86c0e7dfbf857bb87ec405e0164
20827c3d67e71c1f942c29e7cf8adfa4801188a8
2 changes: 1 addition & 1 deletion strata/web/strata-kit/SHA256SUMS
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
7da2a43f932f63e5ae86273a7675b55180dbde4d61dc6e0c5fcd048026879946 strata-loader.js
fa03e8ed92759359359f29f40668c947e63b0c9f3eabafcce2f911110637f5c3 strata-loader.js
8e3fc647b4775773eabc147e564ffbddd0653b252eff96c5432213ee362798df LICENSE
82 changes: 69 additions & 13 deletions strata/web/strata-kit/strata-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,21 @@
// ... create the scene with StrataKit.create({module: mod, ...}), then:
StrataLoader.done();

A host whose own loader already knows how far it has got (a .NET or
Unity-style boot that fetches its own files) shows the same overlay and
drives its bar itself:

if (StrataLoader.show({title: "the game", poster: "/poster.jpg"})) {
StrataLoader.progress(0.4, "Loading assemblies"); // 0..1, label optional
// ... boot; then:
StrataLoader.done();
}

show() takes load()'s display options (title, poster, trailer, fallback,
mount) and returns false, with the fallback showing, when WebGL 2 is
missing. progress(fraction, label) clamps the fraction to 0..1 and never
moves the bar back; the label (or the percentage) replaces the size line.

What it shows, in an overlay it adds to the page (class sk-loader):

- the poster (if any) as a blurred backdrop, and the title;
Expand Down Expand Up @@ -73,7 +88,7 @@
@media (prefers-reduced-motion: reduce){.sk-loader,.sk-loader .sk-fill{transition:none}
.sk-loader .sk-bar.sk-indeterminate .sk-fill{animation:none;width:100%;opacity:.5}}`;

let root = null, opts = {}, state = "idle";
let root = null, opts = {}, state = "idle", shown = 0;

function el(tag, cls, text) {
const e = document.createElement(tag);
Expand Down Expand Up @@ -115,20 +130,57 @@
return root;
}

function progress(got, total) {
// Sets the bar to `fraction` (0..1), or to indeterminate when it is null,
// and the line under it to `text`. Both progress paths draw through here.
function setBar(fraction, text) {
if (!root) return;
const bar = root.querySelector(".sk-bar"), fill = root.querySelector(".sk-fill");
if (!bar) return;
if (total) {
const pct = Math.min(100, Math.round(got / total * 100));
if (fraction != null) {
const pct = Math.min(100, Math.round(fraction * 100));
bar.classList.remove("sk-indeterminate");
fill.style.width = pct + "%";
bar.setAttribute("aria-valuenow", String(pct));
} else {
bar.classList.add("sk-indeterminate");
bar.removeAttribute("aria-valuenow");
}
root.querySelector(".sk-size").textContent = sizeText(got, total);
root.querySelector(".sk-size").textContent = text;
}

// load()'s byte progress: `got` of `total` bytes (total 0 when unknown).
function byteProgress(got, total) {
setBar(total ? got / total : null, sizeText(got, total));
}

// A host's own progress: `fraction` of the way (clamped to 0..1, and never
// lower than the last call's), with an optional label for the line under
// the bar (else the percentage). Mounts the overlay if show() or load() has
// not; does nothing once the shell is done or showing the fallback.
function progress(fraction, label) {
if (state === "done" || state === "fallback") return;
const f = Number(fraction);
if (Number.isNaN(f)) return;
shown = Math.max(shown, Math.min(1, Math.max(0, f)));
if (state === "idle") state = "loading";
mount();
setBar(shown, label != null && label !== "" ? String(label) : `${Math.round(shown * 100)}%`);
const bar = root.querySelector(".sk-bar");
if (bar) {
if (label) bar.setAttribute("aria-valuetext", String(label));
else bar.removeAttribute("aria-valuetext");
}
}

// The overlay without load()'s fetch, for a host that boots its own way.
// Returns false (the fallback showing) when WebGL 2 is missing.
function show(o) {
opts = Object.assign({}, o);
state = "loading";
shown = 0;
mount();
if (!hasWebGL2()) { fallback("no-webgl2"); return false; }
return true;
}

function hasWebGL2() {
Expand Down Expand Up @@ -219,25 +271,25 @@
let total = (!enc || enc === "identity") && header > 0 ? header : (statedBytes || 0);
if (!res.body || !res.body.getReader) {
const buf = await res.arrayBuffer();
progress(buf.byteLength, buf.byteLength);
byteProgress(buf.byteLength, buf.byteLength);
return buf;
}
const reader = res.body.getReader();
const chunks = [];
let got = 0;
progress(0, total);
byteProgress(0, total);
for (;;) {
const {done, value} = await reader.read();
if (done) break;
chunks.push(value);
got += value.byteLength;
if (total && got > total) total = 0; // the stated size was wrong: say bytes only
progress(got, total);
byteProgress(got, total);
}
const out = new Uint8Array(got);
let o = 0;
for (const c of chunks) { out.set(c, o); o += c.byteLength; }
progress(got, got);
byteProgress(got, got);
return out.buffer;
}

Expand All @@ -247,8 +299,9 @@
const keep = [...box.childNodes];
box.textContent = "";
const msg = reason === "save-data" ? "Data saver is on."
: reason === "low-memory" ? "This device has little memory." : "Motion is reduced on this device.";
box.appendChild(el("p", "sk-msg", `${msg} The live view downloads ${opts.bytes ? mb(opts.bytes) + " MB" : "its engine"} and animates.`));
: reason === "low-memory" ? "This device has little memory." : "This device asks for reduced motion.";
const size = opts.bytes ? `a ${mb(opts.bytes)} MB download` : "a download";
box.appendChild(el("p", "sk-msg", `${msg} The live view is ${size} and is animated.`));
const go = el("button", "", "Start the live view");
go.type = "button";
go.addEventListener("click", () => { box.textContent = ""; keep.forEach((n) => box.appendChild(n)); resolve(); });
Expand All @@ -267,6 +320,7 @@
async function load(o) {
opts = Object.assign({}, o);
state = "loading";
shown = 0;
mount();
if (!hasWebGL2()) {
fallback("no-webgl2");
Expand Down Expand Up @@ -311,9 +365,11 @@
}

// The kit could not go any lower and frames are still slow: offer the clip
// (or the fallback link) without taking the live view away.
// (or the fallback link) without taking the live view away. Once: a second
// call while the bar is up does nothing.
function slow() {
if (state === "fallback" || (!opts.trailer && !(opts.fallback && opts.fallback.href))) return;
if (document.querySelector(".sk-slow")) return;
const bar = el("div", "sk-slow");
bar.setAttribute("role", "status");
bar.style.cssText = "position:fixed;left:50%;transform:translateX(-50%);bottom:max(12px,env(safe-area-inset-bottom));" +
Expand All @@ -337,5 +393,5 @@
document.body.appendChild(bar);
}

window.StrataLoader = {load, done, fallback, slow, hasWebGL2, get state() { return state; }};
window.StrataLoader = {load, show, progress, done, fallback, slow, hasWebGL2, get state() { return state; }};
})();
Loading