mirror of
https://github.com/GTNewHorizons/ForgeMultipart.git
synced 2026-11-12 16:37:38 +00:00
feat: add Java collection occlusion query
This commit is contained in:
1 parent
6744f5b04c
commit
d70f69e79f
11 files changed
+317
-25
No files matched your search
+5
-2
@@ -531,7 +531,8 @@ Each row keeps its existing descriptor for binary compatibility and gains a Java
|
||||
| `MultiPartRegistry.registerParts(IPartFactory2, scala.collection.Seq)` | `registerParts(IPartFactory2, String...)` | Sibling already exists |
|
||||
| `MicroMaterialRegistry.getIdMap(): scala.Tuple2[]` | `materialCount(): int` plus existing `materialName(int)` and `getMaterial(int)` | Implemented with static/companion deprecation; [guide and compiling example](docs/api/MATERIAL_ENUMERATION.md). Consumer release/adoption remains pending |
|
||||
| `TileMultipart.operate(scala.Function1<TMultiPart, BoxedUnit>)` | `forEachPart(java.util.function.Consumer<TMultiPart>)` | Implemented through the legacy virtual hook; preserves captured traversal, detached-part filtering and callback failures. Lifecycle still calls `operate` |
|
||||
| `TileMultipart.occlusionTest(scala.collection.Seq, TMultiPart)` | `occlusionTest(Collection<TMultiPart>, TMultiPart)` | |
|
||||
| `TileMultipart.occlusionTest(scala.collection.Seq, TMultiPart)` | `testOcclusion(Collection<? extends TMultiPart>, TMultiPart)` | Implemented with an input snapshot and legacy virtual dispatch, including generated partial occlusion; [guide](docs/api/OCCLUSION.md). Distinct Java name avoids Scala overload resolution |
|
||||
| `NormalOcclusionTest.apply(scala.collection.Traversable<Cuboid6>, scala.collection.Traversable<Cuboid6>)` and companion entry | Java box-versus-box entry, with a distinct name to be chosen after characterization | Still used by ForgeRelocationFMP and OpenComputers. The existing Java `test(Iterable, Iterable)` loop is private; its public part-based sibling does not replace this box-list contract |
|
||||
| `TileMultipart.loadParts(scala.collection.Iterable)` | `loadPartList(Collection<TMultiPart>)` | Implemented through the legacy virtual hook; binding, world notifications, input iteration and partial failure retained. [Loading guide](docs/api/PART_LOADING.md) |
|
||||
| `TileMultipart.partList_$eq(scala.collection.Seq)` | `setPartList(List<TMultiPart>)` | Implemented through the legacy setter; Java list copied without binding, null sentinel supported. GuideNH's old reflective name remains supported |
|
||||
| `TileMultipart.renderID()` / `renderID_$eq(int)` | `getRenderID()` / `setRenderID(int)` | |
|
||||
@@ -549,11 +550,13 @@ same-name overload there invites a silent wrong-overload bind.
|
||||
their override dispatch. JVM and generated-tile Forge cases cover the [documented contract](docs/api/PART_TRAVERSAL.md).
|
||||
- [x] Add Java loading and storage assignment, with legacy override/reflection coverage, explicit ownership and
|
||||
lifecycle contracts, and generated server/client tile checks; [guide](docs/api/PART_LOADING.md), [API index](docs/API.md).
|
||||
- [x] Add `testOcclusion(Collection, candidate)` with snapshot ownership, subtype inputs, legacy hook dispatch and
|
||||
generated partial-occlusion coverage; [guide and compiling example](docs/api/OCCLUSION.md).
|
||||
- [ ] For remaining siblings, compile external Java examples with Scala excluded from the compile classpath before
|
||||
finalizing names. The proposed `loadParts(Collection)` overload required `scala.collection.Iterable` during javac
|
||||
overload resolution; the implemented `loadPartList(Collection)` avoids that dependency. Apply this gate to the
|
||||
remaining occlusion and registration overloads as well as checking the emitted consumer bytecode.
|
||||
- [ ] Mark all nine rows `@Deprecated` with javadoc naming the replacement.
|
||||
- [ ] Mark the remaining legacy entries above `@Deprecated` with javadoc naming a working replacement.
|
||||
- [ ] Confirm every original descriptor still exists in the ABI fixture after the change.
|
||||
- [ ] Document the supported API with compiling usage examples and an old-to-new migration guide. Validate Java
|
||||
subclasses and generated extensions on the actual Forge path, including both sides where relevant.
|
||||
|
||||
@@ -491,7 +491,7 @@ behavior:
|
||||
## Java API adoption ledger
|
||||
|
||||
This ledger tracks migration of a specific legacy contract, not completion of an entire consumer's migration.
|
||||
Material enumeration, tile collection/traversal access and tile loading/storage assignment are implemented on
|
||||
Material enumeration, tile collection/traversal access, tile loading/storage assignment and collection occlusion queries are implemented on
|
||||
`algent/java`. The [API index](docs/API.md) links the guides and compiling Java examples. These additions are not yet
|
||||
tied to a released minimum dependency version. Unlisted contracts remain governed by the inventories above.
|
||||
|
||||
@@ -539,6 +539,18 @@ failure behavior. All 546 pre-change compiled JVM tests pass with their recorded
|
||||
server slot rebuilding/notifications and client storage/loading/render-cache queries. Actual client preview rendering,
|
||||
consumer release and pack adoption are still separate gates.
|
||||
|
||||
Tile collection occlusion is implemented as `testOcclusion(Collection<? extends TMultiPart>, TMultiPart)`, documented
|
||||
in the [occlusion guide](docs/api/OCCLUSION.md). The supplied Java/Scala source search found no direct consumer calls or
|
||||
overrides of the old two-argument tile `occlusionTest` and no `testOcclusion` name collisions. The frozen ABI floor
|
||||
likewise contains no direct reference to that tile descriptor. The legacy hook is nevertheless required by generated
|
||||
`TPartialOcclusionTile` behavior and by existing tile placement/replacement dispatch; it remains supported.
|
||||
|
||||
ProjectRed and ForgeRelocationFMP's `canReplacePart` callers need no rename and must retain outgoing-part exclusion.
|
||||
The new collection query is not a blanket replacement for placement checks or the part-level `occlusionTest` hook.
|
||||
`NormalOcclusionTest$.apply(Traversable, Traversable)` remains a separate box-list bridge used by ForgeRelocationFMP
|
||||
and OpenComputers. Its Java replacement is now explicit in the plan; completion of the tile query does not retire it.
|
||||
Evidence for this slice is under ignored `run/migration-part-occlusion-reference/`; reference checkouts remain unchanged.
|
||||
|
||||
## Practical priority for the current branch
|
||||
|
||||
1. Keep the existing `+678` ABI fixture as the exact binary floor and correct failures before source cleanup.
|
||||
|
||||
+20
-18
@@ -17,7 +17,7 @@ migration checkout; `codex/tile-compatibility-fixes` was deleted after its fixes
|
||||
|
||||
## Current state and next target
|
||||
|
||||
**549 plain-JVM tests and 242 Java 8 Forge tests pass, with zero failures/errors/skips.** Sources total **224 Java
|
||||
**556 plain-JVM tests and 243 Java 8 Forge tests pass, with zero failures/errors/skips.** Sources total **224 Java
|
||||
files and 9 Scala files / 747 nonblank Scala lines**. The packaged inventory has 443 classes.
|
||||
|
||||
Review follow-up: restored packet-scheduler callback mutation behavior with the original Scala hash-map traversal,
|
||||
@@ -32,34 +32,36 @@ use, but no override triggering these three regressions was found. All 443 class
|
||||
payloads and 116 generated dumps are retained. The agreed next milestone is the documented consumer-facing Java API,
|
||||
followed by consumer release/adoption and final Scala removal; see the plan's API migration design and Phases 8–10.
|
||||
|
||||
Latest API addition: `TileMultipart.setPartList(List)` copies Java list storage through the legacy setter, and
|
||||
`loadPartList(Collection)` delegates through the legacy Scala loader. Storage assignment and binding/cache rebuilding
|
||||
have separate documented contracts. Both old entries are deprecated while retaining their descriptors and bodies.
|
||||
Four JVM and one Forge baseline cases were committed first as `92273bd`; three further JVM cases, a Java example
|
||||
and two Forge cases cover the new methods on generated server/client tiles. All 546 pre-change compiled JVM tests
|
||||
pass with their recorded version. The 443 class APIs retain all existing members, with two methods added; 17
|
||||
ScalaSignature payloads, 3,748 existing method bodies and 116 generated dumps are unchanged apart from expected
|
||||
deprecation metadata and build-version literals. Evidence: `run/migration-part-loading-reference/`.
|
||||
Latest API addition: `TileMultipart.testOcclusion(Collection<? extends TMultiPart>, TMultiPart)` copies the input
|
||||
before dispatching through the legacy `occlusionTest(Seq, TMultiPart)` hook. Generated partial-occlusion checks remain
|
||||
in that path; the query is not full placement approval. Three JVM baseline cases and stronger existing Forge
|
||||
assertions were committed first as `6744f5b`. Four further JVM cases, a compiling shape example and one Forge
|
||||
generated-partial case cover the new entry. All 552 pre-change compiled JVM tests pass with their recorded version.
|
||||
All 443 class APIs retain their existing members, with one method added; 17 ScalaSignature payloads, 3,750 existing
|
||||
method bodies and 116 generated dumps are unchanged apart from expected deprecation metadata and build-version
|
||||
literals. Evidence: `run/migration-part-occlusion-reference/`; [guide](docs/api/OCCLUSION.md).
|
||||
|
||||
Naming correction: the planned `loadParts(Collection)` overload made javac require `scala.collection.Iterable` even
|
||||
for Java arguments. The distinct `loadPartList` name allows compilation without Scala on the example's classpath.
|
||||
Keep this compilation gate for remaining occlusion/registration APIs; checking only imports or bytecode is insufficient.
|
||||
Internal storage writes and reconstruction still dispatch through the old setter/loader hooks, not the Java siblings.
|
||||
|
||||
The [API index](docs/API.md) now orients consumers to entry points, compiling examples and migration status. Material
|
||||
enumeration and tile read/traversal access are also complete; use their guides rather than treating them as missing
|
||||
APIs. GuideNH and Schematica have Java loading replacements, but their source changes, other reflection/extension
|
||||
contracts, releases and pack adoption remain pending. The supplied checkouts remain reference-only. The installed
|
||||
`+719` pack scan retains the `+678` floor: 27 consumers, 35 inherited types, 255 members, 76 other types and 20 strings.
|
||||
The [API index](docs/API.md) orients consumers to entry points, compiling examples and migration status. Material
|
||||
enumeration and tile read/traversal/loading/storage APIs are also complete. No supplied consumer directly calls the
|
||||
two-argument tile occlusion hook; existing `canAddPart`/`canReplacePart` calls need no rename. GuideNH and Schematica
|
||||
have Java loading replacements, but consumer source changes, other reflection/extension contracts, releases and pack
|
||||
adoption remain pending. Checkouts remain reference-only. The installed `+719` pack scan retains the `+678` floor:
|
||||
27 consumers, 35 inherited types, 255 members, 76 other types and 20 strings.
|
||||
|
||||
**Next priority: close the Java API gaps in Phases 2 and 9 and map consumer migrations in Phase 10.** Reuse the existing
|
||||
Java surface; supply missing capabilities, precise contracts, migration guidance and compiling examples. Cover
|
||||
subclass/override behavior and generated extensions as well as ordinary calls. ProjectRed's illuminated microblocks
|
||||
are the representative external extension case. Consumer mods may remain Scala internally while adopting this API.
|
||||
The next bounded API candidate is Java collection-based occlusion testing. Verify legacy override dispatch, both
|
||||
directions of occlusion checks, ordering/failures, and compilation without Scala before choosing the Java entry name.
|
||||
The tentative same-name overload may have the same compiler constraint as loading. Registry registration guidance
|
||||
and bridges remain another pending API slice. FMP-side completion does not establish consumer adoption.
|
||||
The next bounded API candidate is the direct box-versus-box `NormalOcclusionTest` contract. Its public box-list entry
|
||||
still takes Scala `Traversable` on both the static class and companion, while the Java `test(Iterable, Iterable)` loop
|
||||
is private. ForgeRelocationFMP and OpenComputers use the Scala descriptor. Characterize eager input collection,
|
||||
ordering/failures and geometric boundaries before adding a distinctly named Java entry and deprecating the bridges.
|
||||
Verify compilation without Scala. Registry registration guidance and bridges remain another pending slice.
|
||||
|
||||
Pause mechanical extraction of retained Scala shells unless it enables that API, fixes a demonstrated issue or has
|
||||
a measured benefit. `ScalaSignature.ClassSymbolRef.info` remains an optional bounded extraction, not the default next
|
||||
|
||||
@@ -16,4 +16,4 @@ If you only want to use the API and not modify it:
|
||||
Implementation of all parts of the API should be clean and easy in Java. The minecraft multipart mod is implemented in java as a test case.
|
||||
|
||||
See the [Java API index](docs/API.md) for supported entry points, compiling examples, ownership/lifecycle contracts
|
||||
and migration status. It links to the material enumeration, part traversal and tile loading guides.
|
||||
and migration status. It links to material enumeration, part traversal, tile loading and occlusion guides.
|
||||
+3
-2
@@ -17,6 +17,7 @@ without converting the rest of its code to Java.
|
||||
| Run callbacks while skipping detached parts | `forEachPart(Consumer)` — [callback and override behavior](api/PART_TRAVERSAL.md#callback-behavior) |
|
||||
| Rebuild parts on an already prepared composite tile | `loadPartList(Collection)` — [part loading](api/PART_LOADING.md) |
|
||||
| Assign stored parts during reconstruction, without binding or notifications | `setPartList(List)` — [storage assignment](api/PART_LOADING.md#storage-assignment) |
|
||||
| Test a candidate against a selected collection of parts | `testOcclusion(Collection, candidate)` — [occlusion queries and generated hooks](api/OCCLUSION.md) |
|
||||
|
||||
Each guide explains ownership, lifecycle, legacy replacements and limitations, and links to a compiling Java example
|
||||
exercised by the test suite. The loading/setter APIs are advanced reconstruction operations; ordinary placement and
|
||||
@@ -46,8 +47,8 @@ Deprecated Scala-facing entry points remain callable, with their descriptors and
|
||||
Follow the method-specific guide: a Java sibling is not automatically a replacement override hook, and reflection
|
||||
must select the intended parameter types when a method is overloaded.
|
||||
|
||||
Java collection occlusion access, remaining registry bridges/documentation, generator/reflection replacements and
|
||||
complete external microblock extension guidance are still pending. In particular, ProjectRed's Scala microblock traits
|
||||
Direct box-versus-box Java occlusion access, remaining registry bridges/documentation, generator/reflection replacements
|
||||
and complete external microblock extension guidance are still pending. In particular, ProjectRed's Scala microblock traits
|
||||
remain a supported dependency; registration signatures alone do not prove a complete Java replacement.
|
||||
|
||||
FMP-side implementation is separate from consumer releases and target-pack adoption. Retiring legacy bridges or the
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
# Testing part occlusion from Java
|
||||
|
||||
[API index](../API.md)
|
||||
|
||||
The `algent/java` branch adds `TileMultipart.testOcclusion(Collection<? extends TMultiPart>, TMultiPart)`.
|
||||
It tests a supplied collection of parts through the tile's existing occlusion hook, including generated
|
||||
partial-occlusion checks. This addition is not yet tied to a minimum published dependency version.
|
||||
|
||||
## Example
|
||||
|
||||
Use a `NormallyOccludedPart` to test a normal bounding shape against a supplied group of parts:
|
||||
|
||||
```java
|
||||
public static boolean fitsShape(TileMultipart tile, Collection<? extends TMultiPart> parts, Cuboid6 bounds) {
|
||||
return tile.testOcclusion(parts, new NormallyOccludedPart(bounds));
|
||||
}
|
||||
```
|
||||
|
||||
The [complete Java example](../../src/test/java/codechicken/multipart/examples/OcclusionExample.java) compiles and runs
|
||||
in the test suite. It also compiles with Scala excluded from its classpath and has no Scala bytecode references.
|
||||
FMP still retains its Scala runtime internally. Collections of part subclasses, such as `List<NormallyOccludedPart>`,
|
||||
are accepted without a cast.
|
||||
|
||||
Pass `tile.jPartList()` to test against the tile's currently stored parts, or pass a separate collection for a preview
|
||||
or selected subset. To exercise a real candidate's custom occlusion behavior, pass that part instead of the shape
|
||||
wrapper. The wrapper represents normal bounding geometry, not all the capabilities of a custom part.
|
||||
|
||||
## What the query checks
|
||||
|
||||
The base tile tests each supplied part against the candidate, then tests the candidate against that same part. It
|
||||
proceeds in input iteration order and stops immediately when either direction rejects. Unbound/detached parts and
|
||||
duplicate entries participate; the candidate is not automatically excluded if it is also in the collection.
|
||||
|
||||
Generated partial-occlusion tiles retain their extra aggregate check. When the candidate implements `JPartialOcclusion`,
|
||||
the existing trait checks the group plus the candidate before the ordinary pair callbacks. A plain `TileMultipart`
|
||||
does not gain that capability just because the supplied parts need it. Use the appropriate composite tile; this method
|
||||
does not generate or select tile traits.
|
||||
|
||||
This is an occlusion query, not full placement approval. It does not check placement permissions, duplicate membership,
|
||||
slot occupancy or world replaceability, and does not bind/store parts or send notifications. For ordinary placement
|
||||
and replacement, continue using `canPlacePart`, `canAddPart` and `canReplacePart` as appropriate. In particular,
|
||||
`canReplacePart(outgoing, candidate)` performs its existing outgoing-part exclusion; this collection query does not.
|
||||
|
||||
## Ownership, callbacks and failures
|
||||
|
||||
The Java entry copies the input into an immutable sequence before calling the legacy hook. Order, duplicates, null
|
||||
entries and part identities are preserved. Callback edits to the caller's collection do not change the current
|
||||
query's sequence. Part objects are shared, so mutations of their state can affect later callbacks. A nested query
|
||||
takes its own copy.
|
||||
|
||||
Copying failures, including a null collection, propagate before the legacy hook is called. There is no eager
|
||||
candidate/null-entry validation: with the base implementation, an empty collection accepts even a null candidate,
|
||||
and an early rejection can avoid a later null dereference. Callback exceptions propagate unchanged and stop the query;
|
||||
callback side effects already performed are not undone. Overrides may add checks or customize these boundaries.
|
||||
|
||||
Use the game thread when querying live parts. A geometric query may invoke arbitrary part callbacks, including ones
|
||||
that read or modify world/part state; it is not a thread-safety guarantee.
|
||||
|
||||
## Overrides and migration
|
||||
|
||||
Dispatch is `testOcclusion(Collection, candidate)` → virtual `occlusionTest(scala.collection.Seq, candidate)`.
|
||||
The old method keeps its descriptor and body, and is deprecated for callers. Existing subclasses and generated traits
|
||||
continue to override it. `canAddPart` and `canReplacePart` still call that hook directly; overriding only the new
|
||||
Java method does not intercept their checks. The distinct Java name avoids introducing another Scala/Java overload
|
||||
pair, following the [loader compilation finding](PART_LOADING.md#overrides-and-reflection).
|
||||
|
||||
No direct consumer calls or overrides of the two-argument tile hook were found in the supplied source/ABI audit.
|
||||
Consumers such as ProjectRed and ForgeRelocationFMP already use the Java-typed `canReplacePart` API and need no rename
|
||||
for that contract. Part-level `occlusionTest(TMultiPart)` remains supported as-is. The Scala `NormalOcclusionTest.apply`
|
||||
box-versus-box entry is a separate, still-used contract whose Java replacement remains pending.
|
||||
|
||||
## Validation
|
||||
|
||||
JVM tests cover both directions, early rejection, original exceptions, null boundaries, snapshot ownership, duplicates,
|
||||
subtype/non-list collections, override routing and the shape example. A Forge test demonstrates that the generated
|
||||
partial trait rejects overlapping partial parts that plain pair tests accept. Existing descriptors, generated output,
|
||||
and frozen pre-change callers are checked separately. Actual client previews and full-pack adoption remain covered
|
||||
by the [manual release checks](../../JAVA_MIGRATION_MANUAL_CHECKS.md).
|
||||
@@ -1784,3 +1784,25 @@ differences belong in the [divergence ledger](../../JAVA_MIGRATION_DIVERGENCES.m
|
||||
- Added `docs/API.md` as the consumer entry point, a loading/storage guide, compiling examples, and reciprocal guide
|
||||
links. Updated the plan, ABI notes and GuideNH/Schematica adoption ledger; reference checkouts remain unchanged and
|
||||
consumer releases/pack adoption are pending. Evidence: `run/migration-part-loading-reference/`.
|
||||
|
||||
### 2026-09-05 — Java collection occlusion query
|
||||
|
||||
- Committed three JVM baseline cases and stronger generated-trait assertions first as `6744f5b`. They pin pair order,
|
||||
both rejection directions, lazy null behavior, original exceptions and replacement-hook dispatch. Saved the pre-API
|
||||
jar, source, reports, 116 generated dumps and all 552 compiled JVM tests before implementation.
|
||||
- Added `TileMultipart.testOcclusion(Collection<? extends TMultiPart>, TMultiPart)` as an adapter through the old
|
||||
virtual `occlusionTest(Seq, TMultiPart)`. It takes a shallow immutable snapshot, retains order/duplicates/identities,
|
||||
and accepts subtype collections. The distinct name avoids a Scala/Java overload pair. The legacy hook is deprecated
|
||||
for callers, with its descriptor and body unchanged; placement/replacement still dispatch directly through it.
|
||||
- Four new JVM cases and a compiling Java shape example cover snapshot/callback behavior, non-list/subtype inputs,
|
||||
failures, override routing and touching/overlapping bounds. One new Forge case proves aggregate partial-occlusion
|
||||
rejection through the generated trait when plain pair tests would accept the same parts.
|
||||
- Normal/clean build/style checks pass: 556 JVM tests, 243 Java 8 Forge tests and all 552 frozen pre-change JVM tests.
|
||||
The Java example compiles with Scala excluded from the classpath and contains no Scala bytecode references.
|
||||
All 443 existing class APIs, 17 ScalaSignature payloads, 3,750 existing method bodies and 116 generated dumps are
|
||||
retained, with exactly one added public method and one deprecation. Only expected deprecation metadata and build
|
||||
versions differ; no production class or dependency is added.
|
||||
- Added the occlusion guide and API index entry. No direct consumer use of the two-argument tile hook or collision
|
||||
with the new name was found. The separately load-bearing Scala box-list `NormalOcclusionTest.apply` contract now
|
||||
has an explicit plan row and is the next candidate. Reference checkouts remain unchanged; client previews and
|
||||
consumer release/adoption remain separate gates. Evidence: `run/migration-part-occlusion-reference/`.
|
||||
@@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@@ -587,6 +588,24 @@ class ForgeEnvironmentSmokeTest {
|
||||
assertFalse(tile.occlusionTest(tile.partList(), new PartialPart(voxel(0))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void javaOcclusionQueryDispatchesThroughTheGeneratedPartialTrait() {
|
||||
PartialPart existing = new PartialPart(voxel(0));
|
||||
PartialPart overlapping = new PartialPart(voxel(0));
|
||||
PartialPart disjoint = new PartialPart(voxel(1));
|
||||
TileMultipart tile = MultipartHelper.createTileFromParts(Collections.singletonList(existing));
|
||||
|
||||
assertTrue(
|
||||
new TileMultipart().testOcclusion(Collections.singletonList(existing), overlapping),
|
||||
"Pair tests alone allow these parts; the generated aggregate check must reject them");
|
||||
assertFalse(tile.testOcclusion(Collections.singletonList(existing), overlapping));
|
||||
assertTrue(tile.testOcclusion(Collections.singletonList(existing), disjoint));
|
||||
assertEquals(Collections.singletonList(existing), tile.jPartList());
|
||||
assertSame(tile, existing.tile());
|
||||
assertNull(overlapping.tile());
|
||||
assertNull(disjoint.tile());
|
||||
}
|
||||
|
||||
@Test
|
||||
void generatedServerPassThroughDelegatesCopiesAndAllowsOnlyOneImplementor() throws Exception {
|
||||
PassThroughPart part = new PassThroughPart();
|
||||
|
||||
@@ -415,7 +415,40 @@ public class TileMultipart extends TileEntity implements IChunkLoadTile {
|
||||
return occlusionTest(others, npart);
|
||||
}
|
||||
|
||||
/** Returns true if parts do not occlude npart. */
|
||||
/**
|
||||
* Tests a candidate against a shallow snapshot of the supplied parts through the legacy
|
||||
* {@link #occlusionTest(Seq, TMultiPart)} hook, including generated partial-occlusion overrides. Preserves
|
||||
* collection iteration order, duplicates and part identities; changes to the caller's collection during callbacks
|
||||
* do not change this query's sequence.
|
||||
*
|
||||
* <p>
|
||||
* The default hook calls each existing part's occlusion test first, then the candidate's reverse test, stopping
|
||||
* immediately on rejection or an exception. It includes detached parts and does not exclude the candidate itself.
|
||||
* Part objects remain shared, so callback changes to their state are visible. Existing overrides may add checks.
|
||||
*
|
||||
* <p>
|
||||
* This tests the supplied geometry, not full placement validity: it does not select tile capabilities, check
|
||||
* placement permissions or slot occupancy, bind parts or change stored parts. Use the appropriate composite tile
|
||||
* for the candidate's capabilities; use {@link #canAddPart(TMultiPart)} /
|
||||
* {@link #canReplacePart(TMultiPart, TMultiPart)} for normal tile checks. Those methods still dispatch directly
|
||||
* through the legacy hook.
|
||||
*
|
||||
* @param parts parts to copy before dispatch; null fails before calling the legacy hook
|
||||
* @param candidate part to test; no eager null check, so the default hook accepts null against an empty collection
|
||||
* @return true when all applicable occlusion checks allow the candidate
|
||||
*/
|
||||
public boolean testOcclusion(Collection<? extends TMultiPart> parts, TMultiPart candidate) {
|
||||
return occlusionTest(toSeq(new ArrayList<>(parts)), candidate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if parts do not occlude npart.
|
||||
*
|
||||
* @deprecated Call {@link #testOcclusion(Collection, TMultiPart)} from Java. Retained as the virtual hook for
|
||||
* existing subclasses, generated traits and tile placement/replacement checks; its sequence behavior is
|
||||
* unchanged.
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean occlusionTest(Seq<TMultiPart> parts, TMultiPart npart) {
|
||||
for (TMultiPart part : JavaConversions.seqAsJavaList(parts)) {
|
||||
if (!part.occlusionTest(npart) || !npart.occlusionTest(part)) {
|
||||
|
||||
@@ -9,10 +9,15 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import codechicken.lib.vec.Cuboid6;
|
||||
import codechicken.multipart.examples.OcclusionExample;
|
||||
import scala.collection.JavaConversions;
|
||||
import scala.collection.Seq;
|
||||
|
||||
@@ -85,6 +90,105 @@ class TileMultipartOcclusionTest {
|
||||
assertEquals(Arrays.asList(outgoing, retained), tile.jPartList());
|
||||
}
|
||||
|
||||
@Test
|
||||
void javaQuerySnapshotsTheSuppliedCollectionAndRetainsDuplicatesAndUnboundParts() {
|
||||
TileMultipart tile = new TileMultipart();
|
||||
List<String> calls = new ArrayList<>();
|
||||
Part stored = new Part("stored", calls);
|
||||
stored.accepts = false;
|
||||
tile.setPartList(Arrays.asList(stored));
|
||||
Part first = new Part("first", calls);
|
||||
Part second = new Part("second", calls);
|
||||
Part candidate = new Part("candidate", calls);
|
||||
List<Part> input = new ArrayList<>(Arrays.asList(first, second, first));
|
||||
first.onTest = input::clear;
|
||||
|
||||
assertTrue(tile.testOcclusion(input, candidate));
|
||||
assertEquals(
|
||||
Arrays.asList(
|
||||
"first:candidate",
|
||||
"candidate:first",
|
||||
"second:candidate",
|
||||
"candidate:second",
|
||||
"first:candidate",
|
||||
"candidate:first"),
|
||||
calls);
|
||||
assertTrue(input.isEmpty());
|
||||
assertEquals(
|
||||
Arrays.asList(stored),
|
||||
tile.jPartList(),
|
||||
"The query uses the supplied collection, not tile storage");
|
||||
assertNull(first.tile());
|
||||
assertNull(candidate.tile());
|
||||
}
|
||||
|
||||
@Test
|
||||
void javaQueryPreservesTheLegacyOverrideAndDoesNotReplaceTheInternalHook() {
|
||||
List<String> calls = new ArrayList<>();
|
||||
List<Seq<TMultiPart>> inputs = new ArrayList<>();
|
||||
TileMultipart tile = new TileMultipart() {
|
||||
|
||||
@Override
|
||||
public boolean occlusionTest(Seq<TMultiPart> parts, TMultiPart candidate) {
|
||||
calls.add("legacy");
|
||||
inputs.add(parts);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean testOcclusion(Collection<? extends TMultiPart> parts, TMultiPart candidate) {
|
||||
calls.add("java");
|
||||
return super.testOcclusion(parts, candidate);
|
||||
}
|
||||
};
|
||||
Part first = new Part("first", calls);
|
||||
Part second = new Part("second", calls);
|
||||
Part candidate = new Part("candidate", calls);
|
||||
assertFalse(tile.testOcclusion(new LinkedHashSet<>(Arrays.asList(first, second)), candidate));
|
||||
tile.setPartList(Arrays.asList(first, second));
|
||||
assertFalse(tile.canReplacePart(first, candidate));
|
||||
assertEquals(Arrays.asList("java", "legacy", "legacy"), calls);
|
||||
assertEquals(Arrays.asList(seq(first, second), seq(second)), inputs);
|
||||
}
|
||||
|
||||
@Test
|
||||
void javaQueryKeepsShortCircuitingAndCallbackFailureBoundaries() {
|
||||
TileMultipart tile = new TileMultipart();
|
||||
List<String> calls = new ArrayList<>();
|
||||
Part first = new Part("first", calls);
|
||||
Part candidate = new Part("candidate", calls);
|
||||
assertTrue(tile.testOcclusion(Collections.emptyList(), null));
|
||||
assertThrows(NullPointerException.class, () -> tile.testOcclusion(null, candidate));
|
||||
first.accepts = false;
|
||||
assertFalse(tile.testOcclusion(Arrays.asList(first, null), null));
|
||||
assertEquals(Arrays.asList("first:null"), calls);
|
||||
first.accepts = true;
|
||||
candidate.accepts = false;
|
||||
calls.clear();
|
||||
assertFalse(tile.testOcclusion(Arrays.asList(first, null), candidate));
|
||||
assertEquals(Arrays.asList("first:candidate", "candidate:first"), calls);
|
||||
calls.clear();
|
||||
IllegalStateException failure = new IllegalStateException("candidate callback");
|
||||
candidate.failure = failure;
|
||||
assertSame(
|
||||
failure,
|
||||
assertThrows(
|
||||
IllegalStateException.class,
|
||||
() -> tile.testOcclusion(Arrays.asList(first, null), candidate)));
|
||||
assertEquals(Arrays.asList("first:candidate", "candidate:first"), calls);
|
||||
}
|
||||
|
||||
@Test
|
||||
void javaShapeExampleDistinguishesTouchingFromOverlappingBounds() {
|
||||
TileMultipart tile = new TileMultipart();
|
||||
NormallyOccludedPart existing = new NormallyOccludedPart(new Cuboid6(0, 0, 0, 0.5, 1, 1));
|
||||
List<NormallyOccludedPart> input = Arrays.asList(existing);
|
||||
assertTrue(OcclusionExample.fitsShape(tile, input, new Cuboid6(0.5, 0, 0, 1, 1, 1)));
|
||||
assertFalse(OcclusionExample.fitsShape(tile, input, new Cuboid6(0.25, 0, 0, 1, 1, 1)));
|
||||
assertTrue(tile.jPartList().isEmpty());
|
||||
assertNull(existing.tile());
|
||||
}
|
||||
|
||||
private static Seq<TMultiPart> seq(TMultiPart... parts) {
|
||||
return JavaConversions.asScalaBuffer(Arrays.asList(parts)).toList();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package codechicken.multipart.examples;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import codechicken.lib.vec.Cuboid6;
|
||||
import codechicken.multipart.NormallyOccludedPart;
|
||||
import codechicken.multipart.TMultiPart;
|
||||
import codechicken.multipart.TileMultipart;
|
||||
|
||||
/** Compiling example for docs/api/OCCLUSION.md; this checks geometry, not full placement validity. */
|
||||
public final class OcclusionExample {
|
||||
|
||||
private OcclusionExample() {}
|
||||
|
||||
public static boolean fitsShape(TileMultipart tile, Collection<? extends TMultiPart> parts, Cuboid6 bounds) {
|
||||
return tile.testOcclusion(parts, new NormallyOccludedPart(bounds));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user