"use strict"; function lineageIdleSchedule(fn, timeout = 180) { const schedule = window.requestIdleCallback || ((cb) => setTimeout(cb, 0)); schedule(fn, { timeout }); } function lineageSameFamilyToken(token) { return uiCache.archiveRenderToken === token; } function lineageBuildComponentsIdle(family, token, onProgress, onDone) { family = lineageEdgeFamily(family); const entries = Object.values(family || {}).filter(n => n?.id); const known = new Set(entries.map(n => n.id)); const parentsByChild = new Map(); const childrenByParent = new Map(); const relatedIds = new Set(); const graph = new Map(); const comps = []; let stage = "relations"; let entryIndex = 0; let nodeIndex = 0; let nodes = []; let sortedNodes = []; let dfsIndex = 0; const seen = new Set(); const addParent = (childId, parentId) => lineageAddParentEdge(childId, parentId, known, parentsByChild, childrenByParent); const addGraphEdge = (a, b) => { if (!a || !b || !graph.has(a) || !graph.has(b)) return; graph.get(a).add(b); graph.get(b).add(a); }; const validParentChild = (childId, parentId) => globalThis.TarinaiFamilyGraph.relationAllowsParentChild(family, childId, parentId); const finishEmpty = () => { lineageRelationIndexCache = { family, version: world.familyVersion || 0, index: { parentsByChild, childrenByParent } }; onDone([]); }; const pump = () => { if (!lineageSameFamilyToken(token)) return; const started = performance.now(); while (performance.now() - started < 6) { if (stage === "relations") { if (entryIndex >= entries.length) { lineageRelationIndexCache = { family, version: world.familyVersion || 0, index: { parentsByChild, childrenByParent } }; stage = "related"; entryIndex = 0; onProgress?.("\u95a2\u4fc2\u3092\u6574\u7406\u4e2d", 0, entries.length); continue; } const n = entries[entryIndex++]; for (const parentId of n.parents || []) addParent(n.id, parentId); for (const childId of n.children || []) addParent(childId, n.id); } else if (stage === "related") { if (entryIndex >= entries.length) { nodes = Array.from(relatedIds).map(id => family[id]).filter(Boolean); for (const n of nodes) graph.set(n.id, new Set()); if (!nodes.length) return finishEmpty(); stage = "graph"; nodeIndex = 0; onProgress?.("\u5bb6\u7cfb\u3092\u7d50\u5408\u4e2d", 0, nodes.length); continue; } const n = entries[entryIndex++]; const parents = parentsByChild.get(n.id) || new Set(); const children = childrenByParent.get(n.id) || new Set(); if (parents.size || children.size) relatedIds.add(n.id); for (const id of parents) relatedIds.add(id); for (const id of children) relatedIds.add(id); } else if (stage === "graph") { if (nodeIndex >= nodes.length) { sortedNodes = nodes.slice().sort(lineageNodeSort); stage = "components"; dfsIndex = 0; onProgress?.("\u5bb6\u7cfb\u3092\u5206\u5272\u4e2d", 0, sortedNodes.length); continue; } const n = nodes[nodeIndex++]; const parentCandidates = new Set((n.parents || []).filter(Boolean)); for (const parentId of parentsByChild.get(n.id) || []) parentCandidates.add(parentId); for (const parentId of parentCandidates) { if (parentId !== n.id && graph.has(parentId) && validParentChild(n.id, parentId)) addGraphEdge(n.id, parentId); } const childCandidates = new Set((n.children || []).filter(Boolean)); for (const childId of childrenByParent.get(n.id) || []) childCandidates.add(childId); for (const childId of childCandidates) { if (childId !== n.id && graph.has(childId) && validParentChild(childId, n.id)) addGraphEdge(n.id, childId); } } else if (stage === "components") { while (dfsIndex < sortedNodes.length && seen.has(sortedNodes[dfsIndex].id)) dfsIndex += 1; if (dfsIndex >= sortedNodes.length) { comps.sort((a, b) => { const ag = Math.min(...a.map(n => n.generation || 1)); const bg = Math.min(...b.map(n => n.generation || 1)); const at = Math.min(...a.map(n => n.birthTime || 0)); const bt = Math.min(...b.map(n => n.birthTime || 0)); return ag - bg || at - bt || b.length - a.length; }); onDone(comps); return; } const start = sortedNodes[dfsIndex++]; const stack = [start.id]; const comp = []; seen.add(start.id); while (stack.length && performance.now() - started < 6) { const id = stack.pop(); const item = family[id]; if (item) comp.push(item); for (const next of graph.get(id) || []) { if (seen.has(next)) continue; seen.add(next); stack.push(next); } } if (stack.length) { const continueComp = () => { if (!lineageSameFamilyToken(token)) return; const resumedStarted = performance.now(); while (stack.length && performance.now() - resumedStarted < 6) { const id = stack.pop(); const item = family[id]; if (item) comp.push(item); for (const next of graph.get(id) || []) { if (seen.has(next)) continue; seen.add(next); stack.push(next); } } if (stack.length) return lineageIdleSchedule(continueComp, 180); if (comp.some(x => x.alive)) comps.push(comp); lineageIdleSchedule(pump, 180); }; return lineageIdleSchedule(continueComp, 180); } if (comp.some(x => x.alive)) comps.push(comp); } } onProgress?.( stage === "relations" ? "\u95a2\u4fc2\u3092\u6574\u7406\u4e2d" : stage === "related" ? "\u8868\u793a\u5bfe\u8c61\u3092\u9078\u5225\u4e2d" : stage === "graph" ? "\u5bb6\u7cfb\u3092\u7d50\u5408\u4e2d" : "\u5bb6\u7cfb\u3092\u5206\u5272\u4e2d", stage === "graph" ? nodeIndex : entryIndex, stage === "graph" ? nodes.length : entries.length ); lineageIdleSchedule(pump, 180); }; lineageIdleSchedule(pump, 160); } function familyStatusText(n) { if (!n) return "\u4e0d\u660e"; return n.alive ? "\u751f\u5b58" : "\u6b7b\u4ea1"; } function familyDeathReasonLabel(rawReason, entity = null) { const text = String(rawReason || ""); if (!text) return ""; // The event log already records the normalized final death reason. Use the // same stored value here so the family tree and event UI cannot diverge. void entity; return text; }