mirror of
https://github.com/GTNewHorizons/ForgeMultipart.git
synced 2026-11-14 01:14:19 +00:00
refactor: port client tile traits to java
Preserve the exact generated client interfaces, render-cache behavior, and ordered display ticks. Teach Java mixin generation to linearize a registered parent and exclude transient runtime caches from copying.
This commit is contained in:
1 parent
6417e9b731
commit
970e888b7f
17 files changed
+448
-227
No files matched your search
+28
-6
@@ -25,11 +25,12 @@ No trustworthy tool will produce a maintainable Java port automatically. A decom
|
||||
|
||||
The binary and source-level consumer audits, low-coupling Phase 3 queue, audit-derived immediate compatibility gate,
|
||||
focused profile, first traversal optimization, complete redstone interaction helper unit, `MicroRecipe` port, and the
|
||||
first six Java-trait ports are complete. `TPartialOcclusionTile` proved the no-field path, `TSlottedTile` proved state
|
||||
first eight Java-trait ports are complete. `TPartialOcclusionTile` proved the no-field path, `TSlottedTile` proved state
|
||||
and lifecycle rewriting, `TRedstoneTile` removed the measured redstone allocation, `TTileChangeTile` exercised mutable
|
||||
state plus inherited access, `TFluidHandlerTile` preserved ordered Forge fluid distribution, and
|
||||
`TIInventoryTile`/`JInventoryTile` preserved AE2's direct rebuild linkage plus flattened sided inventory routing. All
|
||||
retain their exact runtime interfaces. See `JAVA_MIGRATION_PROFILE.md`.
|
||||
`TIInventoryTile`/`JInventoryTile` preserved AE2's direct rebuild linkage plus flattened sided inventory routing.
|
||||
`TileMultipartClient` and `TRandomDisplayTickTile` complete the built-in trait queue with their render-cache and random
|
||||
display-tick behavior intact. All retain their exact runtime interfaces. See `JAVA_MIGRATION_PROFILE.md`.
|
||||
|
||||
That audit found and this branch has now repaired one existing regression: Schematica 1.12.6 reflects the original private
|
||||
`MultiPartRegistry$` field `codechicken$multipart$MultiPartRegistry$$typeMap` and casts it to a Scala mutable map. The
|
||||
@@ -356,15 +357,20 @@ Exit condition: identified steady-state Scala allocation sites are removed with
|
||||
- [x] Verify generated field accessors, initialization, copying, lifecycle callbacks, super bridges, and class caching
|
||||
on the stateful `TSlottedTile` checkpoint.
|
||||
- [x] Characterize ProjectRed/Extra Utilities redstone calls and port `TRedstoneTile` with an immediate paired profile.
|
||||
- [ ] Convert remaining traits in small related groups.
|
||||
- [ ] Document or deliberately relax the Java trait restrictions only when a real trait requires it.
|
||||
- [ ] Preserve pass-through interface behavior.
|
||||
- [x] Convert remaining traits in small related groups.
|
||||
- [x] Document or deliberately relax the Java trait restrictions only when a real trait requires it.
|
||||
- [x] Preserve pass-through interface behavior.
|
||||
- [x] Keep the completed OpenComputers `TSlottedTile.v_partMap` mutation/rebinding case green and cover ProjectRed/
|
||||
Extra Utilities behavior before `TRedstoneTile`.
|
||||
- [x] Explicitly cover AE2's `TIInventoryTile.rebuildSlotMap` before the inventory trait.
|
||||
|
||||
Exit condition: built-in behavior is implemented in Java while the established runtime composition mechanism remains stable.
|
||||
|
||||
Status: complete. All built-in tile traits are Java. The client pair required narrowly extending `registerJavaTrait`
|
||||
for a registered Java trait extending another registered Java trait, explicit Java field accessors, and transient
|
||||
runtime-only fields that must not participate in generated `copyFrom`. The generated runtime interfaces and class
|
||||
cache remain reference-identical; pass-through-interface coverage remains green.
|
||||
|
||||
### Phase 6 — Convert multipart core and microblocks
|
||||
|
||||
- [ ] Add characterization coverage for each subsystem immediately before its conversion.
|
||||
@@ -714,3 +720,19 @@ generated-trait compatibility work. Scala-runtime removal remains a later projec
|
||||
snapshots before publishing a replacement immutable `Seq`.
|
||||
- In the paired run, `getLightValue` fell from 183.9 B to 0.0 B per call with 11.42x throughput, and `getTile` fell
|
||||
from 24.0 B to 0.0 B per call with 2.89x throughput. All 147 plain-JVM and 46 Forge tests pass.
|
||||
|
||||
### 2026-08-30
|
||||
|
||||
- Characterized `TileMultipartClient` and `TRandomDisplayTickTile` together against untouched Scala. Six behavior
|
||||
cases freeze render-cache partitioning and bounds, lazy initialization, dynamic short-circuiting, base no-op display
|
||||
ticks, and ordered `IRandomDisplayTick` dispatch; a Forge smoke case freezes both exact runtime interfaces,
|
||||
inheritance, generated fields, and generated-class caching.
|
||||
- Ported both traits to Java with package-private access shims. `registerJavaTrait` now linearizes an already registered
|
||||
Java parent trait, recognizes explicit field accessors, and skips transient runtime caches when auto-generating
|
||||
`copyFrom`. Trait registration now precedes `BlockMultipart` construction so Forge defines the runtime interfaces
|
||||
before ordinary Java bytecode can preload the mixin inputs.
|
||||
- Added three no-op client dispatch hooks to `TileMultipart`, allowing in-repo Java callers to invoke generated
|
||||
overrides through the stable superclass rather than emit class opcodes against types Forge rewrites to interfaces.
|
||||
The runtime trait surfaces remain exact; the raw dev-jar source-build limitation remains documented.
|
||||
- All 153 plain-JVM tests and all 77 Java 8 Forge dedicated-server tests pass. Actual static/dynamic rendering and
|
||||
particle appearance remain client-only manual checks.
|
||||
@@ -1693,3 +1693,58 @@ supported. The helper adds no public surface.
|
||||
- Complete plain-JVM suite: 153 tests, 0 failures, 0 errors.
|
||||
- Java 8 Forge dedicated-server suite: 70 tests, 0 failures, 0 errors.
|
||||
- Raw `javap` descriptors for both public types match the reference, including `scala.Tuple2[]` state accessors.
|
||||
|
||||
## 2026-08-30 — TileMultipartClient/TRandomDisplayTickTile Java-trait port
|
||||
|
||||
GuideNH is the only audited shipping consumer naming `TileMultipartClient`; it loads the class reflectively and uses
|
||||
it only for `isInstanceOf`. No frozen binary or audited source consumer references either trait's `$class` helper.
|
||||
|
||||
### Observable behavior
|
||||
|
||||
No known runtime divergence. `updateRenderCache` still partitions each part once in source order, using the same
|
||||
`doesTick || shouldRenderDynamic` short-circuit, then builds static and dynamic arrays in their respective order. Its
|
||||
bounding box remains the union of all render bounds translated by tile coordinates, or the full block for an empty or
|
||||
null part list. Static rendering still visits both caches, dynamic rendering still returns immediately when its flag
|
||||
is false, queries still initialize lazily, and `markRender` still calls super before rebuilding the cache.
|
||||
|
||||
The base display-tick method remains a no-op. `TRandomDisplayTickTile` still visits only `IRandomDisplayTick` parts in
|
||||
part-list order and passes the same `Random` instance to every callback.
|
||||
|
||||
### Generated ABI and raw input divergence
|
||||
|
||||
At Forge runtime `TileMultipartClient` remains a public field-free interface with no parent interface and the exact
|
||||
same 16 methods, including six private-cache accessors and the `markRender` super bridge.
|
||||
`TRandomDisplayTickTile` remains a field-free child interface extending exactly `TileMultipartClient` and declaring
|
||||
only `randomDisplayTick(Random)`. Generated composites retain the three private prefixed cache fields plus the plain
|
||||
private `hasDynamicParts` field, and continue to use the same class cache.
|
||||
|
||||
The raw dev artifact replaces the two Scala interfaces, two `$class` helpers, and three closure classes with concrete
|
||||
Java mixin inputs plus package-private `TileMultipartClientAccess` and `TRandomDisplayTickTileAccess`. As with earlier
|
||||
Java-trait ports, a consumer compiling directly against the untransformed dev jar must not emit direct class/field
|
||||
opcodes for these runtime interfaces; use a transformed compile stub for source-compatible downstream rebuilds.
|
||||
|
||||
`TileMultipart` gains additive no-op `renderStatic`, `renderDynamic`, and `randomDisplayTick` hooks. In-repo Java code
|
||||
invokes generated overrides through that stable superclass because javac otherwise emits `invokevirtual` against the
|
||||
concrete mixin input that Forge later rewrites to an interface. Existing binary consumers are unaffected.
|
||||
|
||||
### Transformer constraints
|
||||
|
||||
This is the first registered Java mixin extending another registered Java mixin. `registerJavaTrait` now carries the
|
||||
parent runtime interface into the generated hierarchy and linearization, while parent validation resolves through the
|
||||
registered mixin to `TileMultipart`. It also recognizes explicit source field accessors so it does not emit duplicate
|
||||
runtime declarations.
|
||||
|
||||
Render caches must not be copied by `TileMultipart.copyFrom`, matching the Scala trait. They are marked transient on
|
||||
the Java mixin input, and automatic Java-trait field copying now ignores transient fields. The generated runtime fields
|
||||
remain ordinary private fields. Trait registration moved before `BlockMultipart` construction so its
|
||||
`instanceof TileMultipartClient` bytecode cannot preload the raw class before Forge defines the runtime interface.
|
||||
|
||||
### Validation
|
||||
|
||||
- `TileMultipartClientFunctionalTest`: 6 Forge tests covering cache partition/order, short-circuiting, translated and
|
||||
fallback bounds, lazy initialization, dynamic early return, base no-op behavior, and ordered display-tick dispatch.
|
||||
- `ForgeEnvironmentSmokeTest.generatesClientAndRandomDisplayTickTraitsWithTheirExactHierarchy`: exact interfaces,
|
||||
inheritance, methods, generated fields, distinct combinations, and class-cache behavior.
|
||||
- All seven tests passed against the untouched Scala implementation before the port; all 77 Forge tests pass after it.
|
||||
- Complete plain-JVM suite: 153 tests, 0 failures, 0 errors.
|
||||
- Actual block/TESR rendering and particle appearance require a client and remain on the manual checklist.
|
||||
+35
-13
@@ -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`. 83 commits including separate characterization and port commits for
|
||||
`TIInventoryTile`/`JInventoryTile`.
|
||||
Branch: `algent/java`. Base: `master`. 85 commits including separate characterization and port commits for
|
||||
`TileMultipartClient`/`TRandomDisplayTickTile`.
|
||||
|
||||
## 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: **153 plain-JVM tests, 70 Forge server tests, all passing at their last completed runs.**
|
||||
Current baseline: **153 plain-JVM tests, 77 Forge server tests, all passing at their last completed runs.**
|
||||
|
||||
### ABI diff against the reference
|
||||
|
||||
@@ -197,6 +197,21 @@ trait-to-superclass cast is involved.
|
||||
`MatchError: 188` while registering the trait. `JInventoryTile` therefore converts its collected sided slots to
|
||||
`int[]` in package-private `JInventoryTileAccess`. Reference arrays use `ANEWARRAY` and remain safe inside traits.
|
||||
|
||||
**Registered Java traits may now extend another registered Java trait.** Register the parent first. The transformer
|
||||
adds the parent runtime interface and linearization entry, while parent validation resolves through it to the concrete
|
||||
`TileMultipart` base. This path is characterized by `TRandomDisplayTickTile`; do not generalize it to arbitrary class
|
||||
inheritance without another fixture.
|
||||
|
||||
**Mark runtime-only Java-trait fields transient when Scala did not copy them.** `autoCompleteJavaTrait` copies every
|
||||
ordinary field through generated `copyFrom`, but now excludes transient fields. `TileMultipartClient` uses this for
|
||||
render caches and its derived dynamic flag, preserving the Scala trait's absence of a `copyFrom` override.
|
||||
|
||||
**Do not invoke a registered mixin type directly from Java source.** The untransformed input is a class, so javac emits
|
||||
class opcodes that become invalid when Forge rewrites it to an interface. In-repo client calls use additive no-op hooks
|
||||
on `TileMultipart` so generated overrides dispatch through the stable superclass. Dedicated-server tests use targeted
|
||||
method handles for trait-only methods; ordinary reflection enumerates the client-only `RenderBlocks` descriptor and
|
||||
triggers the side transformer.
|
||||
|
||||
**Type the access shim's parameters `Object`.** `TTileChangeTileAccess` first typed them as the trait itself and cast
|
||||
to `TileMultipart` inside. Because the untransformed trait extends `TileMultipart`, javac elided the cast, and once
|
||||
Forge rewrote the trait to an interface the verifier rejected the `getfield`. `TRedstoneTileAccess` only escapes this
|
||||
@@ -214,7 +229,8 @@ 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`, the complete
|
||||
`IRedstonePart`/`RedstoneInteractions` unit, `MicroRecipe`, and the `TPartialOcclusionTile`, `TSlottedTile`, and
|
||||
`TRedstoneTile`, `TTileChangeTile`, `TFluidHandlerTile`, and `TIInventoryTile`/`JInventoryTile` Java-trait ports.
|
||||
`TRedstoneTile`, `TTileChangeTile`, `TFluidHandlerTile`, `TIInventoryTile`/`JInventoryTile`, `TileMultipartClient`, and
|
||||
`TRandomDisplayTickTile` Java-trait ports.
|
||||
|
||||
Plus the six marker interfaces: `TSlottedPart`, `IRandomDisplayTick`, `INeighborTileChange`, `TRandomUpdateTick`,
|
||||
`ISidedHollowConnect`, `IMicroMaterialRender`, plus `MultipartHelper`, `TileCache`, `PacketScheduler` and the `ControlKeyModifer` pair.
|
||||
@@ -229,7 +245,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.
|
||||
|
||||
89 Java files, 40 Scala files, ~5,453 Scala lines left (non-blank; that is the metric this figure has always used).
|
||||
91 Java files, 38 Scala files, ~5,328 Scala lines left (non-blank; that is the metric this figure has always used).
|
||||
|
||||
## What is left, and in what order
|
||||
|
||||
@@ -305,18 +321,23 @@ behavior tests freeze inventory-list sharing, flattened routing, sided offsets a
|
||||
generated composite retains its two private prefixed fields and per-tile initialization. Primitive `int[]` creation is
|
||||
isolated in `JInventoryTileAccess` because the transformer cannot analyze `NEWARRAY`.
|
||||
|
||||
`TileMultipartClient` and `TRandomDisplayTickTile` complete the built-in Java-trait queue. Six focused behavior tests
|
||||
freeze render-cache order, bounds, lazy initialization, dynamic short-circuiting, and display-tick dispatch. The Forge
|
||||
shape guard pins the exact 16-method base interface, one-method child interface, inheritance, generated private fields,
|
||||
and class caching. GuideNH only loads `TileMultipartClient` by name for `isInstanceOf`; no audited consumer references
|
||||
either removed `$class` helper. The true client rendering and particle paths remain on the manual checklist.
|
||||
|
||||
**Medium.** The `handler` packages on both sides, `ItemMicroPart`,
|
||||
`MicroblockPlacement`, `PlacementGrids`, `BlockMicroMaterial`, `MissingMicroMaterial`, `ConfigContent`,
|
||||
`DefaultContent`.
|
||||
|
||||
**Phase 5, needs the `registerJavaTrait` path.** `TileMultipartClient` and the remaining Scala files in
|
||||
`multipart/scalatraits/`. The no-field, stateful, measured hot-query, and inherited-member cases are all complete
|
||||
without a generator change.
|
||||
**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.
|
||||
|
||||
Take the client tile pair next: `TileMultipartClient` and `TRandomDisplayTickTile`. The latter is the only Scala file
|
||||
left in `multipart/scalatraits/`, is registered client-only, and reads inherited `partList`, so it needs both an access
|
||||
shim and client-side characterization rather than relying on the dedicated-server harness. Audit the base trait's
|
||||
consumer-visible shape before deciding whether to port them together.
|
||||
Take `multipart/handler/MultipartCompatiblity.scala` next as the smallest medium-risk Phase 6 handler unit. Preserve
|
||||
the misspelled public singleton name and its Scala `Function4` accessor, characterize the default allow path and the
|
||||
reflective MCPC hook failure path first, and keep the characterization/port commits separate.
|
||||
|
||||
**Phase 6/7, last.** `Microblock` and the microblock shape hierarchy, `MicroblockGenerator`, `MultipartGenerator`, and
|
||||
all of `multipart/asm/`. The ASM subsystem should be last; freeze generated-class fixtures before touching it.
|
||||
@@ -330,7 +351,8 @@ all of `multipart/asm/`. The ASM subsystem should be last; freeze generated-clas
|
||||
work and is required before release claims about real TPS.
|
||||
- The server side of flag-sensitive pass-through registration is automated; client-side exclusion still belongs to
|
||||
the packaged-client/manual compatibility run.
|
||||
- Shipping consumers compiled against the old `TSlottedTile`, `TRedstoneTile`, and `TIInventoryTile` interfaces remain binary-compatible
|
||||
- Shipping consumers compiled against the old `TSlottedTile`, `TRedstoneTile`, `TIInventoryTile`,
|
||||
`TileMultipartClient`, and `TRandomDisplayTickTile` interfaces remain binary-compatible
|
||||
because Forge still exposes those exact interfaces at runtime. A consumer recompiled against the untransformed dev
|
||||
jar instead sees concrete Java mixin inputs and can emit class/field opcodes that are invalid after Forge rewrites
|
||||
them to interfaces. Provide a transformed compile stub or downstream source guidance before claiming
|
||||
|
||||
@@ -17,6 +17,8 @@ release that includes those ports.
|
||||
| Hover a microblock placement target | The placement highlight renders, and a mod supplying its own highlight renderer overrides it | `MicroMaterialRegistry.renderHighlight` |
|
||||
| Look at a saw in inventory and in hand | The saw item renders with its custom transform | `ItemSaw` `IItemRenderer` |
|
||||
| Place microblocks of several materials, including glass | Transparent materials render in the correct pass and are not opaque | `IMicroMaterial.canRenderInPass` |
|
||||
| Change a mixed multipart tile containing static and dynamically rendered parts | Static parts remain in the block render, dynamic parts remain in the TESR pass, and neither disappears after a part update | `TileMultipartClient` render caches |
|
||||
| Watch a multipart torch long enough to emit display particles | Only parts implementing `IRandomDisplayTick` emit particles, with no duplicate or missing callbacks | `TRandomDisplayTickTile.randomDisplayTick` |
|
||||
|
||||
## Placement and interaction
|
||||
|
||||
|
||||
+53
-13
@@ -7,6 +7,8 @@ import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -43,11 +45,11 @@ class TileMultipartClientFunctionalTest {
|
||||
setParts(tile, dynamicOnly, fixed, ticking);
|
||||
|
||||
TileMultipartClient client = (TileMultipartClient) tile;
|
||||
client.updateRenderCache();
|
||||
invokeUpdateRenderCache(client);
|
||||
|
||||
assertArrayEquals(new TMultiPart[] { fixed }, staticCache(client));
|
||||
assertArrayEquals(new TMultiPart[] { dynamicOnly, ticking }, dynamicCache(client));
|
||||
assertTrue(client.hasDynamicParts());
|
||||
assertTrue((Boolean) invokeClient(client, "hasDynamicParts", boolean.class));
|
||||
assertEquals(1, dynamicOnly.doesTickCalls);
|
||||
assertEquals(1, dynamicOnly.dynamicCalls);
|
||||
assertEquals(1, fixed.doesTickCalls);
|
||||
@@ -66,11 +68,11 @@ class TileMultipartClientFunctionalTest {
|
||||
tile.partList_$eq(null);
|
||||
|
||||
TileMultipartClient client = (TileMultipartClient) tile;
|
||||
client.updateRenderCache();
|
||||
invokeUpdateRenderCache(client);
|
||||
|
||||
assertEquals(0, staticCache(client).length);
|
||||
assertEquals(0, dynamicCache(client).length);
|
||||
assertFalse(client.hasDynamicParts());
|
||||
assertFalse((Boolean) invokeClient(client, "hasDynamicParts", boolean.class));
|
||||
assertBounds(cachedBounds(client), -3.0, 7.0, 11.0, -2.0, 8.0, 12.0);
|
||||
}
|
||||
|
||||
@@ -84,8 +86,9 @@ class TileMultipartClientFunctionalTest {
|
||||
assertNull(staticCache(client));
|
||||
assertNull(cachedBounds(client));
|
||||
|
||||
assertFalse(client.shouldRenderInPass(17));
|
||||
AxisAlignedBB bounds = client.getRenderBoundingBox();
|
||||
assertFalse(
|
||||
(Boolean) invokeClient(client, "shouldRenderInPass", boolean.class, new Class<?>[] { int.class }, 17));
|
||||
AxisAlignedBB bounds = (AxisAlignedBB) invokeClient(client, "getRenderBoundingBox", AxisAlignedBB.class);
|
||||
|
||||
assertEquals(0, staticCache(client).length);
|
||||
assertEquals(0, dynamicCache(client).length);
|
||||
@@ -95,24 +98,32 @@ class TileMultipartClientFunctionalTest {
|
||||
|
||||
@Test
|
||||
void dynamicRenderingReturnsBeforeTouchingTheCacheWhenTheFlagIsFalse() throws Exception {
|
||||
TileMultipartClient client = (TileMultipartClient) newClientTile();
|
||||
TileMultipart tile = newClientTile();
|
||||
TileMultipartClient client = (TileMultipartClient) tile;
|
||||
RenderPart dynamic = new RenderPart("dynamic", true, false, Cuboid6.full);
|
||||
setDynamicCache(client, new TMultiPart[] { dynamic });
|
||||
client.hasDynamicParts_$eq(false);
|
||||
invokeClient(client, "hasDynamicParts_$eq", void.class, new Class<?>[] { boolean.class }, false);
|
||||
|
||||
client.renderDynamic(null, 0.5F, 1);
|
||||
invokeClient(
|
||||
client,
|
||||
"renderDynamic",
|
||||
void.class,
|
||||
new Class<?>[] { codechicken.lib.vec.Vector3.class, float.class, int.class },
|
||||
null,
|
||||
0.5F,
|
||||
1);
|
||||
|
||||
assertEquals(0, dynamic.renderDynamicCalls);
|
||||
}
|
||||
|
||||
@Test
|
||||
void baseRandomDisplayTickIsANoOp() {
|
||||
void baseRandomDisplayTickIsANoOp() throws Exception {
|
||||
TileMultipartClient client = (TileMultipartClient) newClientTile();
|
||||
client.randomDisplayTick(new Random(1L));
|
||||
invokeClient(client, "randomDisplayTick", void.class, new Class<?>[] { Random.class }, new Random(1L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void randomDisplayTickVisitsOnlyMatchingPartsInOrderWithTheSameRandom() {
|
||||
void randomDisplayTickVisitsOnlyMatchingPartsInOrderWithTheSameRandom() throws Exception {
|
||||
TileMultipart tile = newRandomDisplayTickTile();
|
||||
List<String> calls = new ArrayList<>();
|
||||
DisplayPart first = new DisplayPart("first", calls);
|
||||
@@ -121,7 +132,12 @@ class TileMultipartClientFunctionalTest {
|
||||
setParts(tile, first, skipped, second);
|
||||
Random random = new Random(2L);
|
||||
|
||||
((TRandomDisplayTickTile) tile).randomDisplayTick(random);
|
||||
invokeClient(
|
||||
(TileMultipartClient) tile,
|
||||
"randomDisplayTick",
|
||||
void.class,
|
||||
new Class<?>[] { Random.class },
|
||||
random);
|
||||
|
||||
assertEquals(Arrays.asList("first", "second"), calls);
|
||||
assertSame(random, first.lastRandom);
|
||||
@@ -146,6 +162,30 @@ class TileMultipartClientFunctionalTest {
|
||||
tile.partList_$eq(JavaConversions.asScalaBuffer(Arrays.asList(parts)).toList());
|
||||
}
|
||||
|
||||
private static void invokeUpdateRenderCache(TileMultipartClient client) throws Exception {
|
||||
invokeClient(client, "updateRenderCache", void.class);
|
||||
}
|
||||
|
||||
private static Object invokeClient(TileMultipartClient client, String name, Class<?> returnType, Object... args)
|
||||
throws Exception {
|
||||
return invokeClient(client, name, returnType, new Class<?>[0], args);
|
||||
}
|
||||
|
||||
private static Object invokeClient(TileMultipartClient client, String name, Class<?> returnType,
|
||||
Class<?>[] parameterTypes, Object... args) throws Exception {
|
||||
try {
|
||||
return MethodHandles.publicLookup()
|
||||
.findVirtual(TileMultipartClient.class, name, MethodType.methodType(returnType, parameterTypes))
|
||||
.bindTo(client).invokeWithArguments(args);
|
||||
} catch (Exception exception) {
|
||||
throw exception;
|
||||
} catch (Error error) {
|
||||
throw error;
|
||||
} catch (Throwable throwable) {
|
||||
throw new AssertionError(throwable);
|
||||
}
|
||||
}
|
||||
|
||||
private static TMultiPart[] staticCache(TileMultipartClient client) throws Exception {
|
||||
return (TMultiPart[]) generatedField(client, "staticCache").get(client);
|
||||
}
|
||||
|
||||
@@ -194,9 +194,9 @@ public class BlockMultipart extends Block {
|
||||
public boolean addHitEffects(World world, MovingObjectPosition hit, EffectRenderer effectRenderer) {
|
||||
TileMultipartClient tile = getClientTile(world, hit.blockX, hit.blockY, hit.blockZ);
|
||||
if (tile != null) {
|
||||
// TileMultipartClient is a Scala trait, so it is a bare interface here and carries no TileMultipart
|
||||
// members.
|
||||
TileMultipart parts = (TileMultipart) tile;
|
||||
// The mixin transformer turns TileMultipartClient into an interface at runtime, so retain the cast in
|
||||
// bytecode even though the Java source model extends TileMultipart.
|
||||
TileMultipart parts = TileMultipart.class.cast(tile);
|
||||
Tuple2<Object, ExtendedMOP> reduced = reduceMOP(hit);
|
||||
int index = (Integer) reduced._1();
|
||||
if (index < parts.partList().size()) {
|
||||
@@ -291,7 +291,7 @@ public class BlockMultipart extends Block {
|
||||
public void randomDisplayTick(World world, int x, int y, int z, Random random) {
|
||||
TileMultipartClient tile = getClientTile(world, x, y, z);
|
||||
if (tile != null) {
|
||||
tile.randomDisplayTick(random);
|
||||
TileMultipart.class.cast(tile).randomDisplayTick(random);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,9 @@ object MultipartGenerator extends ScratchBitSet {
|
||||
private val partTraitMap_c = Map[Class[_], BitSet]()
|
||||
private val partTraitMap_s = Map[Class[_], BitSet]()
|
||||
private val clientTraitId =
|
||||
MultipartMixinFactory.registerTrait(classOf[TileMultipartClient])
|
||||
MultipartMixinFactory.registerTrait(
|
||||
"codechicken.multipart.TileMultipartClient"
|
||||
)
|
||||
|
||||
private def partTraitMap(client: Boolean) =
|
||||
if (client) partTraitMap_c else partTraitMap_s
|
||||
|
||||
@@ -50,7 +50,7 @@ public final class MultipartRenderer$ extends TileEntitySpecialRenderer implemen
|
||||
state.pullLightmapInstance();
|
||||
state.useNormals = true;
|
||||
|
||||
client.renderDynamic(new Vector3(x, y, z), f, MinecraftForgeClient.getRenderPass());
|
||||
tile.renderDynamic(new Vector3(x, y, z), f, MinecraftForgeClient.getRenderPass());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,7 +80,7 @@ public final class MultipartRenderer$ extends TileEntitySpecialRenderer implemen
|
||||
CCRenderState state = CCRenderState.instance();
|
||||
state.resetInstance();
|
||||
state.lightMatrix.locate(world, x, y, z);
|
||||
boolean b = client.renderStatic(world, new Vector3(x, y, z), renderer);
|
||||
boolean b = tile.renderStatic(world, new Vector3(x, y, z), renderer);
|
||||
state.lightMatrix.access = null;
|
||||
return b;
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import net.minecraft.client.renderer.RenderBlocks;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.item.EntityItem;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
@@ -14,6 +16,7 @@ import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagList;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.Vec3;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import codechicken.lib.data.MCDataOutput;
|
||||
@@ -259,6 +262,17 @@ public class TileMultipart extends TileEntity implements IChunkLoadTile {
|
||||
worldObj.func_147479_m(xCoord, yCoord, zCoord);
|
||||
}
|
||||
|
||||
/** Client render hook overridden by generated tiles carrying TileMultipartClient. */
|
||||
public boolean renderStatic(IBlockAccess world, Vector3 position, RenderBlocks renderer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Client render hook overridden by generated tiles carrying TileMultipartClient. */
|
||||
public void renderDynamic(Vector3 position, float frame, int pass) {}
|
||||
|
||||
/** Client particle hook overridden by generated tiles carrying TRandomDisplayTickTile. */
|
||||
public void randomDisplayTick(Random random) {}
|
||||
|
||||
/** Helper function for calling a second level notify on a side (eg indirect power from a lever). */
|
||||
public void notifyNeighborChange(int side) {
|
||||
BlockCoord pos = new BlockCoord(this).offset(side);
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
package codechicken.multipart;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import net.minecraft.client.renderer.RenderBlocks;
|
||||
import net.minecraft.util.AxisAlignedBB;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
import net.minecraftforge.client.ForgeHooksClient;
|
||||
|
||||
import codechicken.lib.vec.Cuboid6;
|
||||
import codechicken.lib.vec.Vector3;
|
||||
import scala.collection.Iterator;
|
||||
import scala.collection.Seq;
|
||||
|
||||
/** Client-side multipart render-cache mixin. */
|
||||
public class TileMultipartClient extends TileMultipart {
|
||||
|
||||
private transient AxisAlignedBB cachedRenderBounds;
|
||||
private transient TMultiPart[] staticCache;
|
||||
private transient TMultiPart[] dynamicCache;
|
||||
|
||||
public transient boolean hasDynamicParts;
|
||||
|
||||
public boolean hasDynamicParts() {
|
||||
return hasDynamicParts;
|
||||
}
|
||||
|
||||
public void hasDynamicParts_$eq(boolean value) {
|
||||
hasDynamicParts = value;
|
||||
}
|
||||
|
||||
public void updateRenderCache() {
|
||||
Seq<TMultiPart> parts = TileMultipartClientAccess.partList(this);
|
||||
if (parts != null) {
|
||||
List<TMultiPart> dynamicParts = new ArrayList<>();
|
||||
List<TMultiPart> staticParts = new ArrayList<>();
|
||||
Iterator<TMultiPart> iterator = parts.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
TMultiPart part = iterator.next();
|
||||
if (part.doesTick() || part.shouldRenderDynamic()) {
|
||||
dynamicParts.add(part);
|
||||
} else {
|
||||
staticParts.add(part);
|
||||
}
|
||||
}
|
||||
|
||||
TMultiPart[] statics = staticParts.toArray(new TMultiPart[staticParts.size()]);
|
||||
TMultiPart[] dynamics = dynamicParts.toArray(new TMultiPart[dynamicParts.size()]);
|
||||
Cuboid6 bounds = null;
|
||||
for (TMultiPart part : statics) {
|
||||
Cuboid6 partBounds = part.getRenderBounds();
|
||||
if (bounds == null) {
|
||||
bounds = partBounds.copy();
|
||||
} else {
|
||||
bounds.enclose(partBounds);
|
||||
}
|
||||
}
|
||||
for (TMultiPart part : dynamics) {
|
||||
Cuboid6 partBounds = part.getRenderBounds();
|
||||
if (bounds == null) {
|
||||
bounds = partBounds.copy();
|
||||
} else {
|
||||
bounds.enclose(partBounds);
|
||||
}
|
||||
}
|
||||
if (bounds == null) {
|
||||
bounds = Cuboid6.full;
|
||||
}
|
||||
|
||||
bounds.add(TileMultipartClientAccess.position(this));
|
||||
cachedRenderBounds = bounds.toAABB();
|
||||
staticCache = statics;
|
||||
dynamicCache = dynamics;
|
||||
hasDynamicParts = dynamics.length > 0;
|
||||
} else {
|
||||
staticCache = new TMultiPart[0];
|
||||
dynamicCache = new TMultiPart[0];
|
||||
hasDynamicParts = false;
|
||||
cachedRenderBounds = TileMultipartClientAccess.fullBlockBounds(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean renderStatic(IBlockAccess world, Vector3 vector, RenderBlocks renderer) {
|
||||
if (staticCache == null) {
|
||||
updateRenderCache();
|
||||
}
|
||||
|
||||
boolean rendered = false;
|
||||
TMultiPart[] statics = staticCache;
|
||||
if (statics != null) {
|
||||
for (TMultiPart part : statics) {
|
||||
rendered |= TileMultipartClientAccess.renderPart(part, world, vector, renderer);
|
||||
}
|
||||
}
|
||||
|
||||
TMultiPart[] dynamics = dynamicCache;
|
||||
if (dynamics != null) {
|
||||
for (TMultiPart part : dynamics) {
|
||||
rendered |= TileMultipartClientAccess.renderPart(part, world, vector, renderer);
|
||||
}
|
||||
}
|
||||
return rendered;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderDynamic(Vector3 position, float frame, int pass) {
|
||||
if (!hasDynamicParts) {
|
||||
return;
|
||||
}
|
||||
|
||||
TMultiPart[] dynamics = dynamicCache;
|
||||
if (dynamics != null) {
|
||||
for (TMultiPart part : dynamics) {
|
||||
if (part != null) {
|
||||
part.renderDynamic(position, frame, pass);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void randomDisplayTick(Random random) {}
|
||||
|
||||
@Override
|
||||
public boolean shouldRenderInPass(int pass) {
|
||||
if (staticCache == null) {
|
||||
updateRenderCache();
|
||||
}
|
||||
return hasDynamicParts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AxisAlignedBB getRenderBoundingBox() {
|
||||
if (cachedRenderBounds == null) {
|
||||
updateRenderCache();
|
||||
}
|
||||
return cachedRenderBounds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markRender() {
|
||||
super.markRender();
|
||||
updateRenderCache();
|
||||
}
|
||||
}
|
||||
|
||||
/** Keeps inherited tile access and client rendering calls outside the generated trait transformer. */
|
||||
final class TileMultipartClientAccess {
|
||||
|
||||
private TileMultipartClientAccess() {}
|
||||
|
||||
static Seq<TMultiPart> partList(Object tile) {
|
||||
return ((TileMultipart) tile).partList();
|
||||
}
|
||||
|
||||
static Vector3 position(Object tile) {
|
||||
return Vector3.fromTileEntity((TileMultipart) tile);
|
||||
}
|
||||
|
||||
static AxisAlignedBB fullBlockBounds(Object tile) {
|
||||
TileMultipart multipart = (TileMultipart) tile;
|
||||
return AxisAlignedBB.getBoundingBox(
|
||||
multipart.xCoord,
|
||||
multipart.yCoord,
|
||||
multipart.zCoord,
|
||||
multipart.xCoord + 1,
|
||||
multipart.yCoord + 1,
|
||||
multipart.zCoord + 1);
|
||||
}
|
||||
|
||||
static boolean renderPart(TMultiPart part, IBlockAccess world, Vector3 vector, RenderBlocks renderer) {
|
||||
if (part == null) {
|
||||
return false;
|
||||
}
|
||||
if (part instanceof ISBRHPart) {
|
||||
return ((ISBRHPart) part).renderWorldBlock(world, (int) vector.x, (int) vector.y, (int) vector.z, renderer);
|
||||
}
|
||||
return part.renderStatic(vector, ForgeHooksClient.getWorldRenderPass());
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
package codechicken.multipart
|
||||
|
||||
import java.util.Random
|
||||
|
||||
import codechicken.lib.vec.{Cuboid6, Vector3}
|
||||
import net.minecraft.client.renderer.RenderBlocks
|
||||
import net.minecraft.util.AxisAlignedBB
|
||||
import net.minecraft.world.IBlockAccess
|
||||
import net.minecraftforge.client.ForgeHooksClient
|
||||
|
||||
import scala.collection.JavaConversions._
|
||||
|
||||
trait TileMultipartClient extends TileMultipart {
|
||||
private var cachedRenderBounds: AxisAlignedBB = null
|
||||
private var staticCache: Array[TMultiPart] = null
|
||||
private var dynamicCache: Array[TMultiPart] = null
|
||||
|
||||
var hasDynamicParts: Boolean = false
|
||||
|
||||
def updateRenderCache() {
|
||||
if (partList != null) {
|
||||
val (dynamic, static) =
|
||||
partList.partition(p => p.doesTick || p.shouldRenderDynamic)
|
||||
val sArr = static.toArray
|
||||
val dArr = dynamic.toArray
|
||||
|
||||
var c: Cuboid6 = null
|
||||
var i = 0
|
||||
val allParts = sArr ++ dArr
|
||||
while (i < allParts.length) {
|
||||
val b = allParts(i).getRenderBounds
|
||||
if (c == null) c = b.copy
|
||||
else c.enclose(b)
|
||||
i += 1
|
||||
}
|
||||
|
||||
if (c == null) c = Cuboid6.full
|
||||
|
||||
c.add(Vector3.fromTileEntity(this))
|
||||
cachedRenderBounds = c.toAABB
|
||||
staticCache = sArr
|
||||
dynamicCache = dArr
|
||||
hasDynamicParts = dArr.length > 0
|
||||
} else {
|
||||
staticCache = Array.empty
|
||||
dynamicCache = Array.empty
|
||||
hasDynamicParts = false
|
||||
cachedRenderBounds = AxisAlignedBB.getBoundingBox(
|
||||
xCoord,
|
||||
yCoord,
|
||||
zCoord,
|
||||
xCoord + 1,
|
||||
yCoord + 1,
|
||||
zCoord + 1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def renderStatic(
|
||||
world: IBlockAccess,
|
||||
vec: Vector3,
|
||||
renderer: RenderBlocks
|
||||
) = {
|
||||
if (staticCache == null)
|
||||
updateRenderCache()
|
||||
|
||||
var rendered = false
|
||||
|
||||
def renderPart(part: TMultiPart): Unit = {
|
||||
if (part == null)
|
||||
return
|
||||
|
||||
part match {
|
||||
case isbrh: ISBRHPart =>
|
||||
if (
|
||||
isbrh.renderWorldBlock(
|
||||
world,
|
||||
vec.x.toInt,
|
||||
vec.y.toInt,
|
||||
vec.z.toInt,
|
||||
renderer
|
||||
)
|
||||
) {
|
||||
rendered = true
|
||||
}
|
||||
|
||||
case _ =>
|
||||
if (
|
||||
part.renderStatic(
|
||||
vec,
|
||||
ForgeHooksClient.getWorldRenderPass
|
||||
)
|
||||
) {
|
||||
rendered = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val statics = staticCache
|
||||
if (statics != null) {
|
||||
var i = 0
|
||||
val len = statics.length
|
||||
|
||||
while (i < len) {
|
||||
renderPart(statics(i))
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
val dynamics = dynamicCache
|
||||
if (dynamics != null) {
|
||||
var i = 0
|
||||
val len = dynamics.length
|
||||
|
||||
while (i < len) {
|
||||
renderPart(dynamics(i))
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
rendered
|
||||
}
|
||||
|
||||
def renderDynamic(pos: Vector3, frame: Float, pass: Int) {
|
||||
if (!hasDynamicParts) return
|
||||
|
||||
val dynamics = dynamicCache
|
||||
if (dynamics != null) {
|
||||
var i = 0
|
||||
val len = dynamics.length
|
||||
while (i < len) {
|
||||
if (dynamics(i) != null) dynamics(i).renderDynamic(pos, frame, pass)
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def randomDisplayTick(random: Random) {}
|
||||
|
||||
override def shouldRenderInPass(pass: Int) = {
|
||||
if (staticCache == null) updateRenderCache()
|
||||
hasDynamicParts
|
||||
}
|
||||
|
||||
override def getRenderBoundingBox = {
|
||||
if (cachedRenderBounds == null) updateRenderCache()
|
||||
cachedRenderBounds
|
||||
}
|
||||
|
||||
override def markRender(): Unit = {
|
||||
super.markRender()
|
||||
updateRenderCache()
|
||||
}
|
||||
}
|
||||
@@ -556,7 +556,7 @@ object ASMMixinCompiler {
|
||||
"Cannot register abstract class " + cnode.name + " as a java mixin trait. Use scala"
|
||||
)
|
||||
|
||||
// val parentTraits = getAndRegisterParentTraits(cnode)
|
||||
val parentTrait = getMixinInfo(cnode.superName)
|
||||
val fields = cnode.fields
|
||||
.map(f => (f.name, FieldMixin(f.name, f.desc, f.access)))
|
||||
.toMap
|
||||
@@ -591,7 +591,7 @@ object ASMMixinCompiler {
|
||||
cnode.name,
|
||||
null,
|
||||
"java/lang/Object",
|
||||
Array(cnode.interfaces: _*)
|
||||
(cnode.interfaces ++ parentTrait.map(_.name)).distinct.toArray
|
||||
)
|
||||
|
||||
def fname(name: String) = fields(name).accessName(cnode.name)
|
||||
@@ -787,7 +787,14 @@ object ASMMixinCompiler {
|
||||
staticTransform(mv, mnode)
|
||||
}
|
||||
|
||||
cnode.methods.foreach(convertMethod)
|
||||
def isGeneratedFieldAccessor(mnode: MethodNode) = fields.values.exists {
|
||||
field =>
|
||||
val name = field.accessName(cnode.name)
|
||||
mnode.name == name && mnode.desc == "()" + field.desc ||
|
||||
mnode.name == name + "_$eq" && mnode.desc == "(" + field.desc + ")V"
|
||||
}
|
||||
|
||||
cnode.methods.filterNot(isGeneratedFieldAccessor).foreach(convertMethod)
|
||||
|
||||
define(inode.name, createBytes(inode, 0))
|
||||
define(tnode.name, createBytes(tnode, 0))
|
||||
@@ -796,8 +803,8 @@ object ASMMixinCompiler {
|
||||
tnode.name,
|
||||
MixinInfo(
|
||||
tnode.name,
|
||||
cnode.superName,
|
||||
Seq(),
|
||||
parentTrait.map(_.parent).getOrElse(cnode.superName),
|
||||
parentTrait.toSeq,
|
||||
fields.values.toSeq,
|
||||
methods,
|
||||
supers
|
||||
|
||||
@@ -67,10 +67,17 @@ class ASMMixinFactory[T](
|
||||
}
|
||||
|
||||
val info = getClassInfo(cnode)
|
||||
def concreteParent(info: ClassInfo): ClassInfo = info.superClass.map {
|
||||
case i if i.isTrait => concreteParent(i)
|
||||
case i => i
|
||||
}.get
|
||||
def concreteParent(info: ClassInfo): ClassInfo = getMixinInfo(
|
||||
info.name
|
||||
) match {
|
||||
case Some(mixin) => getClassInfo(mixin.parent)
|
||||
case None =>
|
||||
info.superClass.map {
|
||||
case i if i.isTrait || getMixinInfo(i.name).isDefined =>
|
||||
concreteParent(i)
|
||||
case i => i
|
||||
}.get
|
||||
}
|
||||
|
||||
val parentName = concreteParent(info).name
|
||||
def checkParent(info: ClassInfo): Boolean =
|
||||
|
||||
@@ -14,8 +14,9 @@ import scala.collection.JavaConversions._
|
||||
|
||||
object MultipartMixinFactory extends ASMMixinFactory(classOf[TileMultipart]) {
|
||||
override protected def autoCompleteJavaTrait(cnode: ClassNode) {
|
||||
val copyFields = cnode.fields.filter(f => (f.access & ACC_TRANSIENT) == 0)
|
||||
if (
|
||||
!cnode.fields.isEmpty && findMethod(
|
||||
!copyFields.isEmpty && findMethod(
|
||||
new ObfMapping(
|
||||
cnode.name,
|
||||
"copyFrom",
|
||||
@@ -45,7 +46,7 @@ object MultipartMixinFactory extends ASMMixinFactory(classOf[TileMultipart]) {
|
||||
val end = new Label()
|
||||
mv.visitJumpInsn(IFEQ, end)
|
||||
|
||||
cnode.fields.foreach { f =>
|
||||
copyFields.foreach { f =>
|
||||
mv.visitVarInsn(ALOAD, 0)
|
||||
mv.visitVarInsn(ALOAD, 1)
|
||||
mv.visitFieldInsn(GETFIELD, cnode.name, f.name, f.desc)
|
||||
|
||||
@@ -28,15 +28,6 @@ class MultipartProxy_serverImpl {
|
||||
config = new ConfigFile(new File(cfgdir, "multipart.cfg"))
|
||||
.setComment("Multipart API config file")
|
||||
|
||||
GameRegistry.registerBlock(
|
||||
new BlockMultipart().setBlockName("multipart"),
|
||||
null,
|
||||
"block"
|
||||
)
|
||||
block = Block.blockRegistry
|
||||
.getObject("ForgeMultipart:block")
|
||||
.asInstanceOf[BlockMultipart]
|
||||
|
||||
MultipartGenerator.registerTrait(
|
||||
"codechicken.multipart.TSlottedPart",
|
||||
"codechicken.multipart.scalatraits.TSlottedTile"
|
||||
@@ -72,6 +63,15 @@ class MultipartProxy_serverImpl {
|
||||
"codechicken.multipart.scalatraits.TTileChangeTile"
|
||||
)
|
||||
|
||||
GameRegistry.registerBlock(
|
||||
new BlockMultipart().setBlockName("multipart"),
|
||||
null,
|
||||
"block"
|
||||
)
|
||||
block = Block.blockRegistry
|
||||
.getObject("ForgeMultipart:block")
|
||||
.asInstanceOf[BlockMultipart]
|
||||
|
||||
MultipartSaveLoad.hookLoader()
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package codechicken.multipart.scalatraits;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import codechicken.multipart.IRandomDisplayTick;
|
||||
import codechicken.multipart.TMultiPart;
|
||||
import codechicken.multipart.TileMultipart;
|
||||
import codechicken.multipart.TileMultipartClient;
|
||||
import scala.collection.Iterator;
|
||||
|
||||
/** Client mixin that dispatches random display ticks only to interested parts. */
|
||||
public class TRandomDisplayTickTile extends TileMultipartClient {
|
||||
|
||||
@Override
|
||||
public void randomDisplayTick(Random random) {
|
||||
TRandomDisplayTickTileAccess.randomDisplayTick(this, random);
|
||||
}
|
||||
}
|
||||
|
||||
/** Keeps inherited part-list access outside the generated trait transformer. */
|
||||
final class TRandomDisplayTickTileAccess {
|
||||
|
||||
private TRandomDisplayTickTileAccess() {}
|
||||
|
||||
static void randomDisplayTick(Object tile, Random random) {
|
||||
Iterator<TMultiPart> parts = ((TileMultipart) tile).partList().iterator();
|
||||
while (parts.hasNext()) {
|
||||
TMultiPart part = parts.next();
|
||||
if (part instanceof IRandomDisplayTick) {
|
||||
((IRandomDisplayTick) part).randomDisplayTick(random);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package codechicken.multipart.scalatraits
|
||||
|
||||
import codechicken.multipart.TileMultipartClient
|
||||
import codechicken.multipart.IRandomDisplayTick
|
||||
import java.util.Random
|
||||
|
||||
/** Saves processor time looping on tiles that don't need it
|
||||
*/
|
||||
trait TRandomDisplayTickTile extends TileMultipartClient {
|
||||
override def randomDisplayTick(random: Random) {
|
||||
for (p @ (_p: IRandomDisplayTick) <- partList.iterator)
|
||||
p.randomDisplayTick(random)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user