diff --git a/JAVA_MIGRATION.md b/JAVA_MIGRATION.md index bd1da58..31fb55b 100644 --- a/JAVA_MIGRATION.md +++ b/JAVA_MIGRATION.md @@ -24,10 +24,10 @@ No trustworthy tool will produce a maintainable Java port automatically. A decom ## Current status The binary and source-level consumer audits, low-coupling Phase 3 queue, audit-derived immediate compatibility gate, -focused profile, first traversal optimization, complete redstone interaction helper unit, `MicroRecipe` port, and the -no-field `TPartialOcclusionTile` Java-trait pilot are complete. Direct Java source now survives the existing -`registerJavaTrait` rewrite with its generated interface, super accessor, behavior, and class caching intact. The next -checkpoint is the stateful `TSlottedTile`; see `JAVA_MIGRATION_PROFILE.md`. +focused profile, first traversal optimization, complete redstone interaction helper unit, `MicroRecipe` port, and both +Java-trait checkpoints are complete. `TPartialOcclusionTile` proved the no-field path; `TSlottedTile` now proves field +accessors, per-instance initialization, rebinding/copying, callbacks, super bridges, and class caching. The next +measured target is `TRedstoneTile`; see `JAVA_MIGRATION_PROFILE.md`. 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 @@ -329,7 +329,7 @@ and the already-ported registry again supports Schematica. - [x] Rewrite `TileMultipart` update/operate traversal without per-call collection allocation. - [ ] Rewrite redstone queries without closure allocation or `IntRef` boxing. - [x] Replace non-local returns in recipe scans with ordinary Java control flow. -- [ ] Replace the remaining non-local return in the generated `TSlottedTile` slot scan. +- [x] Replace the remaining non-local return in the generated `TSlottedTile` slot scan. - [x] Keep fastutil out of these paths because profiling did not justify another dependency or data structure. - [x] Re-profile the same scenarios and record both improvements and regressions. @@ -337,9 +337,10 @@ Status: active. `operate` now preserves its captured-sequence mutation semantics and the six-interface `IRedstonePart`/`RedstoneInteractions$` unit is ported with its class list and descriptors intact. The comparison proves its allocation is owned by `scalatraits/TRedstoneTile.scala`, so that part waits for Phase 5. `MicroRecipe` is also Java: all five recipe forms and precedence are characterized, its published Scala façade remains, -and its closure-backed scans and non-local returns are gone. `TPartialOcclusionTile` has completed the simple no-field -Java-trait pilot. The remaining two hot-path items are stateful generated traits: port `TSlottedTile` as the field and -lifecycle checkpoint before changing `TRedstoneTile` and re-running the redstone profile. +and its closure-backed scans and non-local returns are gone. `TPartialOcclusionTile` completed the simple no-field +Java-trait pilot, and `TSlottedTile` completed the stateful field/lifecycle checkpoint while removing the remaining +slot-scan closures and exception-backed return. The remaining measured item is `TRedstoneTile`; characterize its exact +generated surface and real consumers, then re-profile immediately before and after its Java port. Exit condition: identified steady-state Scala allocation sites are removed with compatible results. @@ -348,13 +349,13 @@ Exit condition: identified steady-state Scala allocation sites are removed with - [x] Characterize the direct class shape, generated interface, super dispatch, behavior, and class caching of a no-field pilot. - [x] Port one simple built-in trait as a pilot using `registerJavaTrait`. -- [ ] Verify generated field accessors, initialization, copying, lifecycle callbacks, and class caching on the - stateful `TSlottedTile` checkpoint. The no-field pilot proves methods/interfaces/caching but cannot prove state. +- [x] Verify generated field accessors, initialization, copying, lifecycle callbacks, super bridges, and class caching + on the stateful `TSlottedTile` checkpoint. - [ ] Convert remaining traits in small related groups. - [ ] Document or deliberately relax the Java trait restrictions only when a real trait requires it. - [ ] Preserve pass-through interface behavior. -- [ ] Explicitly cover OpenComputers' `TSlottedTile.v_partMap` mutation/rebinding before that port, AE2's - `TIInventoryTile.rebuildSlotMap` before the inventory trait, and ProjectRed/Extra Utilities behavior before +- [ ] Keep the completed OpenComputers `TSlottedTile.v_partMap` mutation/rebinding case green; explicitly cover AE2's + `TIInventoryTile.rebuildSlotMap` before the inventory trait and ProjectRed/Extra Utilities behavior before `TRedstoneTile`. Exit condition: built-in behavior is implemented in Java while the established runtime composition mechanism remains stable. @@ -680,3 +681,14 @@ generated-trait compatibility work. Scala-runtime removal remains a later projec two behavior methods plus its super accessor, caches the generated class, and passes all 37 server tests. - The pilot raises the plain-JVM baseline to 145 tests. It deliberately proves no field or lifecycle behavior, so the next checkpoint is `TSlottedTile`, not a bulk conversion of the remaining traits. +- Characterized `TSlottedTile` against untouched Scala with six focused Forge cases plus the existing live-world + lifecycle fixture. The gate freezes its exact 13-method runtime interface, 27-slot per-instance initialization, + getter/setter rebinding, copy identity, clear/remove/bind behavior, occupied-slot rejection, Scala value equality, + generated field, super accessors, and class cache. +- Ported `TSlottedTile` to a concrete Java mixin input with one public array field and ordinary loops. The runtime + rewrite remains interface- and behavior-identical, while the four Scala range closures, `$class` artifact in the raw + jar, and `NonLocalReturnControl` slot rejection disappear. All 145 plain-JVM and 43 Forge tests pass. +- Found a Phase 5 source-build constraint: shipping binaries still see the same runtime interface, but a consumer + recompiled directly against the untransformed dev jar sees the Java mixin input as a class. Consumers that name a + generated trait therefore need a transformed compile stub or must avoid direct trait invocations; record this before + claiming source-compatible downstream rebuilds. diff --git a/JAVA_MIGRATION_CONSUMER_AUDIT.md b/JAVA_MIGRATION_CONSUMER_AUDIT.md index 5c414d1..9e686c4 100644 --- a/JAVA_MIGRATION_CONSUMER_AUDIT.md +++ b/JAVA_MIGRATION_CONSUMER_AUDIT.md @@ -501,9 +501,10 @@ behavior: converting microblocks or Minecraft part implementations. 6. Treat Scala runtime removal as downstream coordination work after the Java port, not as a requirement of it. -Current-branch generator verification: `ForgeEnvironmentSmokeTest` pins cached built-in `TSlottedTile` generation, -an external Scala microblock trait registered by name like ProjectRed's `LightMicroblock`, direct-Java-source -`TPartialOcclusionTile` rewriting and override dispatch, and server-only pass-through forwarding, overloading, +Current-branch generator verification: `ForgeEnvironmentSmokeTest` pins exact direct-Java-source `TSlottedTile` +interface/field generation, initialization, rebinding and caching, an external Scala microblock trait registered by +name like ProjectRed's `LightMicroblock`, direct-Java-source `TPartialOcclusionTile` rewriting and override dispatch, +and server-only pass-through forwarding, overloading, single-implementor selection, copying/rebinding, and removal cleanup. Full downstream mixed tiles and client-side selection remain Phase 7 integration cases. diff --git a/JAVA_MIGRATION_DIVERGENCES.md b/JAVA_MIGRATION_DIVERGENCES.md index 9f4582e..b172b71 100644 --- a/JAVA_MIGRATION_DIVERGENCES.md +++ b/JAVA_MIGRATION_DIVERGENCES.md @@ -1407,3 +1407,55 @@ assumed safe from this result; `TSlottedTile` is the explicit stateful checkpoin - Complete plain-JVM suite: 145 tests, 0 failures, 0 errors. - Java 8 Forge dedicated-server suite: 37 tests, 0 failures, 0 errors. - Runtime reflection verifies the exact generated interface methods, no fields, override behavior, and class reuse. + +## 2026-08-28 — TSlottedTile Java-trait port + +This is the stateful `registerJavaTrait` checkpoint. OpenComputers casts generated tiles to this type, retrieves the +public `v_partMap()` array, mutates matching entries in place, and calls `bindPart` to rebuild them. The frozen binary +inventory contains that exact getter descriptor and no reference to `TSlottedTile$class`. + +### Observable behavior + +No known runtime divergence. Every generated tile receives its own 27-entry array. `copyFrom` still calls its super +implementation first and then shares the source array by identity only when the source is slotted. `partMap` preserves +normal array indexing. `clearParts` still clears both the superclass part list and every entry in the current array. + +`partRemoved` still calls super first, acts only on `TSlottedPart`, scans exactly 27 slots, and uses Scala-style +null-safe value equality; Java uses `Objects.equals` to preserve the last detail. `canAddPart` still rejects the first +occupied masked slot before delegating to the base tile, while non-slotted and conflict-free parts delegate normally. +`bindPart` still calls super first and fills each of the 27 positive masked slots. OpenComputers' external mutation and +rebind pattern is exercised directly. + +### Generated ABI and raw input divergence + +At Forge runtime `TSlottedTile` remains a public interface with no fields and the exact same 13 abstract methods: six +behavior methods, `v_partMap` getter/setter, and five super accessors. The generated composite still owns a +`TMultiPart[] v_partMap` field, runs the 27-entry initializer for every instance, and reuses its class for equal trait +sets. All 27 shipping consumer jars therefore retain their binary linkage. + +The untransformed artifact necessarily changes shape. Scala emitted an interface, a static `$class` implementation +helper, and four `$$anonfun$` range classes. Java emits one concrete `TileMultipart` subclass with a public array field, +one no-arg constructor, and six overrides; `registerJavaTrait` creates the interface/helper at class-load time. No +shipping consumer references the removed raw helper or closure classes. + +This produces an accepted source-build limitation: code recompiled directly against the raw dev jar sees a class and +public field, so it may emit `invokevirtual` or `getfield`; Forge later rewrites that owner to an interface, for which +those opcodes are invalid. Existing binaries such as OpenComputers use `invokeinterface` and remain valid. A future +downstream-source release needs either a transformed compile stub/API artifact or explicit avoidance of direct +generated-trait calls; runtime binary compatibility does not by itself solve that development artifact problem. + +### Performance boundary + +Ordinary loops remove the four Scala `Range.foreach` closure classes and the `NonLocalReturnControl` used for occupied +slot rejection. The retained focused profiler does not perform slotted placement, so this is a structural removal, not +a numeric performance claim. No new dependency or generator change was needed. + +### Validation + +- `TSlottedTileFunctionalTest`: 6 Forge tests covering copy identity, non-slotted copy, external mutation/rebinding, + bind/remove/clear semantics, value equality, and occupied/free/non-slotted placement. +- `ForgeEnvironmentSmokeTest` verifies the exact 13-method runtime interface, generated field, distinct initialized + arrays, setter rebinding, and class-cache identity. +- Existing live-world lifecycle tests continue to cover add/remove slot caches and relocation of the generated tile. +- Complete plain-JVM suite: 145 tests, 0 failures, 0 errors. +- Java 8 Forge dedicated-server suite: 43 tests, 0 failures, 0 errors. diff --git a/JAVA_MIGRATION_HANDOFF.md b/JAVA_MIGRATION_HANDOFF.md index fb3834a..3c9a765 100644 --- a/JAVA_MIGRATION_HANDOFF.md +++ b/JAVA_MIGRATION_HANDOFF.md @@ -12,7 +12,7 @@ what breaks, and what is left. The other documents hold the reasoning. | `JAVA_MIGRATION_MANUAL_CHECKS.md` | What no automated test can cover, and must be checked by hand in a client | | `JAVA_MIGRATION_PROFILE.md` | The focused baseline, first measured result, findings, and exact rerun command | -Branch: `algent/java`. Base: `master`. 70 commits including the `TPartialOcclusionTile` characterization and Java port. +Branch: `algent/java`. Base: `master`. 72 commits including both `TSlottedTile` commits. ## The one rule that matters @@ -66,7 +66,7 @@ awk -F'"' '/ 0) { + v_partMap[index] = part; + } + } + } + } +} diff --git a/src/main/scala/codechicken/multipart/scalatraits/TSlottedTile.scala b/src/main/scala/codechicken/multipart/scalatraits/TSlottedTile.scala deleted file mode 100644 index a6d650a..0000000 --- a/src/main/scala/codechicken/multipart/scalatraits/TSlottedTile.scala +++ /dev/null @@ -1,55 +0,0 @@ -package codechicken.multipart.scalatraits - -import codechicken.multipart.TileMultipart -import codechicken.multipart.TMultiPart -import codechicken.multipart.TSlottedPart - -/** Mixin implementation for TSlottedPart. Puts parts into a slot array for - * quick access at the cost of memory consumption - */ -trait TSlottedTile extends TileMultipart { - var v_partMap = new Array[TMultiPart](27) - - override def copyFrom(that: TileMultipart) { - super.copyFrom(that) - if (that.isInstanceOf[TSlottedTile]) - v_partMap = that.asInstanceOf[TSlottedTile].v_partMap - } - - override def partMap(slot: Int) = v_partMap(slot) - - override def clearParts() { - super.clearParts() - for (i <- 0 until v_partMap.length) - v_partMap(i) = null - } - - override def partRemoved(part: TMultiPart, p: Int) { - super.partRemoved(part, p) - if (part.isInstanceOf[TSlottedPart]) - for (i <- 0 until 27) - if (partMap(i) == part) - v_partMap(i) = null - } - - override def canAddPart(part: TMultiPart): Boolean = { - if (part.isInstanceOf[TSlottedPart]) { - val slotMask = part.asInstanceOf[TSlottedPart].getSlotMask - for (i <- 0 until v_partMap.length) - if ((slotMask & 1 << i) != 0 && partMap(i) != null) - return false - } - - return super.canAddPart(part) - } - - override def bindPart(part: TMultiPart) { - super.bindPart(part) - if (part.isInstanceOf[TSlottedPart]) { - val mask = part.asInstanceOf[TSlottedPart].getSlotMask - for (i <- 0 until 27) - if ((mask & 1 << i) > 0) - v_partMap(i) = part - } - } -}