tarinai/js/ui_family_async.js
2026-07-05 18:01:36 +09:00

163 lines
6.7 KiB
JavaScript

"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;
}