loadIcons runs in TextureStitchEvent.Post and could cache icons before other mods finished theirs in the same event (Chisel CTMLib -v4 submaps), leaving microblocks with missing or previous-stitch textures. loadIcons now marks icons stale; the first icont() read after a stitch rebuilds them once. Icons set through icont_$eq are kept. Addon materials reading icont() get the fix without API changes. Fixes GTNH issue 27183 without relying on handler priority (upstream #63).
20 KiB
Java migration divergences
Current intentional differences from the 1.7.12 reference. This is a compatibility ledger, not a port diary.
An unchanged implementation needs no entry. Track remaining work in migration status, client gaps in
manual release checks, and use the performance protocol
for measurements. Superseded decisions and per-port narratives remain in git history.
“No audited consumer” means the frozen GTNH binary inventory and consumer-source audit, not every possible
third-party mod. Removed members can still break unaudited consumers. Check
docs/migration/COMPATIBILITY.md, which holds both the binary inventory and the consumer-source
audit, before extending this list.
Runtime and error behavior
The multipart block intentionally retains the pre-PR #54 unlocalized name tile.multipart.
PR #54 (2d050bd) renamed it to tile.codechicken/multipart; cb3bef7 reverted that part to preserve
the established name. The block registry ID remains ForgeMultipart:block. This is an intentional rollback
of the later upstream rename, not a divergence from the 1.7.12 reference.
| Area | Difference and consequence |
|---|---|
| Raw microblock recipe material lookup | Skips MissingMicroMaterial, whose recovery item is stone, so sawing stone selects the real stone material. Existing missing-material microblocks retain their recovery behavior. This fixes a pre-existing Scala bug, also fixed upstream in PR #61. |
BlockMicroMaterial.icont() after a texture stitch |
loadIcons still builds icons eagerly, then marks them stale; the first icont() read after the stitch re-reads the block's icons once. Icons that another mod finishes in a later TextureStitchEvent.Post handler (Chisel's CTMLib -v4 submaps) are picked up instead of rendering as missing or previous-stitch textures. Icons assigned through icont_$eq are kept. Upstream PR #63 targets the same bug by lowering the stitch handler to EventPriority.LOW; this port does not need that ordering. |
| Client part additions | Reuses an existing multipart tile or creates one without converting the stale client block. Conversion and its outgoing packets remain server-side, avoiding duplicate parts and client disconnects. Matches upstream PR #62. |
BlockMicroMaterial.createAndRegister(Block, Seq, String) |
Registers every supplied metadata value instead of only zero, including dark-oak logs and leaves and their legacy-name remaps. This fixes a pre-existing Scala bug; material IDs are rebuilt through the existing registry mapping. |
| Chunk scheduled-tick restoration | Reactivates chunks with restored scheduled ticks so their callbacks resume at the saved deadlines. The Scala implementation restored entries without activating their chunk. NBT layout and duplicate-scheduling behavior are unchanged. |
| World scheduler clock loading | Retains successfully loaded schedTime instead of overwriting it with total world time. Missing or unreadable files still fall back to world time. This fixes a pre-existing Scala bug and preserves the clock used by saved absolute tick deadlines; the file format is unchanged. |
| Converted tile loading | Copies the source tile coordinates to a replacement produced by MultipartSaveLoad.loadTiles before assigning its world and validating it. The Scala implementation left converted replacements at their default coordinates. |
| Placement-modifier player lifecycle | Removes cached control-key state when a player logs out and transfers it from the old player object to its replacement on clone. The Scala implementation retained entries until server shutdown. |
IDWriter before setMax |
read/write throw IllegalStateException instead of NullPointerException. Uninitialized use is unsupported. |
PacketScheduler invalid maskWidth |
Throws IllegalArgumentException instead of scala.MatchError. Valid widths remain 1, 2, 4 and 8. |
NormalOcclusionTest.apply(Traversable, Traversable) |
Materializes both inputs into Java lists before testing instead of nested Scala forall. Finite, side-effect-free collections give the same result; eager traversal can change side effects, failure timing and termination for unusual inputs. |
MultipartRenderer dynamic-part guard |
Uses short-circuit || instead of |; the second getter is no longer evaluated when the first operand succeeds. Ordinary getters give the same render decision. |
ControlKeyModifer.map() |
The exposed Java map has no Scala default-value wrapper: an absent key returns null from get, not false from Scala apply. Use isControlDown for the unchanged absent-means-false behavior. |
Public companion constructors, including ASMImplicits.ExtBitSet$ / ExtClass$ |
Explicit construction creates an independent instance instead of replacing MODULE$. Java initializes the retained final module field in its static initializer; use MODULE$ for singleton identity. |
registerJavaTrait abstract inputs |
Abstract Java classes are accepted instead of throwing IllegalArgumentException. Declared abstract methods become generated-interface contracts without helper bodies; a later mixin must implement them before those members can be invoked. A no-argument abstract-mixin constructor may call an argument-taking superclass constructor; registration discards that direct call and its argument evaluation because the composite invokes the real base constructor first. This enables Java microblock mixins without changing existing concrete registrations. |
registerJavaTrait members annotated @SideOnly |
The old Java path ignored the annotation. It now omits opposite-side fields and methods from generated metadata and output, matching Forge and the Scala-trait path; visible and invisible annotation tables are both accepted. If the no-argument constructor is absent, including after Forge has already stripped it, an empty $init$ preserves the generated initializer contract without running its body. Current-side members are unchanged. No audited Java registration relied on the old retention. |
Concrete collection implementations also change without changing the declared java.lang.Iterable/List contracts:
| Return path | Reference → Java |
|---|---|
JCuboidPart.getSubParts / getCollisionBoxes |
Scala Seq adapter → immutable Collections.singletonList |
MultipartHelper.IPartTileConverter.convertMulti default |
Scala Seq adapter → Collections.emptyList / singletonList |
Microblock.getDrops |
Java view of Scala ListBuffer → mutable ArrayList |
Code depending on concrete collection classes or their exact mutation failure behavior must adapt.
Removed or changed binary surface
These removals were accepted because neither audit identified a consumer of the removed surface. This does not authorize removing other members of the same type.
| Area | Removed or changed surface |
|---|---|
IDWriter |
Removed write(), write_$eq(Function2), read() and read_$eq(Function1). Use the Java methods listed below. |
| Both registries | MicroMaterialRegistry.readIDMap and MultiPartRegistry$.readIDMap return java.util.List<String> instead of scala.collection.Seq<String>; the return descriptor changes. |
| Registry compiler accessors | Removed MicroMaterialRegistry's public mangled $$idMap(), $$typeMap() and $$nameMap() accessors, and the five codechicken$multipart$MultiPartRegistry$$...() accessors. Schematica's separate private MultiPartRegistry$.$$typeMap field is retained as a live Scala-map view; it is not one of the removed methods. |
NormallyOccludedPart |
Removed the inferred getType(): scala.runtime.Null$ overload; the String method remains. |
MissingMicroMaterial |
Removed static canRenderInPass(int) and isSolid() forwarders. The companion no longer declares these methods but inherits the interface defaults. |
TileMultipart |
Removed compiler accessor protected$worldObj(TileMultipart). |
BlockMultipart |
Removed hasTileEntity$default$1(); source callers must supply metadata explicitly. |
TileCache |
map() now returns Java Map; add/remove return void instead of Scala Option; apply(BlockCoord) returns a nullable FlaggedTile instead of Option. |
TileCache.FlaggedTile |
Dropped Scala case-class copy, copy-default getters, canEqual, value equals/hashCode/toString, product methods, and scala.Product/scala.Serializable. It now has ordinary object identity semantics; t() and removed() remain. |
ControlKeyModifer |
Replaced isClientPressing_$eq(boolean) with setClientPressing(boolean); map() returns Java Map<EntityPlayer, Boolean> instead of Scala mutable Map; removed playerControlValue(EntityPlayer) and its implicit wrapper type. |
ControlKeyHandler |
Former static facade is now a KeyBinding subclass with INSTANCE. wasPressed/tick are instance methods, and the twelve inherited-KeyBinding static forwarders disappear. Initializing this client-only class now initializes its binding. |
MultipartRenderer |
Removed inherited-TESR static forwarders func_147498_b, func_147496_a and func_147497_a. |
Removed named helpers and companions (besides private compiler artifacts covered below):
JPartialOcclusion$class, includingallowCompleteOcclusionand$init$.MicroMaterialRegistry$IMicroMaterial$classand its four statics.TRandomUpdateTick$class, includingonWorldJoinand$init$.TScheduledPacketPart$classandPacketScheduler$.IconHitEffects$.TileCache$andTileCache$FlaggedTile$.ControlKeyModifer$,ControlKeyModifer$ControlKeyValueandControlKeyHandler$.codechicken.multipart.package/package$andcodechicken.microblock.package/package$. Their logger aliases are replaced by the corresponding proxy'slogger().
Generated-tile helpers removed only from the raw artifact are listed separately below; Forge recreates them.
Other retained $class bridges and MODULE$ companions are not covered by this removal decision.
Additions and default-method changes
| Area | Additive Java entry points |
|---|---|
IDWriter |
write(MCDataOutput, int) and read(MCDataInput) |
JCuboidPart |
Static subParts, collisionBoxes and renderBreaking implementations for explicit forwarding |
JItemMultiPart |
Static hitDepth and onItemUse(part, ...) implementations |
MicroMaterialRegistry |
Static setupIDMap, calcMaxCuttingStrength and loadIcons forwarders |
PacketScheduler / TScheduledPacketPart |
Static sendScheduled() on the facade and readMask(part, packet) on the interface |
TileMultipart |
hasDynamicParts (false by default), renderStatic, renderDynamic and randomDisplayTick hooks for dispatching generated overrides through a stable superclass |
MultipartMixinFactory |
Four additional static forwarders: onCompiled, autoCompleteJavaTrait and the two mangled ASMMixinFactory parent helpers. Scala emitted them when the base became Java with its original public JVM access; the Java facade retains them. Existing facade entries are unchanged. |
MultipartEventHandler / MultipartEventHandler$ |
Public playerLoggedOut(PlayerLoggedOutEvent) and playerCloned(PlayerEvent.Clone) event handlers for placement-modifier state cleanup and transfer |
MultipartHelper |
createPreviewTileFromNBT(World, NBTTagCompound) builds an uninstalled client composite directly from saved multipart NBT and rejects unsupported parts as a whole |
Previously abstract JVM interface methods now have Java defaults, allowing inheritance without Scala forwarders:
| Interface | Default methods |
|---|---|
JPartialOcclusion |
allowCompleteOcclusion |
TFacePart |
solid, redstoneConductionMap |
JIconHitEffects |
getBreakingIcon |
TItemMultiPart |
getHitDepth |
TEdgePart |
conductsRedstone |
Saw |
getMaxCuttingStrength |
MicroMaterialRegistry.IMicroMaterial |
loadIcons, canRenderInPass, isSolid |
TScheduledPacketPart |
writeScheduled, readScheduled |
PlacementGrid |
render, drawLines, glTransformFace |
MultiPartRegistry.IPartFactory2 |
createPartForClientPreview; defaults to unsupported, while legacy Boolean/function adapters and built-in microblock factories construct their client variant |
ItemSaw consequently no longer declares getMaxCuttingStrength; ordinary calls still resolve to the default,
but declared-member reflection differs. The same distinction applies to the two MissingMicroMaterial$ methods above.
The TNormalOcclusion super accessor
codechicken$multipart$TNormalOcclusion$$super$occlusionTest(TMultiPart) additionally loses ACC_SYNTHETIC and
becomes a default returning true, so javac can see and implement it. NormallyOccludedPart inherits it rather than
declaring its own. Existing Scala binaries retain their explicit super-chain overrides.
Recompiling Scala consumers
Binary descriptors alone do not preserve Scala source semantics after ScalaSignature metadata is removed.
Trait composition
A Java interface cannot supply a Scala trait's class supertype or its linearization. A superclass method wins over
an interface default, so recompiling extends TMultiPart with TCuboidPart silently selects the empty superclass
methods. Explicit forwarding is required where the implementation remains abstract:
| Trait | Source migration |
|---|---|
TCuboidPart |
Extend JCuboidPart, or forward subparts, collision boxes and breaking rendering to its statics. |
TNormalOcclusion |
Explicitly combine NormalOcclusionTest.apply(this, other) with the intended super chain. Audit existing super.occlusionTest calls too: they no longer route through the old trait. |
TIconHitEffects |
Explicitly forward addHitEffects and addDestroyEffects to IconHitEffects. |
TItemMultiPart |
Explicitly forward onItemUse to JItemMultiPart; the inherited Item method otherwise wins. |
TRandomUpdateTick |
Explicitly implement onWorldJoin with TickScheduler.loadRandomTick(this). |
TScheduledPacketPart |
Explicitly forward read to TScheduledPacketPart.readMask(this, packet). |
ScratchBitSet |
Implement both methods and storage accessors, and initialize storage explicitly; the retained $class helper supplies the original implementations. |
Previously compiled calls to retained $class bridges still link. This does not apply to the removed helpers above.
Source syntax and generic signatures
- An object used as a value now needs
X$.MODULE$where that companion is retained (event handlers, proxies, recipes, packet handlers, materials and generators). Static calls alone do not supply a singleton value. - Object-application sugar becomes explicit
.apply(...), e.g.NormalOcclusionTest.apply(...)andMicroblockPlacement$.MODULE$.apply(...). ASMImplicitsno longer provides Scala implicit/value-class syntax. UsenodeName(clazz.getName)andExtBitSet$.MODULE$.copy$extension(bits)explicitly, or construct its retained Java wrappers. Existing boxed and$extensionbinary entry points remain; implicit conversion requires Scala metadata Java cannot emit.- Scala property assignment needs the explicit Java-authored
_$eq(...)method, e.g.renderID,loadingWorld,angelicaCompatand microblockshape. ByteCodeReader.advance(length)(value)becomesadvance(length, value)when recompiling Scala. Argument evaluation remains eager, and previously compiled calls retain the same binary descriptor.ASMMixinFactoryconstructor parameters andconstructarguments need an explicit ScalaSeq, not Scala varargs syntax. Overrides ofonCompiledandautoCompleteJavaTraitmust be public: Java preserves their original public JVM access without Scala's protected-source metadata.tile.partList(i)becomestile.partList.apply(i). Static bounds getters on Face, Corner, Edge and Post factories needaBounds()(index)instead ofaBounds(index); Java no-argument methods may need explicit parentheses.TileMultipart.getOrConvertTile2exposesTuple2<TileMultipart, Object>instead of a Scala-typed Boolean second element; source callers need a cast. Scala 2.11 callbacks tooperate(Function1<..., BoxedUnit>)need an explicitAbstractFunction1instead of the previous Unit-returning lambda adaptation.- The Face, Corner, Edge, Post and Hollow factory companions widen
baseTraittoClass<? extends Microblock>from their specific trait type. Their static facades retain the specific generic type, and the raw JVM return descriptor remainsClass. MultiPartRegistry.registerParts(IPartFactory, String[])and theIPartFactory2array overload gain Java varargs flags.BlockMultipart.addCollisionBoxesToListuses rawListrather thanList<?>to override vanilla. Neither changes its raw JVM descriptor.
Generated tile traits: raw artifact versus Forge runtime
These ports replace Scala interfaces/helpers with concrete Java mixin inputs. Forge rewrites the inputs back into
interfaces and generates implementation helpers. Existing binaries retain the runtime interface calls, but code
recompiled against the untransformed dev jar may emit invokevirtual or getfield against a type that becomes
an interface, causing verification/linkage failures. A transformed compile stub or explicit source guidance is
required before claiming source-compatible downstream rebuilds.
| Raw artifact change | Affected types |
|---|---|
Interface + $class → concrete TileMultipart subclass |
TSlottedTile, TRedstoneTile, TTileChangeTile, TFluidHandlerTile, TileMultipartClient |
Interface + $class → concrete child mixin input |
TRandomDisplayTickTile (extends the registered TileMultipartClient input) |
Removed raw TIInventoryTile$class; moved implementation into JInventoryTile input |
TIInventoryTile itself remains a Java interface. Source consumers should target it, not the transformed JInventoryTile class. |
Inherited/foreign-field and callback workarounds add package-private access shims for redstone, tile-change, fluid, inventory and both client traits; they are not new consumer APIs. Transformer constraints and shim recipes are documented once in the handoff.
The client-trait port also deliberately extends the Java-trait transformer:
- Registered Java traits may extend an already registered Java trait; the parent runtime interface participates in hierarchy and linearization.
- Explicit field accessors are recognized rather than duplicated.
- Transient input fields are excluded from automatic
copyFrom. Client render caches use this opt-out; their generated runtime fields remain ordinary private fields.
Shared classfile differences
These are accepted once for the migration, not repeated for every otherwise equivalent port:
- Java-authored types lose Scala signature/marker attributes and use Java source-file/debug metadata. Static facades
and helper classes gain private constructors where Scala emitted none. Java class initializers lack Scala's
ACC_PUBLIC, and singleton publication follows Java initialization rather than Scala's constructor assignment. - Unreferenced Scala
$$anonfun$/anonymous implementation classes and private compiler accessors disappear or are replaced by Java helpers/lambdas. Their names are not supported API. This does not include named compatibility bridges or reflective fields: audit those individually. Retained Scala traits still emit their required helpers. - The
Object[]renderer bridges onItemMicroPartRenderer$andItemSawRenderer$are ordinary Java methods rather than synthetic Scala methods; signatures and dispatch remain available. - The private scratch
ThreadLocalfields onMicroblockGenerator$andMultipartGenerator$loseACC_FINALso their public Scala-trait setters can still replace storage. - The private
ItemSaw.harvestLevelfield losesACC_FINALsosetHarvestLevel(int)can update the same storage as the retained getter and old Iguana reflection. Its name, type and visibility remain unchanged. - TickScheduler's private Scala scheduler types become package-private static nested Java classes, retaining their binary names.
Updating this ledger
Add only a new effective runtime, binary, source or classfile difference, with its consequence and reason for acceptance. Amend an existing row when a later change supersedes it. Do not add passing test counts, unchanged behavior, build observations, performance claims or a “no divergence” section for each new port.