mirror of
https://github.com/GTNewHorizons/ForgeMultipart.git
synced 2026-11-12 16:37:38 +00:00
fix: restore Schematica registry reflection compatibility
This commit is contained in:
1 parent
8d0ba7c76e
commit
49ed6fd751
7 files changed
+101
-21
No files matched your search
+16
-6
@@ -27,10 +27,10 @@ The binary and source-level consumer audits are complete. Phase 0 still lacks br
|
||||
pre-optimization profile. The low-coupling Phase 3 queue is complete; before entering measured hot-path or generated
|
||||
tile work, the immediate milestone is the audit-derived compatibility gate below.
|
||||
|
||||
That audit found one existing regression on this branch: Schematica 1.12.6 reflects the original private
|
||||
That audit found and this branch has now repaired one existing regression: Schematica 1.12.6 reflects the original private
|
||||
`MultiPartRegistry$` field `codechicken$multipart$MultiPartRegistry$$typeMap` and casts it to a Scala mutable map. The
|
||||
Java registry port moved the state to a private Java `HashMap` on `MultiPartRegistry`, so Schematica's integration
|
||||
currently disables itself. Restoring and testing that compatibility view is the next task.
|
||||
companion again exposes that exact private field as a live view of the canonical Java `HashMap`, and a test performs
|
||||
Schematica's reflective lookup. The remaining source-only member guards are next.
|
||||
|
||||
Phase 1 has not properly started: GTNHLib is present but was added at `api` scope to satisfy a test compile rather
|
||||
than because a migration change needed it, and no Forge mod dependency was declared. Revisit that entry only when a
|
||||
@@ -297,7 +297,7 @@ Exit condition: leaf code is Java and no longer generates avoidable Scala closur
|
||||
|
||||
Do this before another source conversion:
|
||||
|
||||
- [ ] Restore Schematica's reflective `MultiPartRegistry$` type-map view with its exact field name and Scala mutable-map
|
||||
- [x] Restore Schematica's reflective `MultiPartRegistry$` type-map view with its exact field name and Scala mutable-map
|
||||
shape, backed by the canonical Java registry state; add a regression test that performs Schematica's actual lookup.
|
||||
- [ ] Add exact member-level guards for GuideNH's `BlockMicroMaterial.block/meta` mixin fields, Et Futurum's mutable
|
||||
`ButtonPart.metaSideMap/sideMetaMap`, Iguana's `ItemSaw.harvestLevel`, and Galacticraft's name-only
|
||||
@@ -393,7 +393,7 @@ The work should move from low to high risk while keeping a buildable mixed-langu
|
||||
Extensive tests are both possible and recommended here. Their purpose is not to prove that the current Scala behavior is ideal; it is to make its observable behavior explicit before translation, so every later difference is intentional.
|
||||
|
||||
The repository now has a Java-8-compatible JUnit Jupiter suite and a small Forge integration runner. The current
|
||||
baseline is 125 plain-JVM tests and 22 Forge server tests. Minecraft 1.7.10 does not provide the modern GameTest
|
||||
baseline is 126 plain-JVM tests and 22 Forge server tests. Minecraft 1.7.10 does not provide the modern GameTest
|
||||
framework, so game-dependent coverage continues through the existing narrow Forge harness rather than a second test
|
||||
framework.
|
||||
|
||||
@@ -476,7 +476,7 @@ The old pre-audit calendar ranges are retired. They mixed the Java source port,
|
||||
work, and optional Scala-runtime removal into estimates that are no longer useful. Track the remaining work by
|
||||
completed compatibility gates instead:
|
||||
|
||||
1. Repair the Schematica registry view and finish the source-only reflection/member guards.
|
||||
1. Finish the source-only reflection/member guards; the Schematica registry view is repaired.
|
||||
2. Freeze tile ordering, lifecycle, NBT, packet, and generator behavior with compact representative fixtures.
|
||||
3. Capture the first focused CPU/allocation baseline and port the complete redstone unit.
|
||||
4. Convert a representative generated tile-trait group, then the remaining trait groups.
|
||||
@@ -594,3 +594,13 @@ generated-trait compatibility work. Scala-runtime removal remains a later projec
|
||||
- Dropped the three static forwarders Scala generated for inherited `TileEntitySpecialRenderer` members, the same category as the twelve `KeyBinding` forwarders dropped from `ControlKeyHandler`.
|
||||
- Dropped an unused `ThreadSafeISBRH` import. It annotated nothing and appears nowhere else; if the intent was to mark this ISBRH thread-safe for Angelica then the annotation was never applied in the reference either, which is worth checking independently of the migration.
|
||||
- Recorded that this port adds nothing to the automated suites and everything to the manual checklist. Static and dynamic rendering, the breaking overlay and the render id all need a client.
|
||||
|
||||
### 2026-08-27
|
||||
|
||||
- Reproduced Schematica 1.12.6's exact `ReflectionHelper` lookup of the private
|
||||
`MultiPartRegistry$.codechicken$multipart$MultiPartRegistry$$typeMap` field. The test failed against the existing Java
|
||||
port with `UnableToFindFieldException`, matching Schematica's silent integration-disable path.
|
||||
- Restored the exact private field name and `scala.collection.mutable.Map` descriptor as Scala's live wrapper over the
|
||||
canonical Java registry map. The wrapper adds no copied state or synchronization path.
|
||||
- Proved through the reflected Scala view that Schematica can resolve a factory and that the Java registry immediately
|
||||
sees the same entry. The focused registry suite and all 126 plain-JVM tests pass.
|
||||
@@ -351,9 +351,9 @@ effective public API**. A Java `Map` replacement will silently disable Schematic
|
||||
is retained or Schematica is patched and deployed first. This is a higher-risk constraint than the constant-pool
|
||||
inventory alone shows.
|
||||
|
||||
**Current branch:** this is already broken by the Java `MultiPartRegistry` port. The canonical state is now a private
|
||||
Java `HashMap` on `MultiPartRegistry`, while `MultiPartRegistry$` has no field with the reflected name. Restoring and
|
||||
testing a compatibility view is the immediate migration blocker.
|
||||
**Current branch:** the Java port initially broke this lookup. `MultiPartRegistry$` now retains the exact private field
|
||||
as a Scala mutable-map wrapper backed by the canonical Java `HashMap`, and a regression test performs Schematica's
|
||||
lookup and verifies that both views resolve the same factory.
|
||||
|
||||
### UtilitiesInExcess — future replacement, already a substantial consumer
|
||||
|
||||
|
||||
@@ -573,16 +573,18 @@ winning, registration still refuses to run once the registry is closed and still
|
||||
duplicate part id still throws, and `writePartID` and `getModContainer` still throw rather than returning null for an
|
||||
unknown name.
|
||||
|
||||
### Known reflective regression, discovered 2026-08-27
|
||||
### Resolved reflective regression, discovered and fixed 2026-08-27
|
||||
|
||||
The full consumer-source audit found a constraint that the bytecode scan missed. Schematica 1.12.6 obtains the private
|
||||
field `codechicken$multipart$MultiPartRegistry$$typeMap` from `MultiPartRegistry$` and casts its value to
|
||||
`scala.collection.mutable.Map`. The Java port moved the canonical map to a private `java.util.HashMap` on
|
||||
`MultiPartRegistry`, and `MultiPartRegistry$` exposes no compatibility field, so Schematica currently disables its FMP
|
||||
`MultiPartRegistry`, and `MultiPartRegistry$` exposed no compatibility field, so Schematica disabled its FMP
|
||||
integration during reflective initialization.
|
||||
|
||||
This is not an accepted divergence. Restore an exact reflective compatibility view backed by the canonical Java
|
||||
registry state and add a test reproducing Schematica's field lookup before continuing the migration.
|
||||
`MultiPartRegistry$` now restores that exact private field and descriptor using Scala's existing live Java-map wrapper;
|
||||
the Java map remains the single source of truth. `MultiPartRegistryCharacterizationTest` performs Schematica's actual
|
||||
`ReflectionHelper` lookup, casts the result to `scala.collection.mutable.Map`, and proves mutations reach the Java
|
||||
registry. No separate registry or synchronization path was added.
|
||||
|
||||
### Preserved Scala-typed descriptors
|
||||
|
||||
@@ -627,6 +629,15 @@ overloads build.
|
||||
run, and the suite resolves a part registered through each.
|
||||
- Error paths that log remain uncovered headless, as before, because the multipart logger is null until preInit.
|
||||
|
||||
### 2026-08-27 compatibility repair validation
|
||||
|
||||
- The new Schematica reflection case failed first with `UnableToFindFieldException`, then passed with the compatibility
|
||||
field present.
|
||||
- `javap -private -s` confirms the field is private/final with descriptor `Lscala/collection/mutable/Map;`.
|
||||
- `MultiPartRegistryCharacterizationTest`: 6 tests, 0 failures, 0 errors.
|
||||
- Complete plain-JVM suite: 126 tests, 0 failures, 0 errors.
|
||||
- Clean `spotlessApply checkstyleTest build`: passing.
|
||||
|
||||
## 2026-08-14 — TileMultipart Java port
|
||||
|
||||
The central tile. Twelve jars reference it and about twenty-five of its members. `TileMultipartClient` was split into
|
||||
|
||||
@@ -57,7 +57,7 @@ awk -F'"' '/<testsuite /{t+=$4;f+=$8;e+=$10} END{print "tests="t" failures="f" e
|
||||
grep -o 'tests="[0-9]*" skipped="[0-9]*" failures="[0-9]*" errors="[0-9]*"' run/server/junit-out/TEST-*.xml
|
||||
```
|
||||
|
||||
Current baseline: **125 plain-JVM tests, 22 Forge server tests, all passing.**
|
||||
Current baseline: **126 plain-JVM tests, 22 Forge server tests, all passing at their last completed runs.**
|
||||
|
||||
### ABI diff against the reference
|
||||
|
||||
@@ -192,12 +192,12 @@ Both `package.scala` objects are gone, removed rather than ported. `MultipartRen
|
||||
|
||||
**The low-risk queue is empty.** Pick the next piece deliberately rather than off the top of a list.
|
||||
|
||||
**Immediate blocker: repair the already-ported registry for Schematica.** Schematica 1.12.6 reflects
|
||||
`MultiPartRegistry$.codechicken$multipart$MultiPartRegistry$$typeMap` and casts it to a Scala mutable map. The current
|
||||
Java port has only a private Java `HashMap` on `MultiPartRegistry`, so that integration is broken. Restore an exact
|
||||
compatibility view backed by the canonical Java state and freeze Schematica's lookup in a test before any new port.
|
||||
**The Schematica registry blocker is repaired.** `MultiPartRegistry$` again has the exact private field
|
||||
`codechicken$multipart$MultiPartRegistry$$typeMap` with a Scala mutable-map descriptor. It is a live wrapper over the
|
||||
canonical Java map, and `MultiPartRegistryCharacterizationTest` reproduces Schematica's reflective lookup and proves
|
||||
both views reach the same factory.
|
||||
|
||||
Then close the small audit-derived test gap: exact reflection/mixin fields, tile list/order/move lifecycle, compact
|
||||
Next close the remaining audit-derived test gap: exact reflection/mixin fields, tile list/order/move lifecycle, compact
|
||||
mixed NBT/packet fixtures, and representative generated-trait/pass-through fixtures. The checklist is in the
|
||||
"Immediate compatibility gate" section of `JAVA_MIGRATION.md`.
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import codechicken.multipart.MultiPartRegistry.IPartConverter;
|
||||
import codechicken.multipart.MultiPartRegistry.IPartFactory;
|
||||
import codechicken.multipart.MultiPartRegistry.IPartFactory2;
|
||||
import cpw.mods.fml.common.ModContainer;
|
||||
import scala.collection.JavaConversions;
|
||||
|
||||
/**
|
||||
* Scala companion singleton. Retained because compiled Scala consumers read MODULE$ and call these instance methods.
|
||||
@@ -22,6 +23,11 @@ public final class MultiPartRegistry$ {
|
||||
|
||||
public static final MultiPartRegistry$ MODULE$ = new MultiPartRegistry$();
|
||||
|
||||
/** Retained with its original name and Scala type because Schematica reflects it directly. */
|
||||
@SuppressWarnings("unused")
|
||||
private final scala.collection.mutable.Map<String, IPartFactory2> codechicken$multipart$MultiPartRegistry$$typeMap = JavaConversions
|
||||
.mapAsScalaMap(MultiPartRegistry.typeMapBacking());
|
||||
|
||||
private MultiPartRegistry$() {}
|
||||
|
||||
@Deprecated
|
||||
|
||||
@@ -87,6 +87,10 @@ public final class MultiPartRegistry {
|
||||
|
||||
private MultiPartRegistry() {}
|
||||
|
||||
static Map<String, IPartFactory2> typeMapBacking() {
|
||||
return typeMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a part factory with an array of types it is capable of instantiating. Must be called before postInit.
|
||||
*
|
||||
|
||||
@@ -6,6 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -13,17 +14,22 @@ import java.util.NoSuchElementException;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.material.Material;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import codechicken.lib.data.MCDataInput;
|
||||
import codechicken.lib.vec.BlockCoord;
|
||||
import codechicken.multipart.MultiPartRegistry.IPartConverter;
|
||||
import codechicken.multipart.MultiPartRegistry.IPartFactory2;
|
||||
import cpw.mods.fml.relauncher.ReflectionHelper;
|
||||
import scala.Option;
|
||||
|
||||
/**
|
||||
* Part registration needs FML's active mod container, so it cannot run headless; that half of the registry is
|
||||
* characterized in the Forge server suite instead. Converter registration and dispatch touch neither FML nor the
|
||||
* logger, so they are covered here.
|
||||
* characterized in the Forge server suite instead. Converter dispatch and the Schematica reflection contract touch
|
||||
* neither FML nor the logger, so they are covered here.
|
||||
*/
|
||||
class MultiPartRegistryCharacterizationTest {
|
||||
|
||||
@@ -37,6 +43,30 @@ class MultiPartRegistryCharacterizationTest {
|
||||
assertThrows(NoSuchElementException.class, () -> MultiPartRegistry.getModContainer("test:nosuchpart"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void schematicaCanReadTheLiveScalaTypeMap() throws IllegalAccessException {
|
||||
Field moduleField = ReflectionHelper.findField(MultiPartRegistry$.class, "MODULE$");
|
||||
Field typeMapField = ReflectionHelper
|
||||
.findField(MultiPartRegistry$.class, "codechicken$multipart$MultiPartRegistry$$typeMap");
|
||||
Object module = moduleField.get(MultiPartRegistry$.class);
|
||||
scala.collection.mutable.Map<String, Object> reflectedTypeMap = (scala.collection.mutable.Map<String, Object>) typeMapField
|
||||
.get(module);
|
||||
|
||||
String type = "test:schematica_reflection";
|
||||
TestPart part = new TestPart();
|
||||
TestFactory factory = new TestFactory(part);
|
||||
reflectedTypeMap.put(type, factory);
|
||||
try {
|
||||
Option<Object> reflectedFactory = reflectedTypeMap.get(type);
|
||||
assertFalse(reflectedFactory.isEmpty());
|
||||
assertSame(factory, reflectedFactory.get());
|
||||
assertSame(part, MultiPartRegistry.loadPart(type, new NBTTagCompound()));
|
||||
} finally {
|
||||
reflectedTypeMap.remove(type);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void convertersAreDispatchedPerBlockAndFirstNonNullWins() {
|
||||
Block block = new Block(Material.rock) {};
|
||||
@@ -110,4 +140,23 @@ class MultiPartRegistryCharacterizationTest {
|
||||
return "test:converted";
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TestFactory implements IPartFactory2 {
|
||||
|
||||
private final TMultiPart part;
|
||||
|
||||
private TestFactory(TMultiPart part) {
|
||||
this.part = part;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TMultiPart createPart(String name, NBTTagCompound nbt) {
|
||||
return part;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TMultiPart createPart(String name, MCDataInput packet) {
|
||||
return part;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user