Files
Genarrative/apps/ai-game-creator-shell/src/view/project-development/resourceDependencyGraphModel.ts
T
menghao 29dce59b35
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / Backend tests (pull_request) Failing after 7s
Project CI / Frontend tests (pull_request) Successful in 2m50s
Project CI / Native shell tests (pull_request) Successful in 11m33s
将资源依赖图计算迁移到Rust
新增只读资源依赖图模型,完成过滤、去重、环检测和任务流聚合
前端SVG仅负责几何渲染、搜索联动、选中高亮和拖动预览
补充资源图、覆盖层和工作台回归测试
同步更新工作台PRD、技术方案与项目记忆
2026-07-31 19:09:57 +08:00

243 lines
7.7 KiB
TypeScript

export type ProjectResourceGraphNodeInput = {
resourceId: string;
manifestAssetId: string | null;
producerTaskId: string | null;
};
export type ProjectResourceReferenceEdge = {
id: string;
kind: 'asset-reference';
sourceResourceId: string;
targetResourceId: string;
cyclic: boolean;
};
export type ProjectResourceTaskFlow = {
id: string;
kind: 'task-flow';
sourceTaskId: string;
targetTaskId: string;
sourceResourceIds: string[];
targetResourceIds: string[];
cyclic: boolean;
};
export type ProjectResourceConnectionIndexDto = {
resourceId: string;
upstreamReferenceResourceIds: string[];
downstreamReferenceResourceIds: string[];
referenceEdgeIds: string[];
taskFlowIds: string[];
};
export type ProjectResourceProducerAssignment = {
resourceId: string;
taskId: string;
};
export type ProjectResourceGraphReadModel = {
resourceIds: string[];
referenceEdges: ProjectResourceReferenceEdge[];
taskFlows: ProjectResourceTaskFlow[];
connectionIndex: ProjectResourceConnectionIndexDto[];
producerAssignments: ProjectResourceProducerAssignment[];
unresolvedReferenceResourceIds: string[];
cyclicResourceIds: string[];
cyclicTaskIds: string[];
producerMappingTruncated: boolean;
};
type ProjectResourceConnectionIndex = {
upstreamReferenceResourceIds: ReadonlySet<string>;
downstreamReferenceResourceIds: ReadonlySet<string>;
referenceEdgeIds: ReadonlySet<string>;
taskFlowIds: ReadonlySet<string>;
};
export type ProjectResourceGraph = {
resourceIds: ReadonlySet<string>;
referenceEdges: ProjectResourceReferenceEdge[];
referenceEdgeById: ReadonlyMap<string, ProjectResourceReferenceEdge>;
taskFlows: ProjectResourceTaskFlow[];
taskFlowById: ReadonlyMap<string, ProjectResourceTaskFlow>;
connectionIndex: ReadonlyMap<string, ProjectResourceConnectionIndex>;
producerTaskIdByResourceId: ReadonlyMap<string, string>;
unresolvedReferenceResourceIds: string[];
cyclicResourceIds: ReadonlySet<string>;
cyclicTaskIds: ReadonlySet<string>;
producerMappingTruncated: boolean;
};
export type ProjectResourceGraphNeighbors = {
upstreamResourceIds: ReadonlySet<string>;
downstreamResourceIds: ReadonlySet<string>;
connectedEdgeIds: ReadonlySet<string>;
};
const emptyStringSet: ReadonlySet<string> = new Set<string>();
const emptyStringMap: ReadonlyMap<string, string> = new Map<string, string>();
const emptyConnectionMap: ReadonlyMap<string, ProjectResourceConnectionIndex> =
new Map<string, ProjectResourceConnectionIndex>();
const emptyTaskFlowMap: ReadonlyMap<string, ProjectResourceTaskFlow> = new Map<
string,
ProjectResourceTaskFlow
>();
const emptyReferenceEdgeMap: ReadonlyMap<
string,
ProjectResourceReferenceEdge
> = new Map<string, ProjectResourceReferenceEdge>();
export const EMPTY_PROJECT_RESOURCE_GRAPH_NEIGHBORS: ProjectResourceGraphNeighbors =
{
upstreamResourceIds: emptyStringSet,
downstreamResourceIds: emptyStringSet,
connectedEdgeIds: emptyStringSet,
};
export const EMPTY_PROJECT_RESOURCE_GRAPH: ProjectResourceGraph = {
resourceIds: emptyStringSet,
referenceEdges: [],
referenceEdgeById: emptyReferenceEdgeMap,
taskFlows: [],
taskFlowById: emptyTaskFlowMap,
connectionIndex: emptyConnectionMap,
producerTaskIdByResourceId: emptyStringMap,
unresolvedReferenceResourceIds: [],
cyclicResourceIds: emptyStringSet,
cyclicTaskIds: emptyStringSet,
producerMappingTruncated: false,
};
function uniqueSorted(values: Iterable<string>) {
return Array.from(new Set(values)).sort((left, right) =>
left < right ? -1 : left > right ? 1 : 0,
);
}
export function normalizeProjectResourceGraph(
readModel: ProjectResourceGraphReadModel,
): ProjectResourceGraph {
const resourceIds = new Set(uniqueSorted(readModel.resourceIds));
const referenceEdges = readModel.referenceEdges
.filter(
(edge) =>
edge.kind === 'asset-reference' &&
resourceIds.has(edge.sourceResourceId) &&
resourceIds.has(edge.targetResourceId),
)
.sort((left, right) => left.id.localeCompare(right.id));
const referenceEdgeIds = new Set(referenceEdges.map((edge) => edge.id));
const taskFlows = readModel.taskFlows
.flatMap((flow) => {
if (flow.kind !== 'task-flow') {
return [];
}
const sourceResourceIds = uniqueSorted(
flow.sourceResourceIds.filter((resourceId) =>
resourceIds.has(resourceId),
),
);
const targetResourceIds = uniqueSorted(
flow.targetResourceIds.filter((resourceId) =>
resourceIds.has(resourceId),
),
);
return sourceResourceIds.length > 0 && targetResourceIds.length > 0
? [{ ...flow, sourceResourceIds, targetResourceIds }]
: [];
})
.sort((left, right) => left.id.localeCompare(right.id));
const taskFlowIds = new Set(taskFlows.map((flow) => flow.id));
const connectionIndex = new Map<string, ProjectResourceConnectionIndex>();
for (const index of readModel.connectionIndex) {
if (!resourceIds.has(index.resourceId)) {
continue;
}
connectionIndex.set(index.resourceId, {
upstreamReferenceResourceIds: new Set(
index.upstreamReferenceResourceIds.filter((resourceId) =>
resourceIds.has(resourceId),
),
),
downstreamReferenceResourceIds: new Set(
index.downstreamReferenceResourceIds.filter((resourceId) =>
resourceIds.has(resourceId),
),
),
referenceEdgeIds: new Set(
index.referenceEdgeIds.filter((edgeId) =>
referenceEdgeIds.has(edgeId),
),
),
taskFlowIds: new Set(
index.taskFlowIds.filter((flowId) => taskFlowIds.has(flowId)),
),
});
}
const producerTaskIdByResourceId = new Map(
readModel.producerAssignments.flatMap((assignment) =>
resourceIds.has(assignment.resourceId) && assignment.taskId
? [[assignment.resourceId, assignment.taskId] as const]
: [],
),
);
return {
resourceIds,
referenceEdges,
referenceEdgeById: new Map(referenceEdges.map((edge) => [edge.id, edge])),
taskFlows,
taskFlowById: new Map(taskFlows.map((flow) => [flow.id, flow])),
connectionIndex,
producerTaskIdByResourceId,
unresolvedReferenceResourceIds: uniqueSorted(
readModel.unresolvedReferenceResourceIds,
),
cyclicResourceIds: new Set(
readModel.cyclicResourceIds.filter((resourceId) =>
resourceIds.has(resourceId),
),
),
cyclicTaskIds: new Set(readModel.cyclicTaskIds),
producerMappingTruncated: Boolean(readModel.producerMappingTruncated),
};
}
export function projectResourceGraphNeighbors(
graph: ProjectResourceGraph,
resourceId: string | null,
): ProjectResourceGraphNeighbors {
if (!resourceId || !graph.resourceIds.has(resourceId)) {
return EMPTY_PROJECT_RESOURCE_GRAPH_NEIGHBORS;
}
const index = graph.connectionIndex.get(resourceId);
if (!index) {
return EMPTY_PROJECT_RESOURCE_GRAPH_NEIGHBORS;
}
const upstreamResourceIds = new Set(
index.upstreamReferenceResourceIds,
);
const downstreamResourceIds = new Set(
index.downstreamReferenceResourceIds,
);
const connectedEdgeIds = new Set(index.referenceEdgeIds);
for (const flowId of index.taskFlowIds) {
const flow = graph.taskFlowById.get(flowId);
if (!flow) {
continue;
}
if (flow.targetResourceIds.includes(resourceId)) {
flow.sourceResourceIds.forEach((id) => upstreamResourceIds.add(id));
connectedEdgeIds.add(flow.id);
}
if (flow.sourceResourceIds.includes(resourceId)) {
flow.targetResourceIds.forEach((id) => downstreamResourceIds.add(id));
connectedEdgeIds.add(flow.id);
}
}
upstreamResourceIds.delete(resourceId);
downstreamResourceIds.delete(resourceId);
return { upstreamResourceIds, downstreamResourceIds, connectedEdgeIds };
}