refactor: port multipart generator to Java

This commit is contained in:
Algent committed 2026-09-02 15:31:07 +02:00
1 parent e6af4251ae
commit 09670a09ce
8 files changed
+345 -254

No files matched your search

+10
View File
@@ -1211,3 +1211,13 @@ generated-trait compatibility work. Scala-runtime removal remains a later projec
- A clean build passes all 260 plain-JVM tests and the Java 8 Forge server passes all 111 tests. The same three runtime
classes and every callable public descriptor match the reference, including ProjectRed's companion registration
and GuideNH's exact companion `create` method. `multipart/MultipartGenerator.scala` is next.
- Characterized `MultipartGenerator` against untouched Scala. Two plain-JVM cases freeze both public surfaces,
private Scala-map descriptors and companion call opcodes. Five Forge cases freeze side-specific hierarchy caches,
duplicate/failed registration, scratch clearing, class snapshots/reuse, tile upgrades/downgrades, vanilla-block
conversion and a precompiled Scala consumer exercising companion generation and pass-through registration.
- Ported the facade and companion directly to Java. All five Scala maps, both compiler-generated public accessors,
the companion-only `generateCompositeTile` descriptor and side-safe proxy callback remain. Direct iteration removes
six private closures; the ASM factory's sole source adjustment explicitly names `MultipartGenerator$.MODULE$`.
- A clean build passes all 262 plain-JVM tests and the Java 8 Forge server passes all 116 tests. Both supported public
surfaces match the reference. `multipart/asm/ScratchBitSet.scala` is the next isolated support target; the generated
microblock traits still require the documented abstract-Java-mixin and side-only-member prerequisites.
+49
View File
@@ -3044,3 +3044,52 @@ None. The clean reference and port jars contain the same three `MicroblockGenera
initialization and dispatch of the external Scala microblock trait.
- Raw `javap` comparison confirms the exact three-class list and identical callable public names/descriptors.
- The existing ProjectRed illuminated-microblock row remains the full-pack/client manual checkpoint.
## 2026-09-02 — Multipart generator Java port
The `MultipartGenerator` facade and companion are now Java. `MultipartMixinFactory`, `ASMMixinFactory`,
`ScratchBitSet` and the compiler remain Scala; the factory callback only changes its source spelling to
`MultipartGenerator$.MODULE$.registerTileClass`.
### Observable behavior
No known runtime divergence. Trait discovery retains interface/class/superclass traversal order, distinct trait
names, separate client/server caches and the original lack of cache invalidation after late registration. Duplicate
registrations keep the first mapping. A failed trait lookup still leaves its side mapping inserted, and null traits
still exclude that side. Construction clears the same thread-local scratch set and adds the client bit only on the
client path.
Registered tile classes still receive an independent bit-set snapshot and the proxy's virtual companion callback.
Composite construction reuses only an exact registered trait set; unregistered tile classes retain the original
missing-map-entry failure. Vanilla conversion and live tile upgrades/downgrades preserve packet/callback order,
invalidation, part ordering, rebinding and copying. The removal comparison retains Scala's value equality.
### ABI and reflection compatibility
The facade's seven methods and companion's fifteen methods retain their names and descriptors. The five private
Scala mutable-map fields, the public mangled map/cache accessors and the `ScratchBitSet` interface remain. GuideNH
and Schematica still reach `generateCompositeTile(TileEntity, scala.collection.Iterable, boolean)` only through
`MultipartGenerator$.MODULE$`; no new static generation entry point is introduced. Shipping static and companion
pass-through registration calls also remain exact.
A frozen Scala 2.11.5 consumer compiled against the untouched reference calls both companion generation and default
pass-through registration, then exercises a generated forwarding method under Forge.
Accepted classfile-only differences are the private Java facade constructor, ordinary Java singleton initialization
and removed Scala marker attributes. As with `MicroblockGenerator`, the private scratch `ThreadLocal` loses
`ACC_FINAL` so the public Scala-trait setter remains replaceable. Unreferenced private map getters are gone.
### Compiler artifacts
Six private Scala closure classes disappear: the `traitsForPart` closure and its four hierarchy/mapping helpers, plus
the `setTraits` traversal closure. The downstream inventory contains no references to them. Hierarchy discovery uses
a `LinkedHashSet`; part iteration uses the existing Scala iterator without extra collection adapters.
### Validation
- `MultipartGeneratorCharacterizationTest`: 2 plain-JVM tests, unchanged from the Scala baseline.
- `MultipartGeneratorFunctionalTest`: 5 Forge tests, unchanged from the Scala baseline.
- Clean complete plain-JVM suite: 262 tests, 0 failures, 0 errors.
- Java 8 Forge dedicated-server suite: 116 tests, 0 failures, 0 errors.
- Raw `javap` comparison confirms identical callable public names/descriptors and the expected six-closure removal.
- Real client conversion packets/render rebinding and GuideNH/Schematica previews remain manual checkpoints.
+20 -12
View File
@@ -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`. 148 commits including the API-cleanup plan and separate characterization and
port commits through the microblock generator.
Branch: `algent/java`. Base: `master`. 150 commits including the API-cleanup plan and separate characterization and
port commits through the multipart generator.
## The one rule that matters
@@ -67,7 +67,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: **260 plain-JVM tests and 111 Java 8 Forge dedicated-server tests passing.** The ignored local server
Current baseline: **262 plain-JVM tests and 116 Java 8 Forge dedicated-server tests passing.** The ignored local server
EULA is accepted in this checkout. GitHub Actions runs the same self-validating Forge suite in a dependent job after
the shared GTNH build; keep both jobs required.
@@ -183,8 +183,8 @@ proves the other branch did reach it. Use `LinkageError`, not the exact type —
`$class` bridge, but idiomatic and callable from Java.
**`private[multipart]` becomes public, not package-private.** It reaches `codechicken.multipart.handler` too, and Java
has no scope spanning a package and its siblings. `PacketScheduler.sendScheduled` hit this; `MultipartGenerator` and
`MicroblockGenerator` will too.
has no scope spanning a package and its siblings. `PacketScheduler.sendScheduled` and the generator companions keep
those methods public; do not add static forwarders for originally companion-only methods.
**A Forge `FakePlayer` unlocks the server half of player-dependent code.**
`FakePlayerFactory.getMinecraft(worldServer)` needs no connection and its world is not remote, which is the branch a
@@ -272,7 +272,7 @@ across the port. It is also where the two shim constraints in the gotchas list w
`rayTraceAll`'s index production is now characterized, closing the gap the read-path cleanup left: the index written
into `ExtendedMOP.data` is what `reduceMOP` hands back to every click, activate, harvest and pick block.
189 Java files, 15 Scala files, 2,807 Scala lines left (non-blank; that is the metric this figure has always used).
191 Java files, 14 Scala files, 2,588 Scala lines left (non-blank; that is the metric this figure has always used).
## What is left, and in what order
@@ -532,17 +532,25 @@ and clearing, and the load-bearing call opcodes. One Forge case covers the previ
`IGeneratedMaterial` callback through registered external Scala-trait initialization and dispatch. The runtime class
list and all callable public descriptors remain exact for ProjectRed and GuideNH.
**High.** `MultipartGenerator.scala` is next. Freeze its companion-only GuideNH entry point, all `private[multipart]`
members that must become public Java, trait registration maps, server/client exclusion flags, pass-through handling,
scratch-bit reuse, generated class selection and construction before choosing a split. Reuse the extensive existing
generated-tile fixtures and do not combine this with `MultipartMixinFactory` or the rest of ASM.
The multipart generator is now a Java facade and companion. Two plain-JVM cases freeze its exact public surfaces,
Scala-map fields and side-safe companion calls; five Forge cases freeze hierarchy caches, side exclusion, duplicate
and failed registration, scratch clearing, registered-class snapshots, generated-class reuse, live tile upgrades and
downgrades, vanilla torch conversion and a frozen Scala consumer. The generator compiler is unchanged: its callback
merely spells `MultipartGenerator$.MODULE$` explicitly. Six private Scala closures disappear.
**Next: `multipart/asm/ScratchBitSet.scala`.** Both implementors are now Java, so this is a small, isolated support
trait before the compiler work. Preserve its public mangled getter/setter and existing helper linkage, and freeze
per-thread isolation, lazy allocation, clearing and setter replacement. Do not combine it with `ASMMixinFactory`.
The remaining generated microblock traits still need the Phase 7 abstract-Java-mixin and side-only-member support
before their Java mixin inputs are safe; do not bypass those prerequisites by flattening their inheritance.
**Phase 5 is complete.** There are no Scala files left in `multipart/scalatraits/`. The client pair required the first
narrow generator relaxation: Java-trait parent linearization, explicit field-accessor recognition, and exclusion of
transient runtime caches from generated copying.
**Phase 6/7, last.** The retained generated microblock traits, `MultipartGenerator`, and all of `multipart/asm/`.
The ASM subsystem should be last; freeze generated-class fixtures before touching it.
**Phase 6/7, last.** The retained generated microblock traits and all of `multipart/asm/`.
Keep the ASM compiler last, apart from the prerequisite fixes needed by those traits; freeze generated-class fixtures
before changing it.
**Pre-merge cleanup is explicit in `JAVA_MIGRATION.md`.** It covers relocating eligible Java files out of
`src/main/scala`, reconsidering forced Scala compilation, refreshing the README, organizing the durable migration
+1
View File
@@ -45,6 +45,7 @@ below its table.
| [ ] | Run redstone across an edge microblock | Conduction matches the pre-port behavior | `TEdgePart.conductsRedstone` |
| [ ] | Cut materials with saws of different harvest levels | Only materials at or below the saw's strength can be cut | `Saw.getMaxCuttingStrength` |
| [ ] | Place the same microblock in survival and creative, including onto an existing multipart tile | Valid placements play the material sound; survival consumes the expected amount while creative does not | `ItemMicroPart.onItemUse` |
| [ ] | With a second client watching, convert a vanilla torch by adding a part, then add and remove parts requiring different tile interfaces | Both clients see each part exactly once, with no ghost block, stale render or missing part after tile replacement | `MultipartGenerator` conversion packet order and generated tile upgrade/downgrade |
## Downstream integration
@@ -0,0 +1,210 @@
package codechicken.multipart;
import java.util.BitSet;
import java.util.LinkedHashSet;
import java.util.Set;
import net.minecraft.network.play.server.S23PacketBlockChange;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import codechicken.lib.packet.PacketCustom;
import codechicken.lib.vec.BlockCoord;
import codechicken.multipart.asm.ASMImplicits;
import codechicken.multipart.asm.MultipartMixinFactory;
import codechicken.multipart.asm.ScratchBitSet;
import codechicken.multipart.asm.ScratchBitSet$class;
import codechicken.multipart.handler.MultipartProxy;
import codechicken.multipart.handler.MultipartProxy$;
import scala.Option;
import scala.Tuple2;
import scala.collection.Iterable;
import scala.collection.Iterator;
import scala.collection.immutable.Nil$;
import scala.collection.mutable.HashMap;
import scala.collection.mutable.Map;
public final class MultipartGenerator$ implements ScratchBitSet {
public static final MultipartGenerator$ MODULE$ = new MultipartGenerator$();
private final Map<Class<?>, BitSet> tileTraitMap = new HashMap<>();
private final Map<String, String> interfaceTraitMap_c = new HashMap<>();
private final Map<String, String> interfaceTraitMap_s = new HashMap<>();
private final Map<Class<?>, BitSet> partTraitMap_c = new HashMap<>();
private final Map<Class<?>, BitSet> partTraitMap_s = new HashMap<>();
private final int clientTraitId;
private ThreadLocal<BitSet> codechicken$multipart$asm$ScratchBitSet$$bitSets;
private MultipartGenerator$() {
ScratchBitSet$class.$init$(this);
clientTraitId = MultipartMixinFactory.registerTrait("codechicken.multipart.TileMultipartClient");
}
@Override
public ThreadLocal<BitSet> codechicken$multipart$asm$ScratchBitSet$$bitSets() {
return codechicken$multipart$asm$ScratchBitSet$$bitSets;
}
@Override
public void codechicken$multipart$asm$ScratchBitSet$_setter_$codechicken$multipart$asm$ScratchBitSet$$bitSets_$eq(
ThreadLocal value) {
codechicken$multipart$asm$ScratchBitSet$$bitSets = value;
}
@Override
public BitSet getBitSet() {
return ScratchBitSet$class.getBitSet(this);
}
@Override
public BitSet freshBitSet() {
return ScratchBitSet$class.freshBitSet(this);
}
public Map<String, String> codechicken$multipart$MultipartGenerator$$interfaceTraitMap(boolean client) {
return client ? interfaceTraitMap_c : interfaceTraitMap_s;
}
public BitSet codechicken$multipart$MultipartGenerator$$traitsForPart(TMultiPart part, boolean client) {
Map<Class<?>, BitSet> cache = client ? partTraitMap_c : partTraitMap_s;
Option<BitSet> cached = cache.get(part.getClass());
if (cached.isDefined()) return cached.get();
Set<String> traits = new LinkedHashSet<>();
collectTraits(part.getClass(), codechicken$multipart$MultipartGenerator$$interfaceTraitMap(client), traits);
BitSet bitset = new BitSet();
for (String trait : traits) bitset.set(MultipartMixinFactory.getId(trait));
cache.put(part.getClass(), bitset);
return bitset;
}
private void collectTraits(Class<?> type, Map<String, String> map, Set<String> traits) {
for (Class<?> iface : type.getInterfaces()) collectTraits(iface, map, traits);
Option<String> trait = map.get(ASMImplicits.nodeName(type.getName()));
if (trait.isDefined()) traits.add(trait.get());
if (type.getSuperclass() != null) collectTraits(type.getSuperclass(), map, traits);
}
private BitSet setTraits(TMultiPart part, boolean client) {
BitSet bitset = freshBitSet();
bitset.or(codechicken$multipart$MultipartGenerator$$traitsForPart(part, client));
if (client) bitset.set(clientTraitId);
return bitset;
}
private BitSet setTraits(Iterable<TMultiPart> parts, boolean client) {
BitSet bitset = freshBitSet();
for (Iterator<TMultiPart> it = parts.iterator(); it.hasNext();) {
bitset.or(codechicken$multipart$MultipartGenerator$$traitsForPart(it.next(), client));
}
if (client) bitset.set(clientTraitId);
return bitset;
}
/** Replaces the tile if the new part requires additional interfaces, then adds the part. */
public TileMultipart addPart(World world, BlockCoord pos, TMultiPart part) {
Tuple2<TileMultipart, Object> convertedTile = TileMultipart.getOrConvertTile2(world, pos);
TileMultipart tile = convertedTile._1();
boolean converted = (Boolean) convertedTile._2();
BitSet bitset = setTraits(part, world.isRemote);
TileMultipart next = tile;
if (next != null) {
if (converted) {
next.partList().apply(0).invalidateConvertedTile();
world.setBlock(pos.x, pos.y, pos.z, MultipartProxy.block(), 0, 0);
silentAddTile(world, pos, next);
PacketCustom.sendToChunk(
new S23PacketBlockChange(pos.x, pos.y, pos.z, world),
world,
pos.x >> 4,
pos.z >> 4);
next.partList().apply(0).onConverted();
next.writeAddPart(next.partList().apply(0));
}
BitSet tileTraits = tileTraitMap.apply(tile.getClass());
bitset.andNot(tileTraits);
if (!bitset.isEmpty()) {
bitset.or(tileTraits);
next = (TileMultipart) MultipartMixinFactory.construct(bitset, Nil$.MODULE$);
tile.setValid(false);
silentAddTile(world, pos, next);
next.from(tile);
}
} else {
world.setBlock(pos.x, pos.y, pos.z, MultipartProxy.block(), 0, 0);
next = (TileMultipart) MultipartMixinFactory.construct(bitset, Nil$.MODULE$);
silentAddTile(world, pos, next);
}
next.addPart_impl(part);
return next;
}
public void silentAddTile(World world, BlockCoord pos, TileEntity tile) {
Chunk chunk = world.getChunkFromBlockCoords(pos.x, pos.z);
if (chunk != null) chunk.func_150812_a(pos.x & 15, pos.y, pos.z & 15, tile);
}
/** Reuses the tile only when it has exactly the interfaces required by the parts. */
public TileMultipart generateCompositeTile(TileEntity tile, Iterable<TMultiPart> parts, boolean client) {
BitSet bitset = setTraits(parts, client);
if (tile instanceof TileMultipart && bitset.equals(tileTraitMap.apply(tile.getClass()))) {
return (TileMultipart) tile;
}
return (TileMultipart) MultipartMixinFactory.construct(bitset, Nil$.MODULE$);
}
/** Replaces a tile whose remaining parts no longer require all its interfaces. */
public TileMultipart partRemoved(TileMultipart tile) {
TileMultipart next = generateCompositeTile(tile, tile.partList(), tile.getWorldObj().isRemote);
if (!next.equals(tile)) {
tile.setValid(false);
silentAddTile(tile.getWorldObj(), new BlockCoord(tile), next);
next.from(tile);
next.notifyTileChange();
}
return next;
}
public void registerTrait(String marker, String trait) {
registerTrait(marker, trait, trait);
}
public void registerTrait(String marker, String clientTrait, String serverTrait) {
marker = ASMImplicits.nodeName(marker);
clientTrait = ASMImplicits.nodeName(clientTrait);
serverTrait = ASMImplicits.nodeName(serverTrait);
registerSide(interfaceTraitMap_c, marker, clientTrait);
registerSide(interfaceTraitMap_s, marker, serverTrait);
}
private void registerSide(Map<String, String> map, String marker, String trait) {
if (trait == null) return;
if (map.contains(marker)) {
MultipartProxy.logger().error("Trait already registered for " + marker);
} else {
map.put(marker, trait);
MultipartMixinFactory.registerTrait(trait);
}
}
public void registerPassThroughInterface(String name) {
registerPassThroughInterface(name, true, true);
}
public void registerPassThroughInterface(String name, boolean client, boolean server) {
String trait = MultipartMixinFactory.generatePassThroughTrait(name);
if (trait == null) return;
registerTrait(name, client ? trait : null, server ? trait : null);
}
public void registerTileClass(Class<? extends TileMultipart> type, BitSet traits) {
BitSet copy = new BitSet();
copy.or(traits);
tileTraitMap.put(type, copy);
MultipartProxy$.MODULE$.onTileClassBuilt(type);
}
}
@@ -0,0 +1,53 @@
package codechicken.multipart;
import java.util.BitSet;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.World;
import codechicken.lib.vec.BlockCoord;
/**
* Manages dynamic construction of multipart container tiles. Register a mixin against a part's marker interface to add
* tile-level logic or interfaces whenever that part is present. Generated classes can be inspected with the ASM debug
* configuration option.
*/
public final class MultipartGenerator {
private MultipartGenerator() {}
public static BitSet getBitSet() {
return MultipartGenerator$.MODULE$.getBitSet();
}
public static BitSet freshBitSet() {
return MultipartGenerator$.MODULE$.freshBitSet();
}
/** Adds a tile without notifying neighbouring blocks or adding it to the tick list. */
public static void silentAddTile(World world, BlockCoord pos, TileEntity tile) {
MultipartGenerator$.MODULE$.silentAddTile(world, pos, tile);
}
/** Registers the same tile trait for both sides. */
public static void registerTrait(String marker, String trait) {
MultipartGenerator$.MODULE$.registerTrait(marker, trait);
}
/** Registers side-specific tile traits; either trait may be null to exclude that side. */
public static void registerTrait(String marker, String clientTrait, String serverTrait) {
MultipartGenerator$.MODULE$.registerTrait(marker, clientTrait, serverTrait);
}
public static void registerPassThroughInterface(String name) {
MultipartGenerator$.MODULE$.registerPassThroughInterface(name);
}
/**
* Adds the interface to the container and forwards its methods to its single implementing part. A second part
* implementing the same interface is rejected by the generated trait's occlusion check.
*/
public static void registerPassThroughInterface(String name, boolean client, boolean server) {
MultipartGenerator$.MODULE$.registerPassThroughInterface(name, client, server);
}
}
@@ -1,240 +0,0 @@
package codechicken.multipart
import net.minecraft.tileentity.TileEntity
import net.minecraft.world.World
import codechicken.lib.vec.BlockCoord
import codechicken.multipart.handler.{MultipartProxy, MultipartProxy$}
import codechicken.lib.packet.PacketCustom
import net.minecraft.network.play.server.S23PacketBlockChange
import java.util.BitSet
import scala.collection.mutable.Map
import codechicken.multipart.asm.{ScratchBitSet, MultipartMixinFactory}
import codechicken.multipart.asm.ASMImplicits._
/** This class manages the dynamic construction and allocation of container
* TileMultipart instances.
*
* Classes that extend TileMultipart, adding tile centric logic, optimisations
* or interfaces, can be registered to a marker interface on a part instance.
* When a part is added to the tile that implements the certain marker
* interface, the container tile will be replaced with a class that includes
* the functionality from the corresponding mixin class.
*
* Classes are generated in a similar fashion to the way scala traits are
* compiled. To see the output, simply enable the config option and look in the
* asm/multipart folder of you .minecraft directory.
*
* There are several mixin traits that come with the API included in the
* scalatraits package. TPartialOcclusionTile is defined as class instead of
* trait to give an example for Java programmers.
*/
object MultipartGenerator extends ScratchBitSet {
private val tileTraitMap = Map[Class[_], BitSet]()
private val interfaceTraitMap_c = Map[String, String]()
private val interfaceTraitMap_s = Map[String, String]()
private val partTraitMap_c = Map[Class[_], BitSet]()
private val partTraitMap_s = Map[Class[_], BitSet]()
private val clientTraitId =
MultipartMixinFactory.registerTrait(
"codechicken.multipart.TileMultipartClient"
)
private def partTraitMap(client: Boolean) =
if (client) partTraitMap_c else partTraitMap_s
private def interfaceTraitMap(client: Boolean) =
if (client) interfaceTraitMap_c else interfaceTraitMap_s
private def traitsForPart(part: TMultiPart, client: Boolean) =
partTraitMap(client).getOrElseUpdate(
part.getClass, {
def heirachy(clazz: Class[_]): Seq[Class[_]] = {
var superClasses: Seq[Class[_]] =
clazz.getInterfaces.flatMap(c => heirachy(c)) :+ clazz
if (clazz.getSuperclass != null)
superClasses = superClasses ++ heirachy(clazz.getSuperclass)
return superClasses
}
val map = interfaceTraitMap(client)
val traits =
heirachy(part.getClass).flatMap(c => map.get(c.nodeName)).distinct
val bitset = new BitSet
traits.map(MultipartMixinFactory.getId).foreach(bitset.set)
bitset
}
)
private def setTraits(part: TMultiPart, client: Boolean): BitSet =
setTraits(Seq(part), client)
private def setTraits(parts: Iterable[TMultiPart], client: Boolean) = {
val bitset = freshBitSet
parts.foreach(p => bitset.or(traitsForPart(p, client)))
if (client) bitset.set(clientTraitId)
bitset
}
/** Check if part adds any new interfaces to tile, if so, replace tile with a
* new copy and call tile.addPart(part) returns true if tile was replaced
*/
private[multipart] def addPart(
world: World,
pos: BlockCoord,
part: TMultiPart
): TileMultipart = {
val gtile = TileMultipart.getOrConvertTile2(world, pos)
val tile = gtile._1
val converted = gtile._2.asInstanceOf[Boolean]
val bitset = setTraits(part, world.isRemote)
var ntile = tile
if (ntile != null) {
if (converted) { // perform client conversion
ntile.partList.apply(0).invalidateConvertedTile()
world.setBlock(pos.x, pos.y, pos.z, MultipartProxy.block, 0, 0)
silentAddTile(world, pos, ntile)
PacketCustom.sendToChunk(
new S23PacketBlockChange(pos.x, pos.y, pos.z, world),
world,
pos.x >> 4,
pos.z >> 4
)
ntile.partList.apply(0).onConverted()
ntile.writeAddPart(ntile.partList.apply(0))
}
val tileTraits = tileTraitMap(tile.getClass)
bitset.andNot(tileTraits)
if (!bitset.isEmpty) {
bitset.or(tileTraits)
ntile = MultipartMixinFactory.construct(bitset)
tile.setValid(false)
silentAddTile(world, pos, ntile)
ntile.from(tile)
}
} else {
world.setBlock(pos.x, pos.y, pos.z, MultipartProxy.block, 0, 0)
ntile = MultipartMixinFactory.construct(bitset)
silentAddTile(world, pos, ntile)
}
ntile.addPart_impl(part)
return ntile
}
/** Adds a tile entity to the world without notifying neighbor blocks or
* adding it to the tick list
*/
def silentAddTile(world: World, pos: BlockCoord, tile: TileEntity) {
val chunk = world.getChunkFromBlockCoords(pos.x, pos.z)
if (chunk != null)
chunk.func_150812_a(pos.x & 15, pos.y, pos.z & 15, tile)
}
/** Check if tile satisfies all the interfaces required by parts. If not,
* return a new generated copy of tile
*/
private[multipart] def generateCompositeTile(
tile: TileEntity,
parts: Iterable[TMultiPart],
client: Boolean
) = {
val bitset = setTraits(parts, client)
if (
tile != null && tile
.isInstanceOf[TileMultipart] && bitset == tileTraitMap(tile.getClass)
)
tile.asInstanceOf[TileMultipart]
else
MultipartMixinFactory.construct(bitset)
}
/** Check if there are any redundant interfaces on tile, if so, replace tile
* with new copy
*/
private[multipart] def partRemoved(tile: TileMultipart) = {
val ntile =
generateCompositeTile(tile, tile.partList, tile.getWorldObj.isRemote)
if (ntile != tile) {
tile.setValid(false)
silentAddTile(tile.getWorldObj, new BlockCoord(tile), ntile)
ntile.from(tile)
ntile.notifyTileChange()
}
ntile
}
/** register s_trait to be applied to tiles containing parts implementing
* s_interface
*/
def registerTrait(s_interface: String, s_trait: String): Unit =
registerTrait(s_interface, s_trait, s_trait)
/** register traits to be applied to tiles containing parts implementing
* s_interface c_trait for client worlds (may be null) s_trait for server
* worlds (may be null)
*/
def registerTrait(
s_interface$ : String,
c_trait$ : String,
s_trait$ : String
) {
val s_interface = nodeName(s_interface$)
val c_trait = nodeName(c_trait$)
val s_trait = nodeName(s_trait$)
def registerSide(map: Map[String, String], traitName: String) = if (
traitName != null
) {
if (map.contains(s_interface))
MultipartProxy.logger.error(
"Trait already registered for " + s_interface
)
else {
map.put(s_interface, traitName)
MultipartMixinFactory.registerTrait(traitName)
}
}
registerSide(interfaceTraitMap_c, c_trait)
registerSide(interfaceTraitMap_s, s_trait)
}
def registerPassThroughInterface(s_interface: String): Unit =
registerPassThroughInterface(s_interface, true, true)
/** A passthrough interface, is an interface to be implemented on the
* container tile instance, for which all calls are passed directly to the
* single implementing part. Registering a passthrough interface is
* equivalent to defining a mixin class as follows.
* 1. field 'impl' which contains the reference to the corresponding part
* 2. occlusionTest is overriden to prevent more than one part with
* s_interface existing in the block space 3. implementing s_interface
* and passing all calls directly to the part instance.
*
* This allows compatibility with APIs that expect interfaces on the tile
* entity.
*/
def registerPassThroughInterface(
s_interface: String,
client: Boolean,
server: Boolean
) {
val tType = MultipartMixinFactory.generatePassThroughTrait(s_interface)
if (tType == null)
return
registerTrait(
s_interface,
if (client) tType else null,
if (server) tType else null
)
}
private[multipart] def registerTileClass(
clazz: Class[_ <: TileMultipart],
traits: BitSet
) {
tileTraitMap.put(clazz, traits.copy)
MultipartProxy$.MODULE$.onTileClassBuilt(clazz)
}
}
@@ -6,7 +6,7 @@ import codechicken.lib.asm.{ObfMapping, CC_ClassWriter}
import org.objectweb.asm.{Label, FieldVisitor, MethodVisitor}
import org.objectweb.asm.Opcodes._
import org.objectweb.asm.tree.{MethodNode, ClassNode}
import codechicken.multipart.{MultipartGenerator, TileMultipart}
import codechicken.multipart.{MultipartGenerator$, TileMultipart}
import java.util.BitSet
import ASMImplicits._
@@ -245,5 +245,5 @@ object MultipartMixinFactory extends ASMMixinFactory(classOf[TileMultipart]) {
clazz: Class[_ <: TileMultipart],
traitSet: BitSet
) =
MultipartGenerator.registerTileClass(clazz, traitSet)
MultipartGenerator$.MODULE$.registerTileClass(clazz, traitSet)
}