Skip to content

Commit 2665150

Browse files
BAKAOLCstd-microblock
authored andcommitted
feat: enhance dependency resolution with cycle detection and improve mod handling
1 parent 5337c08 commit 2665150

File tree

1 file changed

+73
-33
lines changed

1 file changed

+73
-33
lines changed

src/celemod-ui/src/routes/Manage.tsx

Lines changed: 73 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -185,12 +185,14 @@ const ModLocal = ({
185185
size,
186186
duplicateCount,
187187
duplicateFiles,
188-
}: ModInfo & { optional?: boolean }) => {
188+
renderPath = [],
189+
}: ModInfo & { optional?: boolean; renderPath?: string[] }) => {
189190
const { download } = useGlobalContext();
190191
const [expanded, setExpanded] = useState(false);
191192
const [hovered, setHovered] = useState(false);
192193

193194
const ctx = useContext(modListContext);
195+
const hasCycle = renderPath.includes(name);
194196

195197
const hasDeps = useMemo(
196198
() => dependencies.some((v) => !excludeList.includes(v.name)),
@@ -288,6 +290,12 @@ const ModLocal = ({
288290
</ModBadge>
289291
) : null)}
290292

293+
{hasCycle && (
294+
<ModBadge bg="#9c27b0" color="white">
295+
{_i18n.t('循环依赖')}
296+
</ModBadge>
297+
)}
298+
291299
{optional && (
292300
<ModBadge bg="#ff9800" color="white">
293301
{_i18n.t('可选依赖')}
@@ -389,18 +397,18 @@ const ModLocal = ({
389397
<Icon name="delete" />
390398
</span>
391399
)}
392-
{(!optional || ctx?.fullTree) && expanded && (
400+
{(!optional || ctx?.fullTree) && expanded && !hasCycle && (
393401
<div className={`childTree ${expanded && 'expanded'}`}>
394402
{dependencies.map((v) => (
395-
<Mod {...v} />
403+
<Mod {...v} renderPath={[...renderPath, name]} />
396404
))}
397405
</div>
398406
)}
399407
</div>
400408
);
401409
};
402410

403-
const Mod = (props: ModDepInfo) => {
411+
const Mod = (props: ModDepInfo & { renderPath?: string[] }) => {
404412
if (excludeList.includes(props.name)) {
405413
return null;
406414
}
@@ -574,39 +582,52 @@ export const Manage = () => {
574582
size: mod.size,
575583
_deps: mod.deps,
576584
resolveDependencies: () => {
577-
let status = 'resolved';
578-
let message = '';
579-
580-
const mergeSM = (
581-
s: {
582-
status: DepState;
583-
message: string;
584-
},
585-
name: String
585+
const resolveModDependencies = (
586+
current: BackendModInfo,
587+
visiting = new Set<string>()
586588
) => {
587-
if (s.status === 'resolved') return;
588-
if (status === 'resolved') {
589-
status = s.status;
589+
if (visiting.has(current.name)) {
590+
return { status: 'resolved', message: '' } as DepResolveResult;
590591
}
591-
message += ` | ${name}(${s.status}):${s.message}`;
592-
};
593592

594-
for (const dep of mod.deps) {
595-
if (
596-
excludeList.includes(dep.name) ||
597-
(dep.optional && !checkOptionalDep)
598-
)
599-
continue;
593+
visiting.add(current.name);
594+
595+
let status = 'resolved';
596+
let message = '';
597+
598+
const mergeSM = (
599+
s: {
600+
status: DepState;
601+
message: string;
602+
},
603+
name: string
604+
) => {
605+
if (s.status === 'resolved') return;
606+
if (status === 'resolved') {
607+
status = s.status;
608+
}
609+
message += ` | ${name}(${s.status}):${s.message}`;
610+
};
611+
612+
for (const dep of current.deps) {
613+
if (
614+
excludeList.includes(dep.name) ||
615+
(dep.optional && !checkOptionalDep)
616+
) {
617+
continue;
618+
}
619+
620+
if (!modMap.has(dep.name)) {
621+
mergeSM({ status: 'missing', message: '' }, dep.name);
622+
continue;
623+
}
600624

601-
if (!modMap.has(dep.name)) {
602-
mergeSM({ status: 'missing', message: '' }, dep.name);
603-
} else {
604625
const installedDep = modMap.get(dep.name)!;
605626
if (compareVersion(installedDep.version, dep.version) < 0) {
606627
mergeSM(
607628
{
608629
status: 'mismatched-version',
609-
message: `${mod.name} requires ${installedDep.name} >= ${dep.version} but got ${installedDep.version}`,
630+
message: `${current.name} requires ${installedDep.name} >= ${dep.version} but got ${installedDep.version}`,
610631
},
611632
dep.name
612633
);
@@ -616,18 +637,25 @@ export const Manage = () => {
616637
mergeSM(
617638
{
618639
status: 'not-enabled',
619-
message: `${mod.name} requires ${installedDep.name} to be enabled`,
640+
message: `${current.name} requires ${installedDep.name} to be enabled`,
620641
},
621642
dep.name
622643
);
623644
}
624645

625-
const depRes = installedDep.resolveDependencies();
626-
mergeSM(depRes, dep.name);
646+
const depBackend = installedMods.find((v) => v.name === dep.name);
647+
if (depBackend) {
648+
const depRes = resolveModDependencies(depBackend, visiting);
649+
mergeSM(depRes, dep.name);
650+
}
627651
}
628-
}
629652

630-
return { status, message } as DepResolveResult;
653+
visiting.delete(current.name);
654+
655+
return { status, message } as DepResolveResult;
656+
};
657+
658+
return resolveModDependencies(mod);
631659
},
632660
duplicateCount: 1,
633661
duplicateFiles: [mod.file],
@@ -900,11 +928,18 @@ export const Manage = () => {
900928
'Miao.CelesteNet.Client',
901929
];
902930

931+
const visited = new Set<string>();
932+
903933
const addToSwitchList = (name: string) => {
934+
if (visited.has(name)) return;
935+
visited.add(name);
936+
904937
const mod = installedModMap.get(name);
905938
if (mod) {
906939
mod.enabled = enabled;
907940
switchList.push(name);
941+
} else {
942+
return;
908943
}
909944

910945
if (recursive) {
@@ -975,7 +1010,12 @@ export const Manage = () => {
9751010

9761011
// Find orphaned mods (mods that will have no references after deletion)
9771012
const orphanedMods: ModInfo[] = [];
1013+
const visited = new Set<string>();
1014+
9781015
const checkOrphans = (mod: ModInfo) => {
1016+
if (visited.has(mod.name)) return;
1017+
visited.add(mod.name);
1018+
9791019
for (const dep of mod.dependencies) {
9801020
if ('_missing' in dep) continue;
9811021
const depInfo = installedModMap.get(dep.name);

0 commit comments

Comments
 (0)