From f063cc94b44241794a058feaf4ae3225b0a00a2d Mon Sep 17 00:00:00 2001 From: Algent Date: Tue, 1 Sep 2026 00:31:27 +0200 Subject: [PATCH] refactor: port microblock proxy to java --- JAVA_MIGRATION.md | 9 + JAVA_MIGRATION_DIVERGENCES.md | 52 ++++++ JAVA_MIGRATION_HANDOFF.md | 29 +-- JAVA_MIGRATION_MANUAL_CHECKS.md | 1 + .../codechicken/microblock/ItemSaw.scala | 1 - .../microblock/handler/MicroblockProxy$.java | 8 + .../microblock/handler/MicroblockProxy.java | 98 ++++++++++ .../handler/MicroblockProxy_clientImpl.java | 60 +++++++ .../handler/MicroblockProxy_serverImpl.java | 167 ++++++++++++++++++ .../microblock/handler/proxies.scala | 145 --------------- 10 files changed, 413 insertions(+), 157 deletions(-) create mode 100644 src/main/scala/codechicken/microblock/handler/MicroblockProxy$.java create mode 100644 src/main/scala/codechicken/microblock/handler/MicroblockProxy.java create mode 100644 src/main/scala/codechicken/microblock/handler/MicroblockProxy_clientImpl.java create mode 100644 src/main/scala/codechicken/microblock/handler/MicroblockProxy_serverImpl.java delete mode 100644 src/main/scala/codechicken/microblock/handler/proxies.scala diff --git a/JAVA_MIGRATION.md b/JAVA_MIGRATION.md index b74f7ea..a14ba82 100644 --- a/JAVA_MIGRATION.md +++ b/JAVA_MIGRATION.md @@ -1034,3 +1034,12 @@ generated-trait compatibility work. Scala-runtime removal remains a later projec the inherited server method after Forge strips the client override and its static forwarder. - All 187 plain-JVM and all 96 Java 8 Forge dedicated-server tests pass. Startup registration and two bit-packing helpers are not a meaningful focused performance target; `microblock/handler/proxies.scala` is next. +- Characterized `MicroblockProxy` against untouched Scala. Four plain-JVM cases freeze its four-type hierarchy, + complete facade/companion ABI, eight mutable server fields, protected Scala saw list and exact lazy-renderer shape. + Two Forge cases freeze side stripping, inherited server lifecycle resolution, item/ore/recipe registration and saw + order. +- Ported the hierarchy and facade to four Java types. The Scala `MutableList` remains for binary compatibility, the + client renderer retains its field-only side annotation and lazy bitmap, and the saw-renderer closure becomes a + direct iterator loop. +- All 191 plain-JVM and all 98 Java 8 Forge dedicated-server tests pass. This startup-only proxy is not a meaningful + focused performance target; `multipart/handler/packethandlers.scala` is the last Scala handler unit and is next. diff --git a/JAVA_MIGRATION_DIVERGENCES.md b/JAVA_MIGRATION_DIVERGENCES.md index 7b1c8ad..c1186bc 100644 --- a/JAVA_MIGRATION_DIVERGENCES.md +++ b/JAVA_MIGRATION_DIVERGENCES.md @@ -2205,3 +2205,55 @@ four proxy classes. An unused Scala wildcard import was removed from the packet- public names/descriptors and client-only method annotations. - Client renderer, packet, key-binding and generated-tile renderer registration require a client and remain on the manual checklist. + +## 2026-09-01 — MicroblockProxy Java port + +The ForgeMicroblock server/client proxy hierarchy is now Java, including its static facade and load-bearing companion +singleton. Iguana Tweaks' direct `MicroblockProxy$.MODULE$` item/saw reads and static `MicroblockProxy.sawDiamond()` / +`createSaw(...)` calls retain their original owners and descriptors. + +### Observable behavior + +No known runtime divergence. Pre-init still registers the microblock item, stone/iron/diamond saws and stone rod in +the same sequence, preserves saw-list order, registers `rodStone` and the event-handler singleton, and reads the same +`useSawIcons` configuration tag. Each saw still uses the named braced config tag, strength, unlocalized name and +texture name. + +Init still adds `MicroRecipe$.MODULE$` and, outside Dreamcraft, the four-stone-rod recipe plus all three ore-dictionary +saw recipes. Forge subsequently sorts the global recipe list, so only recipe identity/content and the proxy's own saw +list have meaningful stable order. Post-init still calculates maximum cutting strength, assigns the registry handshake +handler and conditionally registers Postea transformers. + +Client init still registers the same renderer for all three saws. Client post-init still loads material icons, +registers the microblock-item renderer and packet handler, and conditionally installs `AngelicaCompat`. The +`RenderBlocks` instance remains lazy, synchronized and published through the same volatile bitmap. + +### ABI, annotations and source compatibility + +The four callable types remain `MicroblockProxy_serverImpl`, `MicroblockProxy_clientImpl`, `MicroblockProxy`, and +`MicroblockProxy$`, with unchanged inheritance and final flags. The server implementation retains its exact 21 public +methods and eight private fields. Its source-protected `saws` property remains a Scala `MutableList` with public +JVM accessors, preserving compiled callers. The facade retains its exact 20 static methods, deliberately excluding the +protected saw-list accessors, and the companion still declares only `MODULE$`. + +The client implementation retains its private `renderBlocks` field, volatile `bitmap$0`, private +`renderBlocks$lzycompute()` helper and three public methods. As in the Scala reference, only the renderer field is +`@SideOnly(CLIENT)`; the getter and lazy helper are not. Only `init` and `postInit` are client-only on the client +implementation and facade. Consequently dedicated-server reflection across all client methods still encounters the +stripped `RenderBlocks` type, while direct companion lifecycle dispatch resolves to the inherited server methods. + +Accepted classfile-only differences are removed Scala signature/marker attributes, a private facade constructor, one +private Java recipe-list helper, and removal of the unreferenced +`MicroblockProxy_clientImpl$$anonfun$init$1` iteration closure. The downstream inventory contains no reference to that +private implementation class. + +### Validation + +- `MicroblockProxyCharacterizationTest`: 4 plain-JVM tests, unchanged from the Scala baseline. +- `MicroblockProxyFunctionalTest`: 2 Forge tests, unchanged from the Scala baseline. +- Complete plain-JVM suite: 191 tests, 0 failures, 0 errors. +- Java 8 Forge dedicated-server suite: 98 tests, 0 failures, 0 errors. +- A clean build and raw `javap` comparison confirm identical retained class hierarchy, fields, callable public + names/descriptors and side annotations. The sole class-list removal is the private closure above. +- Item-renderer registration, material icon loading, client packet registration and Angelica integration require a + client and remain on the manual checklist. diff --git a/JAVA_MIGRATION_HANDOFF.md b/JAVA_MIGRATION_HANDOFF.md index 39a90d0..ecc8de9 100644 --- a/JAVA_MIGRATION_HANDOFF.md +++ b/JAVA_MIGRATION_HANDOFF.md @@ -12,8 +12,8 @@ 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`. 109 commits including the API-cleanup plan and separate characterization and -port commits for `MultipartProxy`. +Branch: `algent/java`. Base: `master`. 111 commits including the API-cleanup plan and separate characterization and +port commits for both proxy hierarchies. ## The one rule that matters @@ -67,7 +67,7 @@ awk -F'"' '/ iterator = saws().iterator(); + while (iterator.hasNext()) { + MinecraftForgeClient.registerItemRenderer(iterator.next(), ItemSawRenderer$.MODULE$); + } + } +} diff --git a/src/main/scala/codechicken/microblock/handler/MicroblockProxy_serverImpl.java b/src/main/scala/codechicken/microblock/handler/MicroblockProxy_serverImpl.java new file mode 100644 index 0000000..8ef5550 --- /dev/null +++ b/src/main/scala/codechicken/microblock/handler/MicroblockProxy_serverImpl.java @@ -0,0 +1,167 @@ +package codechicken.microblock.handler; + +import java.util.List; + +import net.minecraft.init.Blocks; +import net.minecraft.init.Items; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.CraftingManager; +import net.minecraft.item.crafting.IRecipe; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.oredict.OreDictionary; +import net.minecraftforge.oredict.ShapedOreRecipe; + +import org.apache.logging.log4j.Logger; + +import codechicken.lib.config.ConfigFile; +import codechicken.lib.packet.PacketCustom; +import codechicken.microblock.ItemMicroPart; +import codechicken.microblock.ItemSaw; +import codechicken.microblock.MicroMaterialRegistry; +import codechicken.microblock.MicroRecipe$; +import codechicken.microblock.compat.PosteaCompat; +import codechicken.multipart.handler.MultipartProxy; +import cpw.mods.fml.common.Loader; +import cpw.mods.fml.common.registry.GameRegistry; +import scala.collection.mutable.MutableList; + +public class MicroblockProxy_serverImpl { + + private Logger logger; + private ItemMicroPart itemMicro; + private Item sawStone; + private Item sawIron; + private Item sawDiamond; + private Item stoneRod; + private boolean useSawIcons; + private MutableList saws = new MutableList<>(); + + public Logger logger() { + return logger; + } + + public void logger_$eq(Logger logger) { + this.logger = logger; + } + + public ItemMicroPart itemMicro() { + return itemMicro; + } + + public void itemMicro_$eq(ItemMicroPart itemMicro) { + this.itemMicro = itemMicro; + } + + public Item sawStone() { + return sawStone; + } + + public void sawStone_$eq(Item sawStone) { + this.sawStone = sawStone; + } + + public Item sawIron() { + return sawIron; + } + + public void sawIron_$eq(Item sawIron) { + this.sawIron = sawIron; + } + + public Item sawDiamond() { + return sawDiamond; + } + + public void sawDiamond_$eq(Item sawDiamond) { + this.sawDiamond = sawDiamond; + } + + public Item stoneRod() { + return stoneRod; + } + + public void stoneRod_$eq(Item stoneRod) { + this.stoneRod = stoneRod; + } + + public boolean useSawIcons() { + return useSawIcons; + } + + public void useSawIcons_$eq(boolean useSawIcons) { + this.useSawIcons = useSawIcons; + } + + public void preInit(Logger logger) { + this.logger = logger; + itemMicro = new ItemMicroPart(); + GameRegistry.registerItem(itemMicro, "microblock"); + sawStone = createSaw(MultipartProxy.config(), "sawStone", 1); + sawIron = createSaw(MultipartProxy.config(), "sawIron", 2); + sawDiamond = createSaw(MultipartProxy.config(), "sawDiamond", 3); + stoneRod = new Item().setUnlocalizedName("microblock:stoneRod").setTextureName("microblock:stoneRod"); + GameRegistry.registerItem(stoneRod, "stoneRod"); + + OreDictionary.registerOre("rodStone", stoneRod); + MinecraftForge.EVENT_BUS.register(MicroblockEventHandler$.MODULE$); + + useSawIcons = MultipartProxy.config().getTag("useSawIcons") + .setComment("Set to true to use mc style icons for the saw instead of the 3D model") + .getBooleanValue(false); + } + + public MutableList saws() { + return saws; + } + + public void saws_$eq(MutableList saws) { + this.saws = saws; + } + + public Item createSaw(ConfigFile config, String name, int strength) { + Item saw = new ItemSaw(config.getTag(name).useBraces(), strength).setUnlocalizedName("microblock:" + name) + .setTextureName("microblock:" + name); + GameRegistry.registerItem(saw, name); + saws.$plus$eq(saw); + return saw; + } + + public void addSawRecipe(Item saw, Item blade) { + recipes().add( + new ShapedOreRecipe( + new ItemStack(saw), + "srr", + "sbr", + Character.valueOf('s'), + "stickWood", + Character.valueOf('r'), + "rodStone", + Character.valueOf('b'), + blade)); + } + + public void init() { + recipes().add(MicroRecipe$.MODULE$); + if (!Loader.isModLoaded("dreamcraft")) { + CraftingManager.getInstance() + .addRecipe(new ItemStack(stoneRod, 4), "s", "s", Character.valueOf('s'), Blocks.stone); + addSawRecipe(sawStone, Items.flint); + addSawRecipe(sawIron, Items.iron_ingot); + addSawRecipe(sawDiamond, Items.diamond); + } + } + + public void postInit() { + MicroMaterialRegistry.calcMaxCuttingStrength(); + PacketCustom.assignHandshakeHandler(MicroblockSPH.registryChannel(), MicroblockSPH$.MODULE$); + if (Loader.isModLoaded("postea")) { + PosteaCompat.registerTransformers(); + } + } + + @SuppressWarnings("unchecked") + private static List recipes() { + return CraftingManager.getInstance().getRecipeList(); + } +} diff --git a/src/main/scala/codechicken/microblock/handler/proxies.scala b/src/main/scala/codechicken/microblock/handler/proxies.scala deleted file mode 100644 index 7b926d5..0000000 --- a/src/main/scala/codechicken/microblock/handler/proxies.scala +++ /dev/null @@ -1,145 +0,0 @@ -package codechicken.microblock.handler - -import net.minecraftforge.common.MinecraftForge -import codechicken.multipart.handler.MultipartProxy._ -import codechicken.microblock._ -import net.minecraftforge.client.MinecraftForgeClient -import cpw.mods.fml.relauncher.SideOnly -import cpw.mods.fml.relauncher.Side -import net.minecraft.item.crafting.CraftingManager -import net.minecraft.item.Item -import net.minecraft.item.crafting.IRecipe - -import java.util.{List => JList} -import codechicken.lib.config.ConfigFile -import net.minecraftforge.oredict.OreDictionary -import net.minecraft.item.ItemStack -import net.minecraftforge.oredict.ShapedOreRecipe -import codechicken.lib.packet.PacketCustom -import codechicken.microblock.compat.PosteaCompat -import cpw.mods.fml.common.Loader -import cpw.mods.fml.common.registry.GameRegistry -import net.minecraft.client.renderer.RenderBlocks -import net.minecraft.init.{Blocks, Items} -import org.apache.logging.log4j.Logger - -import scala.collection.mutable - -class MicroblockProxy_serverImpl { - var logger: Logger = _ - - var itemMicro: ItemMicroPart = _ - var sawStone: Item = _ - var sawIron: Item = _ - var sawDiamond: Item = _ - var stoneRod: Item = _ - - var useSawIcons: Boolean = _ - - def preInit(logger: Logger) { - this.logger = logger - itemMicro = new ItemMicroPart - GameRegistry.registerItem(itemMicro, "microblock") - sawStone = createSaw(config, "sawStone", 1) - sawIron = createSaw(config, "sawIron", 2) - sawDiamond = createSaw(config, "sawDiamond", 3) - stoneRod = new Item() - .setUnlocalizedName("microblock:stoneRod") - .setTextureName("microblock:stoneRod") - GameRegistry.registerItem(stoneRod, "stoneRod") - - OreDictionary.registerOre("rodStone", stoneRod) - MinecraftForge.EVENT_BUS.register(MicroblockEventHandler$.MODULE$) - - useSawIcons = config - .getTag("useSawIcons") - .setComment( - "Set to true to use mc style icons for the saw instead of the 3D model" - ) - .getBooleanValue(false) - } - - protected var saws = mutable.MutableList[Item]() - def createSaw(config: ConfigFile, name: String, strength: Int) = { - val saw = new ItemSaw(config.getTag(name).useBraces(), strength) - .setUnlocalizedName("microblock:" + name) - .setTextureName("microblock:" + name) - GameRegistry.registerItem(saw, name) - saws += saw - saw - } - - def addSawRecipe(saw: Item, blade: Item) { - CraftingManager.getInstance.getRecipeList - .asInstanceOf[JList[IRecipe]] - .add( - new ShapedOreRecipe( - new ItemStack(saw), - "srr", - "sbr", - 's': Character, - "stickWood", - 'r': Character, - "rodStone", - 'b': Character, - blade - ) - ) - } - - def init() { - CraftingManager.getInstance.getRecipeList - .asInstanceOf[JList[IRecipe]] - .add(MicroRecipe$.MODULE$) - if (!Loader.isModLoaded("dreamcraft")) { - CraftingManager.getInstance.addRecipe( - new ItemStack(stoneRod, 4), - "s", - "s", - 's': Character, - Blocks.stone - ) - addSawRecipe(sawStone, Items.flint) - addSawRecipe(sawIron, Items.iron_ingot) - addSawRecipe(sawDiamond, Items.diamond) - } - } - - def postInit() { - MicroMaterialRegistry.calcMaxCuttingStrength() - PacketCustom.assignHandshakeHandler( - MicroblockSPH.registryChannel(), - MicroblockSPH$.MODULE$ - ) - if (Loader.isModLoaded("postea")) { - PosteaCompat.registerTransformers() - } - } -} - -class MicroblockProxy_clientImpl extends MicroblockProxy_serverImpl { - @SideOnly(Side.CLIENT) - lazy val renderBlocks = new RenderBlocks - - @SideOnly(Side.CLIENT) - override def postInit() { - super.postInit() - MicroMaterialRegistry.loadIcons() - MinecraftForgeClient.registerItemRenderer(itemMicro, ItemMicroPartRenderer) - PacketCustom.assignHandler( - MicroblockCPH.registryChannel(), - MicroblockCPH$.MODULE$ - ) - - if (Loader.isModLoaded("angelica")) - MicroblockMod.angelicaCompat_$eq(new AngelicaCompat) - } - - @SideOnly(Side.CLIENT) - override def init() { - super.init() - saws.foreach(MinecraftForgeClient.registerItemRenderer(_, ItemSawRenderer)) - } -} - -object MicroblockProxy extends MicroblockProxy_clientImpl {}