mirror of
https://github.com/GTNewHorizons/ForgeMultipart.git
synced 2026-11-13 17:04:25 +00:00
refactor: port the marker interfaces to Java
TSlottedPart, IRandomDisplayTick, INeighborTileChange, TRandomUpdateTick, ISidedHollowConnect and IMicroMaterialRender. All six are member- and descriptor-identical to the reference. The seven implementors and mixin tiles were diffed too and are unchanged member for member. Five were already pure abstract interfaces in bytecode, so their conversion has no bytecode consequence; exactly one class leaves the jar, TRandomUpdateTick$class. onWorldJoin cannot become a default: TMultiPart declares it, and a superclass method beats an interface default, so the auto-registration would silently never run. It stays abstract and implementors call TickScheduler.loadRandomTick themselves, as RedstoneTorchPart already did. No bridge was written for TRandomUpdateTick$class. No jar in the pack references TRandomUpdateTick in any form, and the frozen baseline holds exactly the eight helpers the inventory lists. 99 plain-JVM tests and 5 Forge server tests pass.
This commit is contained in:
1 parent
3e68e67599
commit
9db07f8c7c
15 files changed
+189
-107
No files matched your search
@@ -498,3 +498,14 @@ These ranges were formed before designing the characterization suite and should
|
||||
- Generalised the default-versus-abstract rule. `TItemMultiPart` extends `Item` rather than `TMultiPart`, and the same split applied: `getHitDepth` became a default because `Item` does not declare it, while `onItemUse` stayed abstract because `Item` does. What matters is the implementor's actual superclass, not `TMultiPart` specifically.
|
||||
- Confirmed that placement logic is testable headless. `onItemUse` short-circuits as soon as `newPart` returns null, before any world, player or stack access, so the depth branch, the neighbour offset and the shared mutated `BlockCoord` can all be asserted with null arguments.
|
||||
- Added the first functional coverage of runtime class generation interacting with a ported type. Microblock classes are built by ASM at run time, so a plain-JVM test cannot show that a generated class resolves a Java interface default; the Forge server suite now generates a `FaceMicroblock` and asserts it inherits `redstoneConductionMap`, and generates a `HollowMicroblock` and asserts its own `0x10` and `false` still win.
|
||||
|
||||
### 2026-08-15
|
||||
|
||||
- Ported the six marker interfaces: `TSlottedPart`, `IRandomDisplayTick`, `INeighborTileChange`, `TRandomUpdateTick`, `ISidedHollowConnect` and `IMicroMaterialRender`. All six are member- and descriptor-identical to the reference, and the seven implementors and mixin tiles were diffed as well and are unchanged member for member.
|
||||
- Confirmed that five of the six were already pure abstract interfaces in bytecode with no `$class` helper, so their conversion had no bytecode consequence at all. Exactly one class left the jar: `TRandomUpdateTick$class`.
|
||||
- Applied the default-versus-abstract rule to `TRandomUpdateTick.onWorldJoin` and got the failing answer: `TMultiPart` declares `onWorldJoin`, so a default would be shadowed by the superclass and the auto-registration would silently never run. It stays abstract, and implementors declare it and call `TickScheduler.loadRandomTick` themselves, which `RedstoneTorchPart` — the only implementor in this codebase — already did.
|
||||
- Applied the bridge rule to `TRandomUpdateTick$class` and got a clean negative: no jar in the pack references `TRandomUpdateTick` in any form, and the frozen baseline contains exactly the eight `$class` helpers the inventory lists. No bridge was written, following the `IDWriter` precedent. The accepted cost is a recompiled-Scala-consumer break with no shipping consumer.
|
||||
- Found that `IMicroMaterialRender` is implemented for every part solely by `TMultiPart`'s Scala-style `world`/`x`/`y`/`z`/`getRenderBounds` accessors. Renaming any of them to a bean accessor would silently unimplement the interface, so the names are now pinned by a characterization test rather than left to reviewer attention.
|
||||
- Established that for interfaces carrying no implementation, the characterization is the shape: every member abstract and public, no superinterface, and the exact member set. That is enough to catch the two realistic failure modes, a stray default and a renamed accessor.
|
||||
- Noted that `IRedstonePart.scala` is misleadingly named and is not a marker-trait file. It carries six traits plus `RedstoneInteractions`, whose `MODULE$` is load-bearing, and was moved out of the low-risk group.
|
||||
- Noted a documentation gap, not addressed here: this log was last appended for the `TItemMultiPart` port. The eight ports between it and this entry — `TEdgePart`, `Saw`, both registries, `TileMultipart`, `TMultiPart`, `TickScheduler` and `BlockMultipart` — are recorded in `JAVA_MIGRATION_DIVERGENCES.md` but never made it here, so the "Current status" section above should be read with that in mind.
|
||||
@@ -846,3 +846,54 @@ nested inner closures, are removed. The Java loops produce no replacement classe
|
||||
- Almost everything else in this class needs a player, a ray trace or a renderer. Block breaking, selection boxes,
|
||||
collision, pick block, activation, particles and light are all on the manual checklist and are the reason that
|
||||
checklist exists.
|
||||
|
||||
## 2026-08-15 — Marker interface Java port
|
||||
|
||||
Six interfaces that carry no implementation: `TSlottedPart`, `IRandomDisplayTick`, `INeighborTileChange`,
|
||||
`TRandomUpdateTick`, `ISidedHollowConnect` and `IMicroMaterialRender`.
|
||||
|
||||
### Observable behavior
|
||||
|
||||
No known divergence. Five of the six were already pure abstract interfaces in bytecode with no `$class` helper, so
|
||||
their conversion is a source-language change with no bytecode consequence at all. The sixth is discussed below.
|
||||
|
||||
### Supported JVM API
|
||||
|
||||
All six are member-identical and descriptor-identical to the reference. Every implementor and mixin was diffed as
|
||||
well — `Microblock`, `RedstoneTorchPart`, `TorchPart`, `HollowMicroblock`, `TSlottedTile`, `TTileChangeTile` and
|
||||
`TRandomDisplayTickTile` are unchanged member for member, so no concrete class gained or lost a forwarder.
|
||||
|
||||
`TSlottedPart` and `TRandomUpdateTick` extended `TMultiPart` in Scala source. A trait extending a class is a bare
|
||||
interface in bytecode, carrying none of the class's members, so the Java interfaces are bare too and match the
|
||||
reference exactly. This is the same pattern as `JIconHitEffects` and `TileMultipartClient`; the port makes it explicit
|
||||
in the source rather than changing it.
|
||||
|
||||
`IMicroMaterialRender` keeps the Scala accessor names `world`, `x`, `y` and `z`. `TMultiPart` declares all five
|
||||
members under exactly these names, which is what implements the interface for every part. Renaming any of them to a
|
||||
bean accessor would silently unimplement it, so the names are pinned by a characterization test.
|
||||
|
||||
### Removed API
|
||||
|
||||
`TRandomUpdateTick$class`, with its `onWorldJoin` and `$init$` statics. It is the only class removed from the jar by
|
||||
this port; the full class list is otherwise identical.
|
||||
|
||||
`onWorldJoin` **cannot** become an interface default. `TMultiPart` declares `onWorldJoin`, and a superclass method
|
||||
always beats an interface default on the JVM, so the default would never run and the failure would be silent. It stays
|
||||
abstract, and each implementor declares it and calls `TickScheduler.loadRandomTick` itself, which is what
|
||||
`RedstoneTorchPart` — the only implementor in this codebase — already did. The Scaladoc already carried this warning
|
||||
for Java implementors; it now applies to Scala implementors too.
|
||||
|
||||
No jar in the pack references `TRandomUpdateTick` in any form, and the frozen baseline contains exactly the eight
|
||||
`$class` helpers listed in the inventory, which does not include this one. Per the `IDWriter` precedent, no
|
||||
compatibility bridge was written. The consequence to accept: a Scala class mixing in `TRandomUpdateTick` and relying
|
||||
on the trait's `onWorldJoin` must now declare it, or it will resolve to `TMultiPart`'s no-op and never register for
|
||||
random ticks. This is a recompiled-Scala-consumer break with no shipping consumer.
|
||||
|
||||
### Validation
|
||||
|
||||
- Complete plain-JVM suite: 99 tests, 0 failures, 0 errors.
|
||||
- Clean `spotlessApply checkstyleTest build`: passing.
|
||||
- Java 8 Forge dedicated-server suite: 5 tests, 0 failures, 0 errors.
|
||||
- Public members, descriptors and the full emitted class list diffed against a reference jar built at `3e68e67`.
|
||||
- Random tick firing over time still needs a world and stays on the manual checklist, as does torch display-tick
|
||||
rendering and hollow-cover connection sizing.
|
||||
@@ -10,7 +10,7 @@ what breaks, and what is left. The other documents hold the reasoning.
|
||||
| `JAVA_MIGRATION_DIVERGENCES.md` | Every intentional difference from the reference, one entry per port |
|
||||
| `JAVA_MIGRATION_MANUAL_CHECKS.md` | What no automated test can cover, and must be checked by hand in a client |
|
||||
|
||||
Branch: `algent/java`. Base: `master`. 39 commits so far.
|
||||
Branch: `algent/java`. Base: `master`. 41 commits so far.
|
||||
|
||||
## The one rule that matters
|
||||
|
||||
@@ -56,7 +56,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: **90 plain-JVM tests, 5 Forge server tests, all passing.**
|
||||
Current baseline: **99 plain-JVM tests, 5 Forge server tests, all passing.**
|
||||
|
||||
### ABI diff against the reference
|
||||
|
||||
@@ -112,8 +112,8 @@ frozen bytes, so recompiling against the port would defeat the point.
|
||||
**Default methods vs superclass.** Emit a Java `default` only when **no class in an implementor's superclass chain
|
||||
declares the same member**. A superclass method always beats an interface default on the JVM, and the failure is
|
||||
silent. `TFacePart`, `TEdgePart`, `Saw`, `JIconHitEffects.getBreakingIcon` and `IMicroMaterial`'s three members passed
|
||||
this test. `TCuboidPart`, `TNormalOcclusion`, `TIconHitEffects` and `TItemMultiPart.onItemUse` failed it and had to
|
||||
stay abstract with explicit forwarders in implementors.
|
||||
this test. `TCuboidPart`, `TNormalOcclusion`, `TIconHitEffects`, `TItemMultiPart.onItemUse` and
|
||||
`TRandomUpdateTick.onWorldJoin` failed it and had to stay abstract with explicit forwarders in implementors.
|
||||
|
||||
**Audit existing `super` call sites, not just missing overrides.** `PostMicroblock` already overrode `occlusionTest`
|
||||
and ended with `super.occlusionTest(npart)`, which used to route through the trait. After the port it reached the
|
||||
@@ -148,13 +148,18 @@ All eight load-bearing `$class` helpers from the inventory, both registries, and
|
||||
`TFacePart`, the `TIconHitEffects` unit, `TItemMultiPart`/`JItemMultiPart`, `TEdgePart`, `Saw`, `MicroMaterialRegistry`,
|
||||
`MultiPartRegistry`, `TileMultipart`, `TMultiPart`, `TickScheduler`, `BlockMultipart`.
|
||||
|
||||
56 Java files, 61 Scala files, ~6,815 Scala lines left.
|
||||
Plus the six marker interfaces: `TSlottedPart`, `IRandomDisplayTick`, `INeighborTileChange`, `TRandomUpdateTick`,
|
||||
`ISidedHollowConnect`, `IMicroMaterialRender`.
|
||||
|
||||
62 Java files, 55 Scala files, ~6,733 Scala lines left (non-blank; that is the metric this figure has always used).
|
||||
|
||||
## What is left, and in what order
|
||||
|
||||
**Low risk, good next steps.** `TileCache`, `MultipartHelper`, `PacketScheduler`, `ControlKeyModifier`, the small
|
||||
marker traits (`TSlottedPart`, `IRedstonePart`, `INeighborTileChange`, `IRandomDisplayTick`, `TRandomUpdateTick`,
|
||||
`ISidedHollowConnect`, `IMicroMaterialRender`), and the two `package.scala` objects.
|
||||
**Low risk, good next steps.** `TileCache`, `MultipartHelper`, `PacketScheduler`, `ControlKeyModifier`, and the two
|
||||
`package.scala` objects.
|
||||
|
||||
`IRedstonePart.scala` is misleadingly named and is **not** a marker-trait file. It holds six traits plus
|
||||
`RedstoneInteractions`, whose `MODULE$` is load-bearing, so it is its own piece of work at medium risk.
|
||||
|
||||
**Medium.** The `handler` packages on both sides, `MultipartRenderer`, `MicroRecipe`, `ItemMicroPart`,
|
||||
`MicroblockPlacement`, `PlacementGrids`, `BlockMicroMaterial`, `MissingMicroMaterial`, `ConfigContent`,
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package codechicken.microblock;
|
||||
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import codechicken.lib.vec.Cuboid6;
|
||||
|
||||
/**
|
||||
* Wrapper class blocks/tiles/parts wanting to use the micro material system to render part of their models.
|
||||
* <p>
|
||||
* The accessors keep their Scala names because TMultiPart already declares world/x/y/z/getRenderBounds under exactly
|
||||
* these names, which is what implements this interface for every part.
|
||||
*/
|
||||
public interface IMicroMaterialRender {
|
||||
|
||||
/** May be null for inventory rendering. */
|
||||
World world();
|
||||
|
||||
int x();
|
||||
|
||||
int y();
|
||||
|
||||
int z();
|
||||
|
||||
/** Return the bounds of the part for texture mapping side decals like grass. */
|
||||
Cuboid6 getRenderBounds();
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package codechicken.microblock
|
||||
|
||||
import codechicken.lib.vec.Cuboid6
|
||||
import net.minecraft.world.World
|
||||
|
||||
/** Wrapper class blocks/tiles/parts wanting to use the micro material system to
|
||||
* render part of their models.
|
||||
*/
|
||||
trait IMicroMaterialRender {
|
||||
|
||||
/** May be null for inventory rendering.
|
||||
*/
|
||||
def world: World
|
||||
def x: Int
|
||||
def y: Int
|
||||
def z: Int
|
||||
|
||||
/** Return the bounds of the part for texture mapping side decals like grass
|
||||
*/
|
||||
def getRenderBounds: Cuboid6
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package codechicken.microblock;
|
||||
|
||||
/**
|
||||
* Implement this on center parts that connect through hollow covers to have the covers render their hollows to fit the
|
||||
* connection size.
|
||||
*/
|
||||
public interface ISidedHollowConnect {
|
||||
|
||||
/**
|
||||
* @param side The side of the block on which the cover resides
|
||||
* @return The size (width and height) of the connection in pixels. Must be less than 12 and more than 0
|
||||
*/
|
||||
int getHollowSize(int side);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package codechicken.microblock
|
||||
|
||||
/** Implement this on center parts that connect through hollow covers to have
|
||||
* the covers render their hollows to fit the connection size
|
||||
*/
|
||||
trait ISidedHollowConnect {
|
||||
|
||||
/** @param side
|
||||
* The side of the block on which the cover resides
|
||||
* @return
|
||||
* The size (width and height) of the connection in pixels. Must be be less
|
||||
* than 12 and more than 0
|
||||
*/
|
||||
def getHollowSize(side: Int): Int
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package codechicken.multipart;
|
||||
|
||||
/**
|
||||
* Mixin interface for parts that want to be notified of neighbor tile change events (comparators or inventory
|
||||
* maintainers).
|
||||
*/
|
||||
public interface INeighborTileChange {
|
||||
|
||||
/** Returns whether this part needs calls for tile changes through one solid block. */
|
||||
boolean weakTileChanges();
|
||||
|
||||
/** Callback for neighbor tile changes, from same function in Block. */
|
||||
void onNeighborTileChanged(int side, boolean weak);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package codechicken.multipart
|
||||
|
||||
/** Mixin interface for parts that want to be notified of neighbor tile change
|
||||
* events (comparators or inventory maintainers)
|
||||
*/
|
||||
trait INeighborTileChange {
|
||||
|
||||
/** Returns whether this part needs calls for tile changes through one solid
|
||||
* block
|
||||
*/
|
||||
def weakTileChanges(): Boolean
|
||||
|
||||
/** Callback for neighbor tile changes, from same function in Block
|
||||
*/
|
||||
def onNeighborTileChanged(side: Int, weak: Boolean)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package codechicken.multipart;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Interface for parts that need random display ticks (torches).
|
||||
* <p>
|
||||
* Marker interface for TRandomDisplayTickTile.
|
||||
*/
|
||||
public interface IRandomDisplayTick {
|
||||
|
||||
/** Called on a random display tick. */
|
||||
void randomDisplayTick(Random random);
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package codechicken.multipart
|
||||
|
||||
import java.util.Random
|
||||
|
||||
/** Interface for parts that need random display ticks (torches)
|
||||
*
|
||||
* Marker interface for TRandomDisplayTickTile
|
||||
*/
|
||||
trait IRandomDisplayTick {
|
||||
|
||||
/** Called on a random display tick.
|
||||
*/
|
||||
def randomDisplayTick(random: Random)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package codechicken.multipart;
|
||||
|
||||
/**
|
||||
* Interface for parts with random update ticks. Used in conjunction with TickScheduler.
|
||||
* <p>
|
||||
* The Scala trait extended TMultiPart, but a trait extending a class is a bare interface in bytecode, so implementors
|
||||
* must extend TMultiPart themselves.
|
||||
*/
|
||||
public interface TRandomUpdateTick {
|
||||
|
||||
/**
|
||||
* Called on random update. Random ticks are between 800 and 1600 ticks from their last scheduled/random tick.
|
||||
*/
|
||||
void randomUpdate();
|
||||
|
||||
/**
|
||||
* Registers the part for random ticking. This cannot be a default method: TMultiPart declares onWorldJoin, and a
|
||||
* superclass method always beats an interface default, so the default would never run. Every implementor must
|
||||
* declare it and call {@link TickScheduler#loadRandomTick(TRandomUpdateTick)} itself.
|
||||
*
|
||||
* <pre>
|
||||
*
|
||||
* @Override
|
||||
* public void onWorldJoin() {
|
||||
* TickScheduler.loadRandomTick(this);
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
void onWorldJoin();
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package codechicken.multipart
|
||||
|
||||
/** Interface for parts with random update ticks. Used in conjuction with
|
||||
* TickScheduler
|
||||
*/
|
||||
trait TRandomUpdateTick extends TMultiPart {
|
||||
|
||||
/** Called on random update. Random ticks are between 800 and 1600 ticks from
|
||||
* their last scheduled/random tick
|
||||
*/
|
||||
def randomUpdate()
|
||||
|
||||
/** If implementing interface in java, be sure to implement this method
|
||||
* yourself
|
||||
*/
|
||||
override def onWorldJoin() {
|
||||
TickScheduler.loadRandomTick(this)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package codechicken.multipart;
|
||||
|
||||
/**
|
||||
* Interface for parts that fill a slot based configuration as defined in PartMap. If this is implemented, calling
|
||||
* partMap(slot) on the host tile will return this part if the corresponding bit in the slotMask is set.
|
||||
* <p>
|
||||
* Marker interface for TSlottedTile.
|
||||
* <p>
|
||||
* The Scala trait extended TMultiPart, but a trait extending a class is a bare interface in bytecode, so implementors
|
||||
* must extend TMultiPart themselves.
|
||||
*/
|
||||
public interface TSlottedPart {
|
||||
|
||||
/** A bitmask of slots that this part fills. Slot x is 1<<x. */
|
||||
int getSlotMask();
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package codechicken.multipart
|
||||
|
||||
/** Interface for parts that fill a slot based configuration as defined in
|
||||
* PartMap. If this is implemented, calling partMap(slot) on the host tile will
|
||||
* return this part if the corresponding bit in the slotMask is set
|
||||
*
|
||||
* Marker interface for TSlottedTile
|
||||
*/
|
||||
trait TSlottedPart extends TMultiPart {
|
||||
|
||||
/** a bitmask of slots that this part fills. slot x is 1<<x
|
||||
*/
|
||||
def getSlotMask: Int
|
||||
}
|
||||
Reference in new issue
Block a user