diff --git a/src/functionalTest/java/codechicken/multipart/test/ForgeEnvironmentSmokeTest.java b/src/functionalTest/java/codechicken/multipart/test/ForgeEnvironmentSmokeTest.java index b86c64b..afda930 100644 --- a/src/functionalTest/java/codechicken/multipart/test/ForgeEnvironmentSmokeTest.java +++ b/src/functionalTest/java/codechicken/multipart/test/ForgeEnvironmentSmokeTest.java @@ -39,6 +39,7 @@ import codechicken.multipart.TMultiPart; import codechicken.multipart.TileMultipart; import codechicken.multipart.asm.MultipartMixinFactory; import codechicken.multipart.scalatraits.TSlottedTile; +import codechicken.multipart.scalatraits.TTileChangeTile; import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.Loader; import scala.collection.JavaConversions; @@ -214,6 +215,52 @@ class ForgeEnvironmentSmokeTest { assertSame(replacement, trait.getMethod("v_partMap").invoke(first)); } + @Test + void generatesAndCachesTileChangeTileClass() throws Exception { + int traitId = MultipartMixinFactory.getId(TTileChangeTile.class.getName().replace('.', '/')); + assertFalse(traitId < 0); + + BitSet traits = new BitSet(); + traits.set(traitId); + Object first = MultipartMixinFactory.construct(traits, Nil$.MODULE$); + Object second = MultipartMixinFactory.construct(traits, Nil$.MODULE$); + Class trait = Class.forName("codechicken.multipart.scalatraits.TTileChangeTile"); + + assertTrue(first instanceof TileMultipart); + assertTrue(first instanceof TTileChangeTile); + assertTrue(trait.isInterface()); + assertNotSame(first, second); + assertSame(first.getClass(), second.getClass()); + assertEquals(0, trait.getDeclaredFields().length); + Set signatures = new TreeSet<>(); + for (java.lang.reflect.Method method : trait.getDeclaredMethods()) { + signatures.add(method.getName() + org.objectweb.asm.Type.getMethodDescriptor(method)); + } + String prefix = "codechicken$multipart$scalatraits$TTileChangeTile$$super$"; + assertEquals( + new TreeSet<>( + Arrays.asList( + "bindPart(Lcodechicken/multipart/TMultiPart;)V", + "clearParts()V", + "copyFrom(Lcodechicken/multipart/TileMultipart;)V", + "getWeakChanges()Z", + "onNeighborTileChange(III)V", + "partRemoved(Lcodechicken/multipart/TMultiPart;I)V", + "weakTileChanges()Z", + "weakTileChanges_$eq(Z)V", + prefix + "bindPart(Lcodechicken/multipart/TMultiPart;)V", + prefix + "clearParts()V", + prefix + "copyFrom(Lcodechicken/multipart/TileMultipart;)V", + prefix + "onNeighborTileChange(III)V", + prefix + "partRemoved(Lcodechicken/multipart/TMultiPart;I)V")), + signatures); + + assertEquals(boolean.class, first.getClass().getDeclaredField("weakTileChanges").getType()); + trait.getMethod("weakTileChanges_$eq", boolean.class).invoke(first, true); + assertEquals(true, trait.getMethod("weakTileChanges").invoke(first)); + assertEquals(false, trait.getMethod("weakTileChanges").invoke(second), "State is per tile"); + } + @Test void generatedJavaTraitOverridesPartialOcclusion() throws Exception { PartialPart existing = new PartialPart(voxel(0)); diff --git a/src/functionalTest/java/codechicken/multipart/test/TTileChangeTileFunctionalTest.java b/src/functionalTest/java/codechicken/multipart/test/TTileChangeTileFunctionalTest.java new file mode 100644 index 0000000..d608e76 --- /dev/null +++ b/src/functionalTest/java/codechicken/multipart/test/TTileChangeTileFunctionalTest.java @@ -0,0 +1,174 @@ +package codechicken.multipart.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import codechicken.multipart.INeighborTileChange; +import codechicken.multipart.TMultiPart; +import codechicken.multipart.TileMultipart; +import codechicken.multipart.asm.MultipartMixinFactory; +import codechicken.multipart.scalatraits.TTileChangeTile; +import scala.collection.immutable.Nil$; + +/** Consumer-visible state and behavior of the generated neighbor-tile-change trait. */ +class TTileChangeTileFunctionalTest { + + @Test + void theFlagStartsFalseAndIsPublishedThroughGetWeakChanges() { + TileMultipart tile = newTileChangeTile(); + + assertFalse(weakTileChanges(tile)); + assertFalse(tile.getWeakChanges()); + } + + @Test + void bindPartOnlyEverSetsTheFlag() { + TileMultipart tile = newTileChangeTile(); + + tile.bindPart(new PlainPart()); + assertFalse(tile.getWeakChanges(), "A part that is not an INeighborTileChange contributes nothing"); + + tile.bindPart(new ChangePart("quiet", false)); + assertFalse(tile.getWeakChanges()); + + tile.bindPart(new ChangePart("weak", true)); + assertTrue(tile.getWeakChanges()); + + // The flag is or-ed in, so a later quiet part cannot clear it. + tile.bindPart(new ChangePart("quiet2", false)); + assertTrue(tile.getWeakChanges()); + } + + @Test + void copyFromTakesTheFlagOnlyFromAnotherChangeTile() { + TileMultipart source = newTileChangeTile(); + source.bindPart(new ChangePart("weak", true)); + TileMultipart target = newTileChangeTile(); + + target.copyFrom(source); + assertTrue(target.getWeakChanges()); + + // A plain source leaves the existing value alone rather than clearing it. + target.copyFrom(new TileMultipart()); + assertTrue(target.getWeakChanges()); + } + + @Test + void clearPartsResetsTheFlagAndTheSuperclassList() { + TileMultipart tile = newTileChangeTile(); + tile.addPart_do(new ChangePart("weak", true)); + assertTrue(tile.getWeakChanges()); + + tile.clearParts(); + + assertFalse(tile.getWeakChanges()); + assertTrue(tile.jPartList().isEmpty()); + } + + @Test + void partRemovedRecomputesTheFlagFromTheRemainingParts() { + TileMultipart tile = newTileChangeTile(); + ChangePart weak = new ChangePart("weak", true); + ChangePart quiet = new ChangePart("quiet", false); + tile.addPart_do(weak); + tile.addPart_do(quiet); + assertTrue(tile.getWeakChanges()); + + // partRemoved recomputes from the list, so the part must already be gone from it. + tile.partList_$eq(seq(quiet)); + tile.partRemoved(weak, 0); + assertFalse(tile.getWeakChanges()); + + tile.partList_$eq(seq(quiet, weak)); + tile.partRemoved(quiet, 0); + assertTrue(tile.getWeakChanges(), "A remaining weak part keeps the flag set"); + } + + @Test + void neighborChangesDispatchOnlyWithinTwoBlocksAlongOneAxis() { + TileMultipart tile = newTileChangeTile(); + ChangePart part = new ChangePart("weak", true); + tile.addPart_do(part); + + // The tile sits at the origin, so the neighbor coordinates are the offset. + tile.onNeighborTileChange(1, 0, 0); + tile.onNeighborTileChange(0, -2, 0); + assertEquals(Arrays.asList("5:false", "0:true"), part.calls); + + part.calls.clear(); + tile.onNeighborTileChange(0, 0, 0); // no distance + tile.onNeighborTileChange(3, 0, 0); // too far + tile.onNeighborTileChange(1, 1, 0); // not axial, so no side + assertTrue(part.calls.isEmpty()); + } + + @Test + void onlyPartsWantingNeighborChangesAreCalled() { + TileMultipart tile = newTileChangeTile(); + ChangePart listening = new ChangePart("weak", true); + tile.addPart_do(new PlainPart()); + tile.addPart_do(listening); + + tile.onNeighborTileChange(0, 1, 0); + + assertEquals(Arrays.asList("1:false"), listening.calls); + } + + private static TileMultipart newTileChangeTile() { + int traitId = MultipartMixinFactory.getId(TTileChangeTile.class.getName().replace('.', '/')); + BitSet traits = new BitSet(); + traits.set(traitId); + return (TileMultipart) MultipartMixinFactory.construct(traits, Nil$.MODULE$); + } + + private static boolean weakTileChanges(TileMultipart tile) { + return ((TTileChangeTile) tile).weakTileChanges(); + } + + private static scala.collection.Seq seq(TMultiPart... parts) { + return scala.collection.JavaConversions.asScalaBuffer(new ArrayList<>(Arrays.asList(parts))).toList(); + } + + private static class PlainPart extends TMultiPart { + + @Override + public String getType() { + return "tilechange_test:plain"; + } + } + + private static final class ChangePart extends PlainPart implements INeighborTileChange { + + private final String type; + private final boolean weak; + private final List calls = new ArrayList<>(); + + private ChangePart(String type, boolean weak) { + this.type = type; + this.weak = weak; + } + + @Override + public String getType() { + return "tilechange_test:" + type; + } + + @Override + public boolean weakTileChanges() { + return weak; + } + + @Override + public void onNeighborTileChanged(int side, boolean weak) { + calls.add(side + ":" + weak); + } + } +}