feat(justfile): added justfile with catalog of builds

This commit is contained in:
2026-09-05 07:46:36 -04:00
parent 6a54f2625e
commit 7282b2e134
8 changed files with 1167 additions and 0 deletions

195
web/demo.js Normal file
View File

@@ -0,0 +1,195 @@
"use strict";
const elements = {
form: document.querySelector("#solve-form"),
cells: document.querySelector("#cells"),
cellsValue: document.querySelector("#cells-value"),
order: document.querySelector("#order"),
button: document.querySelector("#solve-button"),
status: document.querySelector("#runtime-status"),
statusDot: document.querySelector("#status-dot"),
capabilities: document.querySelector("#capability-list"),
canvas: document.querySelector("#mesh-canvas"),
runIndex: document.querySelector("#run-index"),
dofs: document.querySelector("#metric-dofs"),
iterations: document.querySelector("#metric-iterations"),
error: document.querySelector("#metric-error"),
time: document.querySelector("#metric-time"),
log: document.querySelector("#runtime-log"),
footerVersion: document.querySelector("#footer-version"),
};
let mfemModule;
let completedRuns = 0;
let lastCells = Number(elements.cells.value);
function setStatus(message, state = "loading") {
elements.status.textContent = message;
elements.statusDot.dataset.state = state;
}
function appendLog(message) {
const timestamp = new Date().toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
});
const prior = elements.log.textContent.startsWith("Waiting") ? "" : `${elements.log.textContent}\n`;
elements.log.textContent = `${prior}[${timestamp}] ${message}`;
}
function drawMesh(cells, solved = false) {
const canvas = elements.canvas;
const context = canvas.getContext("2d");
const ratio = Math.max(1, window.devicePixelRatio || 1);
const bounds = canvas.getBoundingClientRect();
const width = Math.max(320, Math.round(bounds.width));
const height = Math.max(260, Math.round(bounds.height));
canvas.width = width * ratio;
canvas.height = height * ratio;
context.scale(ratio, ratio);
const gradient = context.createRadialGradient(
width * 0.5,
height * 0.46,
10,
width * 0.5,
height * 0.5,
width * 0.66,
);
gradient.addColorStop(0, solved ? "rgba(125, 249, 197, 0.30)" : "rgba(89, 199, 255, 0.16)");
gradient.addColorStop(0.55, solved ? "rgba(35, 124, 126, 0.20)" : "rgba(27, 72, 104, 0.13)");
gradient.addColorStop(1, "rgba(8, 14, 24, 0)");
context.fillStyle = gradient;
context.fillRect(0, 0, width, height);
const padding = Math.max(30, Math.min(width, height) * 0.11);
const side = Math.min(width - 2 * padding, height - 2 * padding);
const left = (width - side) / 2;
const top = (height - side) / 2;
const step = side / cells;
context.strokeStyle = solved ? "rgba(151, 255, 215, 0.30)" : "rgba(138, 207, 236, 0.22)";
context.lineWidth = 1;
context.beginPath();
for (let index = 0; index <= cells; index += 1) {
const offset = Math.round(index * step) + 0.5;
context.moveTo(left + offset, top);
context.lineTo(left + offset, top + side);
context.moveTo(left, top + offset);
context.lineTo(left + side, top + offset);
}
context.stroke();
context.strokeStyle = solved ? "#7df9c5" : "#59c7ff";
context.lineWidth = 2;
context.strokeRect(left, top, side, side);
}
function renderCapabilities(capabilities) {
const entries = [
["Wasm", capabilities.wasm],
["MFEM", true],
["libCEED", capabilities.ceed],
["zlib", capabilities.zlib],
["MPI", capabilities.mpi],
];
elements.capabilities.replaceChildren(
...entries.map(([name, enabled]) => {
const badge = document.createElement("span");
badge.className = enabled ? "capability enabled" : "capability disabled";
badge.textContent = enabled ? name : `${name} off`;
return badge;
}),
);
elements.footerVersion.textContent = `MFEM ${capabilities.mfemVersion} · WebAssembly`;
}
function formatScientific(value) {
return Number(value).toExponential(3);
}
function formatTime(seconds) {
const milliseconds = Number(seconds) * 1000;
return milliseconds < 1000 ? `${milliseconds.toFixed(1)} ms` : `${Number(seconds).toFixed(2)} s`;
}
async function runSolve() {
const cells = Number(elements.cells.value);
const order = Number(elements.order.value);
elements.button.disabled = true;
elements.button.classList.add("working");
setStatus("Solving finite-element system", "working");
appendLog(`Starting ${cells}×${cells} mesh at polynomial order ${order}.`);
await new Promise((resolve) => window.setTimeout(resolve, 20));
try {
const raw = mfemModule.ccall(
"mfem_demo_solve",
"string",
["number", "number"],
[cells, order],
);
const result = JSON.parse(raw);
if (!result.ok) {
throw new Error(result.error || "The MFEM solve failed.");
}
completedRuns += 1;
lastCells = cells;
elements.runIndex.textContent = `Run ${String(completedRuns).padStart(2, "0")}`;
elements.dofs.textContent = Number(result.trueDofs).toLocaleString();
elements.iterations.textContent = Number(result.iterations).toLocaleString();
elements.error.textContent = formatScientific(result.l2Error);
elements.time.textContent = formatTime(result.elapsedSeconds);
drawMesh(cells, true);
setStatus("Solve complete", "ready");
appendLog(
`Converged in ${result.iterations} iterations with residual ${formatScientific(result.finalNorm)}.`,
);
} catch (error) {
setStatus("Solve failed", "error");
appendLog(error instanceof Error ? error.message : String(error));
} finally {
elements.button.disabled = false;
elements.button.classList.remove("working");
}
}
elements.cells.addEventListener("input", () => {
elements.cellsValue.value = elements.cells.value;
drawMesh(Number(elements.cells.value), false);
});
elements.form.addEventListener("submit", (event) => {
event.preventDefault();
void runSolve();
});
window.addEventListener("resize", () => drawMesh(lastCells, completedRuns > 0));
async function initialize() {
drawMesh(lastCells, false);
try {
if (typeof MFEMDemoModule !== "function") {
throw new Error("The generated MFEM WebAssembly loader was not found.");
}
mfemModule = await MFEMDemoModule({
locateFile: (path) => new URL(path, window.location.href).href,
print: (message) => appendLog(message),
printErr: (message) => appendLog(`Runtime: ${message}`),
});
const capabilities = JSON.parse(
mfemModule.ccall("mfem_demo_capabilities", "string", [], []),
);
renderCapabilities(capabilities);
elements.button.disabled = false;
setStatus("Runtime ready", "ready");
appendLog(`Loaded MFEM ${capabilities.mfemVersion} from WebAssembly.`);
} catch (error) {
setStatus("Runtime failed to load", "error");
appendLog(error instanceof Error ? error.message : String(error));
}
}
void initialize();