// DungeonCraft browser plugin for miniquad: storage, file download/upload, save-on-leave, audio output. (function () { "use strict"; var pending = null; // value handed over by dc_storage_get var file = null; // bytes of a file chosen via the file picker var cache = {}; // key -> string, persisted to localStorage when the page is hidden/closed function bytes(ptr, len) { return new Uint8Array(wasm_memory.buffer, ptr, len); } function str(ptr, len) { return new TextDecoder().decode(bytes(ptr, len)); } function flushCache() { for (var k in cache) { try { window.localStorage.setItem(k, cache[k]); } catch (e) { } } } window.addEventListener("pagehide", flushCache); document.addEventListener("visibilitychange", function () { if (document.visibilityState === "hidden") flushCache(); }); // hidden for loading savegames var input = document.createElement("input"); input.type = "file"; input.accept = ".dcsave,.json,application/json"; input.style.display = "none"; input.addEventListener("change", function () { var f = input.files && input.files[0]; if (!f) return; f.arrayBuffer().then(function (buf) { file = new Uint8Array(buf); }); input.value = ""; }); document.addEventListener("DOMContentLoaded", function () { document.body.appendChild(input); }); function register(importObject) { var env = importObject.env; env.dc_storage_get = function (kp, kl) { var v = null; try { v = window.localStorage.getItem(str(kp, kl)); } catch (e) { } if (v === null) { pending = null; return -1; } pending = new TextEncoder().encode(v); return pending.length; }; env.dc_storage_take = function (ptr) { if (pending === null) return; bytes(ptr, pending.length).set(pending); pending = null; }; env.dc_storage_set = function (kp, kl, vp, vl) { try { window.localStorage.setItem(str(kp, kl), str(vp, vl)); } catch (e) { } }; env.dc_cache_set = function (kp, kl, vp, vl) { cache[str(kp, kl)] = str(vp, vl); }; env.dc_download = function (np, nl, dp, dl) { var blob = new Blob([bytes(dp, dl).slice()], { type: "application/json" }); var a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = str(np, nl); document.body.appendChild(a); a.click(); setTimeout(function () { URL.revokeObjectURL(a.href); a.remove(); }, 1000); }; env.dc_open_file = function () { file = null; input.click(); }; env.dc_file_poll = function () { return file === null ? -1 : file.length; }; env.dc_file_take = function (ptr) { if (file === null) return; bytes(ptr, file.length).set(file); file = null; }; } // ---- audio: the Rust mixer renders on demand (dc_audio_render), started by the first user input var actx = null; var stats = { frames: 0, peak: 0 }; // diagnostics, e.g. for automated tests: state of the audio output window.dc_audio_stats = function () { return { state: actx ? actx.state : "not started", frames: stats.frames, peak: stats.peak }; }; function startAudio() { if (typeof wasm_exports === "undefined" || !wasm_exports.dc_audio_render) return; if (actx) { if (actx.state !== "running") actx.resume(); return; } var AC = window.AudioContext || window.webkitAudioContext; if (!AC) return; actx = new AC(); wasm_exports.dc_audio_init(actx.sampleRate); var node = actx.createScriptProcessor(2048, 0, 2); node.onaudioprocess = function (e) { var n = e.outputBuffer.length; var l = e.outputBuffer.getChannelData(0); var r = e.outputBuffer.getChannelData(1); var ptr = wasm_exports.dc_audio_render(n); if (!ptr) { l.fill(0); r.fill(0); return; } var buf = new Float32Array(wasm_memory.buffer, ptr, n * 2); var peak = 0; for (var i = 0; i < n; i++) { l[i] = buf[2 * i]; r[i] = buf[2 * i + 1]; var a = Math.abs(l[i]); if (a > peak) peak = a; } stats.frames += n; stats.peak = Math.max(stats.peak * 0.9, peak); }; node.connect(actx.destination); } ["mousedown", "keydown", "touchstart"].forEach(function (ev) { window.addEventListener(ev, startAudio, true); }); miniquad_add_plugin({ register_plugin: register, version: 1, name: "dungeoncraft" }); })();