Files
noname-shuying/mengsan/runtime/engine-session.js
T
2026-09-28 20:58:29 +08:00

84 lines
3.6 KiB
JavaScript

// Candidate only: attach to fresh, session-owned roots BEFORE scheduling them.
const attached = new WeakSet();
export function observeFreshEvent(session, event) {
if (!event || typeof event.start !== "function" || !event.manager?.eventStack ||
event.finished || event.manager.eventStack.includes(event) ||
event.parent?.childEvents?.includes(event) || attached.has(event)) {
throw new Error("Fresh, unobserved engine event required");
}
const descriptor = Object.getOwnPropertyDescriptor(event, "start");
if (descriptor && !descriptor.configurable) throw new Error("Event start is not configurable");
const original = event.start;
let resolve, reject;
const completion = new Promise((yes, no) => { resolve = yes; reject = no; });
session.ownEvent(event, completion);
let observed = false;
function start(...args) {
let result;
try { result = Reflect.apply(original, this, args); }
catch (error) { reject(error); throw error; }
if (!observed) {
observed = true;
if (!(result instanceof Promise)) {
reject(new TypeError("Native engine start promise required"));
} else {
result.then(() => {
if (event.manager.eventStack.includes(event) || event.manager.tempEvent === event) {
reject(new Error("Event still active after start completion"));
} else resolve();
}, reject);
}
}
return result; // Never assimilate event.then(), never proactively start a queue.
}
Object.defineProperty(event, "start", { configurable: true, writable: true, value: start });
attached.add(event);
session.ownResource({}, () => {
if (event.start === start) {
if (descriptor) Object.defineProperty(event, "start", descriptor);
else delete event.start;
}
attached.delete(event);
});
return completion;
}
// Independent delay entry: releasing it never calls global resume/pause2/over.resolve.
export function acquirePauseLease(session, pauseManager) {
if (!session.active || typeof pauseManager?.setDelay !== "function") throw new Error("Active session and pause manager required");
let unlock;
const gate = new Promise(resolve => { unlock = resolve; });
const settled = pauseManager.setDelay(gate);
let released = false;
const lease = Object.freeze({
release() { if (!released) { released = true; unlock(); } return settled; },
get released() { return released; },
});
session.ownResource(lease, () => lease.release());
return lease;
}
// Call outside tracked event/task bodies. Own pauses must be lifted before drain.
export async function stopAndDrain(session, leases = []) {
session.requestStop();
await Promise.all(leases.map(lease => lease.release()));
await session.drain();
}
// Only remove this exact node from the parent recorded at acquisition.
export function ownNode(session, node) {
const parent = node.parentNode;
if (!parent) throw new Error("Attached node required");
return session.ownResource(node, () => {
if (node.parentNode === parent) parent.removeChild(node);
});
}
export function ownListener(session, target, type, listener, options) {
const guarded = session.guard(listener);
const capture = typeof options === "boolean" ? options : !!options?.capture;
const token = {};
session.ownResource(token, () => target.removeEventListener(type, guarded, capture));
target.addEventListener(type, guarded, options);
return guarded;
}