- drop per-guide Validation sections; manual gates stay in MANUAL_CHECKS - drop audit/provenance lines (reference checkouts, dated refreshes, commit pins) - state version status and Scala-free compile check once in API.md - replace API.md status prose with link to migration status - fix RELEASE_NOTES dangling sentence - rewrite README for GTNH fork and Java port
5.4 KiB
Reading and visiting multipart parts from Java
Use TileMultipart.jPartList() for ordered collection access, and forEachPart(Consumer<TMultiPart>) for callbacks
that skip detached parts. Both methods work on generated tiles.
Examples
Read all part types in list order:
public static List<String> partTypes(TileMultipart tile) {
List<String> types = new ArrayList<>();
for (TMultiPart part : tile.jPartList()) {
types.add(part.getType());
}
return types;
}
To visit only parts that are still bound to this tile:
public static List<String> boundPartTypes(TileMultipart tile) {
List<String> types = new ArrayList<>();
tile.forEachPart(part -> types.add(part.getType()));
return types;
}
See the complete Java example. The returned name lists are independent copies.
Collection ownership and ordering
jPartList() wraps the sequence returned by the virtual partList() getter at that call. It preserves every entry,
its index and its object identity, including detached parts. Use it for indexing, searches, serialization and ordinary
iteration. It does not require parts to have joined a world.
Normal FMP additions/removals replace an immutable sequence. A previously returned Java list keeps the old sequence;
call jPartList() again for the tile's new contents. A legacy setter or getter override can instead supply a mutable
sequence, in which case mutations of that sequence remain visible through its Java view. Therefore the method does
not promise either an independent snapshot or an unmodifiable list for every implementation.
Treat the view as read-only. Editing it, even when the backing sequence permits it, does not perform part binding,
slot updates, notifications or synchronization. Use TileMultipart.addPart(world, pos, part) and tile.remPart(part)
for normal world changes, retaining their returned tile because adding/removing capabilities can replace the tile
instance. To capture independent list storage, use new ArrayList<>(tile.jPartList()); the part objects remain shared.
Callback behavior
Use the game thread. The default forEachPart traversal has the same behavior as the legacy operate hook:
- Capture the current sequence once and visit it in order. Normal additions during a callback publish a new sequence, so the current traversal does not visit the added parts.
- Check
part.tile() == thisimmediately before each callback. Parts detached or rebound to another tile are skipped, including when an earlier callback replaces the tile and transfers its parts. - A nested traversal captures the sequence anew and can observe the updated list. Each call has its own traversal state.
- Propagate the original callback exception immediately and stop that traversal. Side effects already performed remain.
- Preserve the iterator behavior of legacy mutable sequences. Do not structurally mutate such a sequence during traversal.
- Pass a non-null callback. For compatibility, there is no eager null check: with the default hook, null throws
NullPointerExceptiononly when a part still bound to this tile reaches the callback. Empty sequences or sequences containing only detached/transferred parts invoke nothing.
tile.jPartList().forEach(action) visits every entry, including detached parts. It is not equivalent to
tile.forEachPart(action). Preserve the consumer's original filtering and callback behavior when migrating.
Overrides and legacy entry points
The dispatch paths are jPartList() → virtual partList(), and forEachPart(consumer) → virtual operate(adapter).
The existing Java adapter and traversal implementation are shared; the legacy methods keep their descriptors and
bodies. Both legacy methods are deprecated for callers, with replacements named in their Javadocs.
Existing getter and operate overrides remain supported and can customize these behaviors. Lifecycle callbacks still
call operate directly. Overriding forEachPart intercepts calls to that convenience method only; it does not intercept
ticks, chunk callbacks or other lifecycle dispatch. Keep a legacy operate override in place if it supplies that hook.
No replacement lifecycle override contract is introduced by this addition.
Consumer migration
| Existing use | Java replacement |
|---|---|
partList().size() / isEmpty() / indexOf(part) |
The corresponding jPartList() list method |
Scala sequence indexing (partList().apply(index) or tile.partList(index)) |
tile.jPartList().get(index) |
| Scala iterator for reading/searching every part | Enhanced for loop over tile.jPartList() |
operate(scalaFunction) callback |
forEachPart(javaConsumer); preserve any existing hook overrides |
AE2's placement helper and Extra Utilities' renderer loops can use jPartList() without adding detached-part filtering.
ProjectRed's illumination calculations and packet/render indices, and OpenComputers' cable/print/network searches,
must retain their original order, filters and aggregation. Those mods can remain Scala internally while calling Java
collection methods. Existing Java consumers already using jPartList() need no rename.
GuideNH already uses jPartList() for material export and statistics. Its preview reconstruction and AE2 bridge
still use Scala partList; those move to the client preview helper.