mirror of
https://github.com/Shu-Ying/noname-shuying.git
synced 2026-10-03 02:20:01 +08:00
84 lines
3.6 KiB
JavaScript
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;
|
|
}
|