mirror of
https://github.com/GTNewHorizons/ForgeMultipart.git
synced 2026-11-13 17:04:25 +00:00
refactor: port multipart generator to Java
This commit is contained in:
1 parent
e6af4251ae
commit
09670a09ce
8 files changed
+345
-254
No files matched your search
@@ -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.
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
Reference in new issue
Block a user