civ/boot.js

116 lines
3.2 KiB
JavaScript
Raw Normal View History

2026-05-14 20:15:23 +09:00
function requestRender() {
renderDirty = true;
}
function drawFrame(forceStats = false) {
render();
updateStats(forceStats);
renderTooltip();
renderDirty = false;
lastRenderAt = performance.now();
}
function scheduleLoop() {
if (loopScheduled) return;
loopScheduled = true;
const delay = document.hidden
? LoopConfig.hiddenPollMs
: running ? 0 : LoopConfig.idlePollMs;
if (delay > 0) {
loopTimer = setTimeout(() => {
loopTimer = null;
requestAnimationFrame(loop);
}, delay);
} else {
requestAnimationFrame(loop);
}
}
function loop(now = performance.now()) {
loopScheduled = false;
if (running && !document.hidden && now - lastSimTickAt >= LoopConfig.simTickMs) {
lastSimTickAt = now;
const steps = Number(els.speed.value);
const stepBudget = LoopConfig.stepBudgetMs * Math.max(1, steps);
const loopStart = performance.now();
let completedSteps = 0;
for (let i = 0; i < steps; i++) {
sim.step();
completedSteps++;
if (completedSteps > 0 && performance.now() - loopStart > stepBudget) break;
}
if (completedSteps > 0) requestRender();
}
const shouldRender = !document.hidden &&
(renderDirty || (running && now - lastRenderAt >= LoopConfig.renderMs));
if (shouldRender) drawFrame();
scheduleLoop();
}
function reset() {
const size = Number(els.worldSize.value);
2026-09-16 13:51:43 +09:00
const initialPopulation = Number(els.agentCount.value);
2026-05-14 20:15:23 +09:00
els.canvas.width = size;
els.canvas.height = size;
renderImage = null;
renderImageSize = 0;
2026-09-16 13:51:43 +09:00
sim = new Simulation(size, initialPopulation);
2026-05-14 20:15:23 +09:00
setLegend();
drawFrame(true);
renderStateGraph();
}
function refreshStateGraph() {
renderStateGraph();
clearStateGraphInfo();
}
els.toggleRun.addEventListener("click", () => {
running = !running;
els.toggleRun.textContent = running ? "Pause" : "Run";
lastSimTickAt = 0;
if (running && loopTimer) {
clearTimeout(loopTimer);
loopTimer = null;
loopScheduled = false;
scheduleLoop();
} else if (running) {
scheduleLoop();
}
});
els.stepOnce.addEventListener("click", () => {
sim.step();
requestRender();
drawFrame(true);
renderStateGraph();
});
els.resetWorld.addEventListener("click", reset);
for (const control of [els.worldSize, els.agentCount]) control.addEventListener("change", reset);
els.viewMode.addEventListener("change", () => {
setLegend();
requestRender();
drawFrame();
});
els.canvas.addEventListener("mousemove", showTooltip);
els.canvas.addEventListener("mouseleave", hideTooltip);
for (const control of [els.historyFilter, els.historySort, els.historyMetric]) {
control?.addEventListener("change", refreshStateGraph);
}
els.toggleActiveStates?.addEventListener("click", () => {
graphState.showActive = !graphState.showActive;
els.toggleActiveStates.setAttribute("aria-pressed", String(graphState.showActive));
refreshStateGraph();
});
els.togglePastStates?.addEventListener("click", () => {
graphState.showPast = !graphState.showPast;
els.togglePastStates.setAttribute("aria-pressed", String(graphState.showPast));
refreshStateGraph();
});
els.stateGraph?.addEventListener("mousemove", updateStateGraphInfo);
els.stateGraph?.addEventListener("mouseleave", clearStateGraphInfo);
document.addEventListener("visibilitychange", () => {
lastSimTickAt = 0;
requestRender();
if (!document.hidden && loopTimer) {
clearTimeout(loopTimer);
loopTimer = null;
loopScheduled = false;
scheduleLoop();
}
});
reset();
loop();