Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 2939x 2939x 2939x 2939x 2939x 73x 73x 73x 73x 73x 73x 73x 2939x 2939x 2939x 2939x 2939x 2939x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73642x 73642x 73x 73x 73x 73x 73x 20729x 20729x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 72x 72x 72x 72x 72x 72x 72x | import { game } from './gstate.js';
// modal_guard.js — Single-threaded modal ownership assertions.
//
// C NetHack is single-threaded. When more() calls wgetch(), the CPU blocks
// and nothing else executes until the key is pressed. In JS, `await nhgetch()`
// yields the event loop, allowing any pending microtask to run.
//
// This module enforces the C contract in JS: when a modal operation (more,
// yn_function, getlin, direction prompt) is waiting for input, no other
// game code should be executing. Any violation means the JS execution order
// diverges from C's.
//
// Usage:
// import { enterModal, exitModal, assertNotInModal, getModalOwner } from './modal_guard.js';
//
// // In more(), yn_function(), getlin():
// enterModal('more');
// try {
// const key = await nhgetch(); // only allowed operation
// } finally {
// exitModal('more');
// }
//
// // In any async game function that shouldn't run during a modal wait:
// assertNotInModal('moveloop_core'); // throws if a modal is active
//
// The guard is lightweight (one string comparison) and can be enabled/disabled
// via the WEBHACK_MODAL_GUARD env flag for production vs debugging.
const _env = typeof process !== 'undefined' ? process.env : {};
const ENABLED = _env.WEBHACK_MODAL_GUARD !== '0'; // enabled by default
// string identifying the active modal ('more', 'yn', 'getlin', etc.)
// stack for nested modals (shouldn't happen but defensive)
// call stack at the point enterModal was called
// set by allmain.js to () => game.context?.mon_moving
/**
* Register a getter that checks if monster turns are in progress.
* Violations during monster turns are suppressed (microtask ordering
* artifacts, not real reentrancy).
*/
export function setMonMovingGetter(fn) { game.monMovingGetter = fn; }
/**
* Enter a modal wait state. Only nhgetch calls from this owner are allowed
* until exitModal is called.
*
* @param {string} owner — identifier for the modal operation ('more', 'yn', 'getlin', 'direction')
*/
export function enterModal(owner) {
if (!ENABLED) return;
if (game.modalOwner) {
_reportViolation(
`enterModal('${owner}') while modal '${game.modalOwner}' already owns input`,
game.modalEntryStack
);
}
game.modalOwner = owner;
game.modalEntryStack = new Error(`enterModal('${owner}')`).stack || null;
}
/**
* Exit a modal wait state. Must match the most recent enterModal.
*
* @param {string} owner — must match the current modal owner
*/
export function exitModal(owner) {
if (!ENABLED) return;
if (game.modalOwner !== owner) {
_reportViolation(`exitModal('${owner}') but current owner is '${game.modalOwner}'`);
}
const prev = game.modalStack.pop();
game.modalOwner = prev?.owner || null;
game.modalEntryStack = prev?.stack || null;
}
/**
* Assert that no modal wait is active. Call this at the start of any
* game code that should not execute during a modal pause.
*
* In C, this code would never run during more()/yn_function()/getlin()
* because the process is blocked in wgetch(). In JS, if this fires,
* it means some code is taking advantage of the await-yield to run
* when it shouldn't.
*
* @param {string} caller — identifier for the code asserting (for diagnostics)
*/
export function assertNotInModal(caller) {
if (!ENABLED || !game.modalOwner) return;
_reportViolation(
`Game code '${caller}' ran while modal '${game.modalOwner}' owns input. `
+ `In C this would be impossible (wgetch blocks). `
+ `This indicates an async ordering bug.`,
game.modalEntryStack
);
}
/**
* Get the current modal owner (for diagnostics). Returns null if no modal active.
*/
export function getModalOwner() {
return game.modalOwner;
}
/**
* Get count of violations detected (for test assertions).
*/
export function getViolationCount() {
return game.violationCount;
}
/**
* Reset state (for tests).
*/
export function resetModalGuard() {
game.modalOwner = null;
game.modalStack = [];
game.violationCount = 0;
}
/**
* Set a custom violation handler (for tests). Called with (message, stack).
* Set to null to use default (console.error + throw).
*/
export function setViolationHandler(handler) {
game.violationHandler = handler;
}
function _reportViolation(message, modalEntryStack) {
game.violationCount++;
const violationStack = new Error().stack || '';
if (game.violationHandler) {
game.violationHandler(message, violationStack, modalEntryStack);
return;
}
console.error(`[MODAL VIOLATION] ${message}`);
console.error(`Violating code entered at:\n${violationStack}`);
if (modalEntryStack) {
console.error(`Modal is waiting at (a caller in this chain is likely missing 'await'):\n${modalEntryStack}`);
}
// In development, throw to make violations loud and unfixable.
// In production (browser), log but don't crash.
if (typeof process !== 'undefined') {
// During monster turns, microtask ordering between nhgetch
// resolution and more() exitModal creates transient violations
// that are harmless — the more() will complete on the next
// microtask. In C, wgetch blocks so this can't happen.
if (game.monMovingGetter && game.monMovingGetter()) return;
throw new Error(`[MODAL VIOLATION] ${message}`);
}
}
export function init_modal_guard_globals() {
game.modalOwner = null;
game.modalStack = [];
game.modalEntryStack = null;
game.violationCount = 0;
game.violationHandler = null;
game.monMovingGetter = null;
}
|