diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java index fddbe95684..c441b7d9fc 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java @@ -293,6 +293,8 @@ public abstract class DefaultGameRepository implements GameRepository { LOG.warning("Failed to load instance", e); } } + + newSnapshot.removeInvalidInstances(); } catch (IOException e) { LOG.warning("Failed to load instance from " + instancesDir, e); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepositoryDraft.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepositoryDraft.java index 9ef4b22d6d..1f042bca61 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepositoryDraft.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepositoryDraft.java @@ -361,6 +361,7 @@ public final class DefaultGameRepositoryDraft implements GameRepositoryDraft { : repository.createInstance(committedSnapshot, manifest.id(), manifest); committedSnapshot.put(updated); } + committedSnapshot.removeInvalidInstances(); committedSnapshot.seal(); return committedSnapshot; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepositorySnapshot.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepositorySnapshot.java index 2a7cfea471..7a47959646 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepositorySnapshot.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepositorySnapshot.java @@ -160,6 +160,51 @@ public class DefaultGameRepositorySnapshot implements GameRepositorySnapshot { instances.remove(id); } + /// Remove invalid instances. + /// TODO: In the future we could retain these instances and show their status in the UI. + void removeInvalidInstances() { + checkMutable(); + + HashSet visited = null; + loop: + for (DefaultGameInstance instance : List.copyOf(instances.values())) { + if (instance.getManifest().inheritsFrom() == null) { + continue; + } + + if (visited != null) + visited.clear(); + else + visited = new HashSet<>(); + + DefaultGameInstance current = instance; + visited.add(current.getId()); + while (current.getManifest().inheritsFrom() != null) { + GameInstanceID parentId = current.getManifest().inheritsFrom(); + if (!visited.add(parentId)) { + // DefaultGameRepositorySnapshot.resolve can handle circular references + continue loop; + } + + DefaultGameInstance parent = instances.get(parentId); + if (parent == null) { + LOG.warning("Instance " + current.getId() + " inherits from missing instance " + parentId); + + for (DefaultGameInstance toRemoved = instance; toRemoved != null; ) { + instances.remove(toRemoved.getId()); + toRemoved = toRemoved.getManifest().inheritsFrom() != null + ? instances.get(toRemoved.getManifest().inheritsFrom()) + : null; + } + + break; + } + current = parent; + } + } + + } + /// Creates an unpublished mutable copy with instances rebound to the copy. /// /// The caller must call [#seal()] before retaining or publishing the result as a snapshot.