Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,49 @@ public void mergeCraftingJobs(CraftingJob target, CraftingJob mergee, boolean ma
}

if (markMergeeAsFinished) {
// Transfer all dependents of the mergee to the target before removing the mergee.
// This ensures that jobs which depended on the mergee now correctly depend on the target,
// so that crafted outputs are properly routed to those dependent jobs.
IntCollection mergeeDependents = dependents.get(mergee.getId());
if (mergeeDependents != null) {
for (int dependentId : mergeeDependents.toIntArray()) {
CraftingJob dependentJob = craftingJobs.get(dependentId);

// Update graph-level dependency map: replace mergee with target for this dependent
IntCollection depDependencies = dependencies.get(dependentId);
if (depDependencies != null) {
depDependencies.rem(mergee.getId());
if (!depDependencies.contains(target.getId())) {
depDependencies.add(target.getId());
}
}

// Update graph-level dependent map: add this dependent to target
IntCollection targetDependents = dependents.get(target.getId());
if (targetDependents == null) {
targetDependents = new IntArrayList();
dependents.put(target.getId(), targetDependents);
}
if (!targetDependents.contains(dependentId)) {
targetDependents.add(dependentId);
}

// Update job-level lists if the dependent job is available
if (dependentJob != null) {
dependentJob.getDependencyCraftingJobs().rem(mergee.getId());
if (!dependentJob.getDependencyCraftingJobs().contains(target.getId())) {
dependentJob.getDependencyCraftingJobs().add(target.getId());
}
if (!target.getDependentCraftingJobs().contains(dependentId)) {
target.getDependentCraftingJobs().add(dependentId);
}
}
}
// Clear mergee's dependents so onCraftingJobFinished doesn't process them
dependents.remove(mergee.getId());
mergee.getDependentCraftingJobs().clear();
}

// Remove the crafting job from the graph
this.onCraftingJobFinished(mergee, true);
}
Expand Down Expand Up @@ -300,7 +343,7 @@ public static CraftingJobDependencyGraph deserialize(HolderLookup.Provider looku
}

Int2ObjectMap<IntCollection> dependents = new Int2ObjectOpenHashMap<>();
CompoundTag dependentsTag = tag.getCompound("dependencies");
CompoundTag dependentsTag = tag.getCompound("dependents");
for (String key : dependentsTag.getAllKeys()) {
int id = Integer.parseInt(key);
int[] value = dependentsTag.getIntArray(key);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,4 +426,44 @@ public void testMergeCraftingJobsDependenciesMatchingAndNonMatching() {
assertThat(g.getDependents(J11D), equalTo(Lists.newArrayList(J0)));
}

/**
* Test that when two jobs (with different dependents) are merged, the mergee's dependents are
* transferred to the target rather than being lost. This covers the bug where a job that
* depended on the mergee would lose its dependency link after the merge, causing the merged
* job's output to bypass that dependent and flow to network storage instead.
*
* Concrete scenario: planks_for_fences (target) and planks_for_sticks (mergee) have the same
* recipe and are merged. fence_main depends on planks_for_fences; sticks_job depends on
* planks_for_sticks. After the merge, planks_merged must be depended upon by BOTH fence_main
* and sticks_job, so that all produced planks are routed correctly.
*/
@Test
public void testMergeCraftingJobsDependentsTransferred() {
// J2 depends on J0 (J0 is depended upon by J2)
J2.addDependency(J0);
g.addDependency(J2, J0);
// J3 depends on J1 (J1 is depended upon by J3)
J3.addDependency(J1);
g.addDependency(J3, J1);

// Merge J1 into J0 (same recipe, different dependents)
g.mergeCraftingJobs(J0, J1, true);

// J1 should be removed; J0, J2, J3 should remain
assertThat(Sets.newHashSet(g.getCraftingJobs()), equalTo(Sets.newHashSet(J0, J2, J3)));
assertThat(J0.getAmount(), equalTo(2));

// J0 should now be depended upon by both J2 and J3
assertThat(Sets.newHashSet(g.getDependents(J0)), equalTo(Sets.newHashSet(J2, J3)));
assertThat(J0.getDependentCraftingJobs().size(), equalTo(2));

// J2 should still depend on J0 (unchanged)
assertThat(Sets.newHashSet(g.getDependencies(J2)), equalTo(Sets.newHashSet(J0)));
assertThat(J2.getDependencyCraftingJobs(), equalTo(Lists.newArrayList(0)));

// J3 should now depend on J0 (transferred from J1)
assertThat(Sets.newHashSet(g.getDependencies(J3)), equalTo(Sets.newHashSet(J0)));
assertThat(J3.getDependencyCraftingJobs(), equalTo(Lists.newArrayList(0)));
}

}
Loading