sounds
This commit is contained in:
parent
c6ba3e38a2
commit
37087d08a7
189 changed files with 12864 additions and 9589 deletions
152
js/ui_family_data.js
Normal file
152
js/ui_family_data.js
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
"use strict";
|
||||
|
||||
function buildArchiveRows(family) {
|
||||
return liveFamilyComponents(family);
|
||||
}
|
||||
|
||||
function lineageEdgeFamily(family = world.family || {}) {
|
||||
return family || {};
|
||||
}
|
||||
|
||||
let lineageRelationIndexCache = { family: null, version: null, index: null };
|
||||
|
||||
function lineageBuildRelationIndex(family) {
|
||||
const known = new Set(Object.keys(family || {}));
|
||||
const parentsByChild = new Map();
|
||||
const childrenByParent = new Map();
|
||||
const addParent = (childId, parentId) => {
|
||||
if (!childId || !parentId || childId === parentId || !known.has(childId) || !known.has(parentId)) return;
|
||||
if (!parentsByChild.has(childId)) parentsByChild.set(childId, new Set());
|
||||
if (!childrenByParent.has(parentId)) childrenByParent.set(parentId, new Set());
|
||||
parentsByChild.get(childId).add(parentId);
|
||||
childrenByParent.get(parentId).add(childId);
|
||||
};
|
||||
for (const n of Object.values(family || {})) {
|
||||
if (!n?.id) continue;
|
||||
for (const parentId of n.parents || []) addParent(n.id, parentId);
|
||||
for (const childId of n.children || []) addParent(childId, n.id);
|
||||
}
|
||||
return { parentsByChild, childrenByParent };
|
||||
}
|
||||
|
||||
function lineageRelationIndex(family = world.family || {}) {
|
||||
family = lineageEdgeFamily(family);
|
||||
if (family !== (world.family || {})) return lineageBuildRelationIndex(family);
|
||||
const version = world.familyVersion || 0;
|
||||
if (lineageRelationIndexCache.family !== family || lineageRelationIndexCache.version !== version) {
|
||||
lineageRelationIndexCache = { family, version, index: lineageBuildRelationIndex(family) };
|
||||
}
|
||||
return lineageRelationIndexCache.index;
|
||||
}
|
||||
|
||||
function lineageMutualParentIds(n, family = world.family || {}, idSet = null) {
|
||||
const seen = new Set();
|
||||
const parents = [];
|
||||
if (!n?.id) return parents;
|
||||
const relationIndex = lineageRelationIndex(family);
|
||||
const candidates = new Set((n.parents || []).filter(Boolean));
|
||||
// Runtime updates can briefly touch one side first; recover the reciprocal
|
||||
// edge from the relation index so the archive does not flicker.
|
||||
for (const parentId of relationIndex.parentsByChild.get(n.id) || []) candidates.add(parentId);
|
||||
for (const id of candidates) {
|
||||
if (!id || id === n.id || seen.has(id)) continue;
|
||||
if (idSet && !idSet.has(id)) continue;
|
||||
const parent = family[id];
|
||||
if (!parent) continue;
|
||||
const childListsParent = Array.isArray(n.parents) && n.parents.includes(id);
|
||||
const parentListsChild = Array.isArray(parent.children) && parent.children.includes(n.id);
|
||||
if (!childListsParent && !parentListsChild) continue;
|
||||
seen.add(id);
|
||||
parents.push(id);
|
||||
}
|
||||
parents.sort((a, b) => lineageNodeSort(family[a], family[b]));
|
||||
return parents;
|
||||
}
|
||||
|
||||
function lineageMutualChildIds(n, family = world.family || {}, idSet = null) {
|
||||
const seen = new Set();
|
||||
const children = [];
|
||||
if (!n?.id) return children;
|
||||
const relationIndex = lineageRelationIndex(family);
|
||||
const candidates = new Set((n.children || []).filter(Boolean));
|
||||
// Same recovery as above, in the opposite direction.
|
||||
for (const childId of relationIndex.childrenByParent.get(n.id) || []) candidates.add(childId);
|
||||
for (const id of candidates) {
|
||||
if (!id || id === n.id || seen.has(id)) continue;
|
||||
if (idSet && !idSet.has(id)) continue;
|
||||
const child = family[id];
|
||||
if (!child) continue;
|
||||
const parentListsChild = Array.isArray(n.children) && n.children.includes(id);
|
||||
const childListsParent = Array.isArray(child.parents) && child.parents.includes(n.id);
|
||||
if (!parentListsChild && !childListsParent) continue;
|
||||
seen.add(id);
|
||||
children.push(id);
|
||||
}
|
||||
children.sort((a, b) => lineageNodeSort(family[a], family[b]));
|
||||
return children;
|
||||
}
|
||||
|
||||
function lineageHasActualRelation(n, family = world.family || {}) {
|
||||
return lineageMutualParentIds(n, family).length > 0 || lineageMutualChildIds(n, family).length > 0;
|
||||
}
|
||||
|
||||
function lineageRelatedIdSet(family) {
|
||||
family = lineageEdgeFamily(family);
|
||||
const relationIndex = lineageRelationIndex(family);
|
||||
const ids = new Set();
|
||||
for (const n of Object.values(family || {}).filter(Boolean)) {
|
||||
const parents = relationIndex.parentsByChild.get(n.id) || [];
|
||||
const children = relationIndex.childrenByParent.get(n.id) || [];
|
||||
if (parents.size || children.size) ids.add(n.id);
|
||||
for (const id of parents) ids.add(id);
|
||||
for (const id of children) ids.add(id);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
function liveFamilyComponents(family) {
|
||||
// Childless pairs, including pairs currently performing the birth ritual, are
|
||||
// intentionally excluded. A \u5bb6\u7cfb\u56f3 node is created only after an actual parent/child
|
||||
// relation exists; otherwise ritual participants become isolated pseudo-families.
|
||||
family = lineageEdgeFamily(family);
|
||||
const relatedIds = lineageRelatedIdSet(family);
|
||||
const nodes = Array.from(relatedIds).map(id => family[id]).filter(Boolean);
|
||||
const ids = new Set(nodes.map(n => n.id));
|
||||
const graph = new Map(nodes.map(n => [n.id, new Set()]));
|
||||
const addEdge = (a, b) => {
|
||||
if (!a || !b || !ids.has(a) || !ids.has(b)) return;
|
||||
graph.get(a).add(b);
|
||||
graph.get(b).add(a);
|
||||
};
|
||||
for (const n of nodes) {
|
||||
for (const p of lineageMutualParentIds(n, family, ids)) addEdge(n.id, p);
|
||||
for (const c of lineageMutualChildIds(n, family, ids)) addEdge(n.id, c);
|
||||
}
|
||||
const seen = new Set();
|
||||
const comps = [];
|
||||
for (const n of nodes.sort(lineageNodeSort)) {
|
||||
if (seen.has(n.id)) continue;
|
||||
const stack = [n.id];
|
||||
const comp = [];
|
||||
seen.add(n.id);
|
||||
while (stack.length) {
|
||||
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 (comp.some(x => x.alive)) comps.push(comp);
|
||||
}
|
||||
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;
|
||||
});
|
||||
return comps;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue