From a6a33c2341024b0cb632286cc31b13177db35503 Mon Sep 17 00:00:00 2001 From: Up Date: Tue, 31 Dec 2024 16:35:28 +0100 Subject: [PATCH 1/4] add test cases --- .../block/StaticBlockComponentPlugin.java | 11 +++-- .../cca/test/block/CcaBlockTestMod.java | 6 +-- .../cca/test/block/CcaBlockTestSuite.java | 45 ++++++++++++++++- .../test/block/GlobalTickingComponent.java | 48 +++++++++++++++++++ .../src/testmod/resources/fabric.mod.json | 3 +- 5 files changed, 103 insertions(+), 10 deletions(-) create mode 100644 cardinal-components-block/src/testmod/java/dev/onyxstudios/cca/test/block/GlobalTickingComponent.java diff --git a/cardinal-components-block/src/main/java/dev/onyxstudios/cca/internal/block/StaticBlockComponentPlugin.java b/cardinal-components-block/src/main/java/dev/onyxstudios/cca/internal/block/StaticBlockComponentPlugin.java index ed7ed6af..7f832f96 100644 --- a/cardinal-components-block/src/main/java/dev/onyxstudios/cca/internal/block/StaticBlockComponentPlugin.java +++ b/cardinal-components-block/src/main/java/dev/onyxstudios/cca/internal/block/StaticBlockComponentPlugin.java @@ -40,6 +40,7 @@ import net.minecraft.block.entity.BlockEntityTicker; import net.minecraft.world.World; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.Collections; @@ -63,8 +64,10 @@ private StaticBlockComponentPlugin() { private final List> dynamicFactories = new ArrayList<>(); private final Map, Map, QualifiedComponentFactory>>> beComponentFactories = new Reference2ObjectOpenHashMap<>(); - private final Set> clientTicking = new ReferenceOpenHashSet<>(); - private final Set> serverTicking = new ReferenceOpenHashSet<>(); + @VisibleForTesting + public final Set> clientTicking = new ReferenceOpenHashSet<>(); + @VisibleForTesting + public final Set> serverTicking = new ReferenceOpenHashSet<>(); @Nullable public BlockEntityTicker getComponentTicker(World world, T be, @Nullable BlockEntityTicker base) { @@ -97,12 +100,12 @@ public boolean requiresStaticFactory(Class entityClass) { public ComponentContainer.Factory buildDedicatedFactory(Class entityClass) { StaticBlockComponentPlugin.INSTANCE.ensureInitialized(); - var compiled = new LinkedHashMap<>(this.beComponentFactories.getOrDefault(entityClass, Collections.emptyMap())); + var compiled = new LinkedHashMap<>(this.beComponentFactories.getOrDefault(entityClass, Map.of())); Class type = entityClass; while (type != BlockEntity.class) { type = type.getSuperclass().asSubclass(BlockEntity.class); - for (var e : this.beComponentFactories.getOrDefault(type, Collections.emptyMap()).entrySet()) { + for (var e : this.beComponentFactories.getOrDefault(type, Map.of()).entrySet()) { compiled.putIfAbsent(e.getKey(), e.getValue()); } } diff --git a/cardinal-components-block/src/testmod/java/dev/onyxstudios/cca/test/block/CcaBlockTestMod.java b/cardinal-components-block/src/testmod/java/dev/onyxstudios/cca/test/block/CcaBlockTestMod.java index 5fca1c0f..8cf4cd58 100644 --- a/cardinal-components-block/src/testmod/java/dev/onyxstudios/cca/test/block/CcaBlockTestMod.java +++ b/cardinal-components-block/src/testmod/java/dev/onyxstudios/cca/test/block/CcaBlockTestMod.java @@ -30,10 +30,7 @@ import dev.onyxstudios.cca.test.base.Vita; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup; -import net.minecraft.block.entity.BlockEntityType; -import net.minecraft.block.entity.CommandBlockBlockEntity; -import net.minecraft.block.entity.EndGatewayBlockEntity; -import net.minecraft.block.entity.EndPortalBlockEntity; +import net.minecraft.block.entity.*; import net.minecraft.util.Identifier; import net.minecraft.util.math.Direction; @@ -46,6 +43,7 @@ public void registerBlockComponentFactories(BlockComponentFactoryRegistry regist registry.registerFor(EndGatewayBlockEntity.class, VitaCompound.KEY, VitaCompound::new); registry.registerFor(EndPortalBlockEntity.class, TickingTestComponent.KEY, be -> new TickingTestComponent()); registry.registerFor(CommandBlockBlockEntity.class, LoadAwareTestComponent.KEY, be -> new LoadAwareTestComponent()); + registry.registerFor(BlockEntity.class, GlobalTickingComponent.KEY, GlobalTickingComponent::new); } @Override diff --git a/cardinal-components-block/src/testmod/java/dev/onyxstudios/cca/test/block/CcaBlockTestSuite.java b/cardinal-components-block/src/testmod/java/dev/onyxstudios/cca/test/block/CcaBlockTestSuite.java index 458efafd..d144170e 100644 --- a/cardinal-components-block/src/testmod/java/dev/onyxstudios/cca/test/block/CcaBlockTestSuite.java +++ b/cardinal-components-block/src/testmod/java/dev/onyxstudios/cca/test/block/CcaBlockTestSuite.java @@ -22,6 +22,7 @@ */ package dev.onyxstudios.cca.test.block; +import dev.onyxstudios.cca.internal.block.StaticBlockComponentPlugin; import dev.onyxstudios.cca.test.base.LoadAwareTestComponent; import dev.onyxstudios.cca.test.base.TickingTestComponent; import dev.onyxstudios.cca.test.base.Vita; @@ -39,6 +40,8 @@ import org.jetbrains.annotations.NotNull; import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BooleanSupplier; public class CcaBlockTestSuite implements FabricGameTest { @GameTest(templateName = EMPTY_STRUCTURE) @@ -92,8 +95,17 @@ public void canQueryThroughLookup(TestContext ctx) { @GameTest(templateName = EMPTY_STRUCTURE) public void beComponentsTick(TestContext ctx) { ctx.setBlockState(BlockPos.ORIGIN, Blocks.END_PORTAL); + + var blockentity = ctx.getBlockEntity(BlockPos.ORIGIN); + GameTestUtil.assertTrue("Block entity should not be null", blockentity != null); + GameTestUtil.assertTrue("BlockEntity should have TickingTestComponent", TickingTestComponent.KEY.getNullable(blockentity) != null); + GameTestUtil.assertTrue("Class should be registered as server ticker", StaticBlockComponentPlugin.INSTANCE.serverTicking.contains(blockentity.getClass())); + GameTestUtil.assertTrue("Class should be registered as client ticker", StaticBlockComponentPlugin.INSTANCE.clientTicking.contains(blockentity.getClass())); + ctx.waitAndRun(5, () -> { - int ticks = Objects.requireNonNull(ctx.getBlockEntity(BlockPos.ORIGIN)).getComponent(TickingTestComponent.KEY).serverTicks(); + var blockentity2 = ctx.getBlockEntity(BlockPos.ORIGIN); + GameTestUtil.assertTrue("Block entity should still exist", blockentity2 != null); + int ticks = blockentity2.getComponent(TickingTestComponent.KEY).serverTicks(); GameTestUtil.assertTrue("Component should tick 5 times", ticks == 5); ctx.complete(); }); @@ -121,4 +133,35 @@ public void beComponentsLoadUnload(TestContext ctx) { ctx.complete(); }); } + + @GameTest(templateName = EMPTY_STRUCTURE) + public void rootClassServerTicker(TestContext ctx) { + ctx.setBlockState(BlockPos.ORIGIN, Blocks.BARREL); + + var blockentity = ctx.getBlockEntity(BlockPos.ORIGIN); + GameTestUtil.assertTrue("Block entity should not be null", blockentity != null); + + GameTestUtil.assertTrue("Class should be registered as server ticker", StaticBlockComponentPlugin.INSTANCE.serverTicking.contains(blockentity.getClass())); + GameTestUtil.assertFalse("Class should NOT be registered as client ticker", StaticBlockComponentPlugin.INSTANCE.clientTicking.contains(blockentity.getClass())); + + var component = GlobalTickingComponent.KEY.getNullable(blockentity); + GameTestUtil.assertTrue("Component should exist", component != null); + + AtomicInteger flag = new AtomicInteger(0); + BooleanSupplier action = () -> { + flag.getAndIncrement(); + return false; + }; + component.setTickAction(action); + GameTestUtil.assertTrue("Tick action should be set", component.getTickAction().isPresent()); + + ctx.waitAndRun(5, () -> { + var blockentity2 = ctx.getBlockEntity(BlockPos.ORIGIN); + GameTestUtil.assertTrue("Block entity should still exist", blockentity2 != null); + GameTestUtil.assertTrue("Tick action should be cleared", blockentity2.getComponent(GlobalTickingComponent.KEY).getTickAction().isEmpty()); + GameTestUtil.assertTrue("Tick action should have run exactly once", flag.get() == 1); + + ctx.complete(); + }); + } } diff --git a/cardinal-components-block/src/testmod/java/dev/onyxstudios/cca/test/block/GlobalTickingComponent.java b/cardinal-components-block/src/testmod/java/dev/onyxstudios/cca/test/block/GlobalTickingComponent.java new file mode 100644 index 00000000..43b6b53b --- /dev/null +++ b/cardinal-components-block/src/testmod/java/dev/onyxstudios/cca/test/block/GlobalTickingComponent.java @@ -0,0 +1,48 @@ +package dev.onyxstudios.cca.test.block; + +import dev.onyxstudios.cca.api.v3.component.Component; +import dev.onyxstudios.cca.api.v3.component.ComponentKey; +import dev.onyxstudios.cca.api.v3.component.ComponentRegistry; +import dev.onyxstudios.cca.api.v3.component.tick.ServerTickingComponent; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.util.Identifier; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; +import java.util.function.BooleanSupplier; + +public class GlobalTickingComponent implements Component, ServerTickingComponent { + + public static final ComponentKey KEY = ComponentRegistry.getOrCreate(new Identifier(CcaBlockTestMod.MOD_ID, "global_ticking_test"), GlobalTickingComponent.class); + + public GlobalTickingComponent(Object provider) { + // NO-OP + } + + @Nullable + private BooleanSupplier onTick; + + void setTickAction(@Nullable BooleanSupplier onTick) { + this.onTick = onTick; + } + + @Override public void serverTick() { + if(this.onTick != null) { + if(!this.onTick.getAsBoolean()) { + this.onTick = null; + } + } + } + + public Optional getTickAction() { + return Optional.ofNullable(this.onTick); + } + + @Override public void readFromNbt(NbtCompound tag) { + // NO-OP + } + + @Override public void writeToNbt(NbtCompound tag) { + // NO-OP + } +} diff --git a/cardinal-components-block/src/testmod/resources/fabric.mod.json b/cardinal-components-block/src/testmod/resources/fabric.mod.json index 4190d2a1..ed7fdf14 100644 --- a/cardinal-components-block/src/testmod/resources/fabric.mod.json +++ b/cardinal-components-block/src/testmod/resources/fabric.mod.json @@ -25,7 +25,8 @@ "license": "MIT", "custom": { "cardinal-components": [ - "cca-block-test:vita_compound" + "cca-block-test:vita_compound", + "cca-block-test:global_ticking_test" ] } } From 4ce8c3764c9fbe685e3918845e1faa5c05d1c52a Mon Sep 17 00:00:00 2001 From: Up Date: Tue, 31 Dec 2024 22:33:55 +0100 Subject: [PATCH 2/4] add child classes to ticking lists --- .../cca/internal/block/CardinalBlockInternals.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cardinal-components-block/src/main/java/dev/onyxstudios/cca/internal/block/CardinalBlockInternals.java b/cardinal-components-block/src/main/java/dev/onyxstudios/cca/internal/block/CardinalBlockInternals.java index 7c8ff664..01cd5778 100644 --- a/cardinal-components-block/src/main/java/dev/onyxstudios/cca/internal/block/CardinalBlockInternals.java +++ b/cardinal-components-block/src/main/java/dev/onyxstudios/cca/internal/block/CardinalBlockInternals.java @@ -56,6 +56,10 @@ private static synchronized ComponentContainer.Factory getBeCompone @SuppressWarnings("unchecked") var superclass = (Class) entityClass.getSuperclass(); assert BlockEntity.class.isAssignableFrom(superclass) : "requiresStaticFactory returned false on BlockEntity?"; factory = /* recursive call */ getBeComponentFactory(superclass); + + // if parent class needs to tick, this one does, too! + if(StaticBlockComponentPlugin.INSTANCE.clientTicking.contains(superclass)) StaticBlockComponentPlugin.INSTANCE.clientTicking.add(entityClass); + if(StaticBlockComponentPlugin.INSTANCE.serverTicking.contains(superclass)) StaticBlockComponentPlugin.INSTANCE.serverTicking.add(entityClass); } entityContainerFactories.put(entityClass, factory); return factory; From 26e225bc9f83c46c0f508a3255773c24fc500e89 Mon Sep 17 00:00:00 2001 From: Up Date: Mon, 6 Jan 2025 00:52:51 +0100 Subject: [PATCH 3/4] add another test case --- .../cca/test/block/CcaBlockTestSuite.java | 75 ++++++++----------- 1 file changed, 31 insertions(+), 44 deletions(-) diff --git a/cardinal-components-block/src/testmod/java/dev/onyxstudios/cca/test/block/CcaBlockTestSuite.java b/cardinal-components-block/src/testmod/java/dev/onyxstudios/cca/test/block/CcaBlockTestSuite.java index d144170e..d245d6fe 100644 --- a/cardinal-components-block/src/testmod/java/dev/onyxstudios/cca/test/block/CcaBlockTestSuite.java +++ b/cardinal-components-block/src/testmod/java/dev/onyxstudios/cca/test/block/CcaBlockTestSuite.java @@ -44,44 +44,24 @@ import java.util.function.BooleanSupplier; public class CcaBlockTestSuite implements FabricGameTest { - @GameTest(templateName = EMPTY_STRUCTURE) - public void beSerialize(TestContext ctx) { + @GameTest(templateName = EMPTY_STRUCTURE) public void beSerialize(TestContext ctx) { BlockPos pos = ctx.getAbsolutePos(BlockPos.ORIGIN); - BlockEntity be = Objects.requireNonNull( - BlockEntityType.END_GATEWAY.instantiate( - pos, - Blocks.END_GATEWAY.getDefaultState() - ) - ); + BlockEntity be = Objects.requireNonNull(BlockEntityType.END_GATEWAY.instantiate(pos, Blocks.END_GATEWAY.getDefaultState())); be.getComponent(Vita.KEY).setVitality(42); NbtCompound nbt = be.createNbt(); - BlockEntity be1 = Objects.requireNonNull( - BlockEntityType.END_GATEWAY.instantiate( - pos, Blocks.END_GATEWAY.getDefaultState() - ) - ); + BlockEntity be1 = Objects.requireNonNull(BlockEntityType.END_GATEWAY.instantiate(pos, Blocks.END_GATEWAY.getDefaultState())); GameTestUtil.assertTrue("New BlockEntity should have values zeroed", be1.getComponent(Vita.KEY).getVitality() == 0); be1.readNbt(nbt); GameTestUtil.assertTrue("BlockEntity component data should survive deserialization", be1.getComponent(Vita.KEY).getVitality() == 42); ctx.complete(); } - @GameTest(templateName = EMPTY_STRUCTURE) - public void canQueryThroughLookup(TestContext ctx) { + @GameTest(templateName = EMPTY_STRUCTURE) public void canQueryThroughLookup(TestContext ctx) { BlockPos pos = ctx.getAbsolutePos(BlockPos.ORIGIN); - BlockEntity be = Objects.requireNonNull( - BlockEntityType.END_GATEWAY.instantiate( - pos, - Blocks.END_GATEWAY.getDefaultState() - ) - ); + BlockEntity be = Objects.requireNonNull(BlockEntityType.END_GATEWAY.instantiate(pos, Blocks.END_GATEWAY.getDefaultState())); getVita(ctx, pos, be).setVitality(42); NbtCompound nbt = be.createNbt(); - BlockEntity be1 = Objects.requireNonNull( - BlockEntityType.END_GATEWAY.instantiate( - pos, Blocks.END_GATEWAY.getDefaultState() - ) - ); + BlockEntity be1 = Objects.requireNonNull(BlockEntityType.END_GATEWAY.instantiate(pos, Blocks.END_GATEWAY.getDefaultState())); GameTestUtil.assertTrue("New BlockEntity should have values zeroed", getVita(ctx, pos, be1).getVitality() == 0); be1.readNbt(nbt); GameTestUtil.assertTrue("BlockEntity component data should survive deserialization", getVita(ctx, pos, be1).getVitality() == 42); @@ -92,8 +72,7 @@ public void canQueryThroughLookup(TestContext ctx) { return Objects.requireNonNull(CcaBlockTestMod.VITA_API_LOOKUP.find(ctx.getWorld(), pos, null, be, Direction.DOWN)); } - @GameTest(templateName = EMPTY_STRUCTURE) - public void beComponentsTick(TestContext ctx) { + @GameTest(templateName = EMPTY_STRUCTURE) public void beComponentsTick(TestContext ctx) { ctx.setBlockState(BlockPos.ORIGIN, Blocks.END_PORTAL); var blockentity = ctx.getBlockEntity(BlockPos.ORIGIN); @@ -111,31 +90,20 @@ public void beComponentsTick(TestContext ctx) { }); } - @GameTest(templateName = EMPTY_STRUCTURE) - public void beComponentsLoadUnload(TestContext ctx) { + @GameTest(templateName = EMPTY_STRUCTURE) public void beComponentsLoadUnload(TestContext ctx) { BlockEntity firstCommandBlock = new CommandBlockBlockEntity(ctx.getAbsolutePos(BlockPos.ORIGIN), Blocks.CHAIN_COMMAND_BLOCK.getDefaultState()); - GameTestUtil.assertTrue( - "Load counter should not be incremented until the block entity joins the world", - LoadAwareTestComponent.KEY.get(firstCommandBlock).getLoadCounter() == 0 - ); + GameTestUtil.assertTrue("Load counter should not be incremented until the block entity joins the world", LoadAwareTestComponent.KEY.get(firstCommandBlock).getLoadCounter() == 0); ctx.setBlockState(BlockPos.ORIGIN, Blocks.CHAIN_COMMAND_BLOCK); BlockEntity commandBlock = Objects.requireNonNull(ctx.getBlockEntity(BlockPos.ORIGIN)); - GameTestUtil.assertTrue( - "Load counter should be incremented once when the block entity joins the world", - LoadAwareTestComponent.KEY.get(commandBlock).getLoadCounter() == 1 - ); + GameTestUtil.assertTrue("Load counter should be incremented once when the block entity joins the world", LoadAwareTestComponent.KEY.get(commandBlock).getLoadCounter() == 1); ctx.setBlockState(BlockPos.ORIGIN, Blocks.AIR); ctx.waitAndRun(1, () -> { - GameTestUtil.assertTrue( - "Load counter should be decremented when the block entity leaves the world", - LoadAwareTestComponent.KEY.get(commandBlock).getLoadCounter() == 0 - ); + GameTestUtil.assertTrue("Load counter should be decremented when the block entity leaves the world", LoadAwareTestComponent.KEY.get(commandBlock).getLoadCounter() == 0); ctx.complete(); }); } - @GameTest(templateName = EMPTY_STRUCTURE) - public void rootClassServerTicker(TestContext ctx) { + @GameTest(templateName = EMPTY_STRUCTURE) public void rootClassServerTicker(TestContext ctx) { ctx.setBlockState(BlockPos.ORIGIN, Blocks.BARREL); var blockentity = ctx.getBlockEntity(BlockPos.ORIGIN); @@ -164,4 +132,23 @@ public void rootClassServerTicker(TestContext ctx) { ctx.complete(); }); } + + /** + * same as {@link CcaBlockTestSuite#rootClassServerTicker(TestContext)} but for a BlockEntity that has an explicit + * component registered, so that {@link StaticBlockComponentPlugin#requiresStaticFactory(Class)} returns true for + * the class itself rather than delegating to the parent class. + */ + @GameTest(templateName = EMPTY_STRUCTURE) + public void rootClassServerTickerWithExplicitRegistration(TestContext ctx) { + ctx.setBlockState(BlockPos.ORIGIN, Blocks.COMMAND_BLOCK); + + var blockentity = ctx.getBlockEntity(BlockPos.ORIGIN); + GameTestUtil.assertTrue("Block entity should not be null", blockentity != null); + GameTestUtil.assertTrue("Class should be registered as server ticker", StaticBlockComponentPlugin.INSTANCE.serverTicking.contains(blockentity.getClass())); + + var component = GlobalTickingComponent.KEY.getNullable(blockentity); + GameTestUtil.assertTrue("Component should exist", component != null); + + ctx.complete(); + } } From db91ba311b7ca73cae90a22043d0d0b809041c88 Mon Sep 17 00:00:00 2001 From: Up Date: Mon, 6 Jan 2025 01:05:51 +0100 Subject: [PATCH 4/4] dont access ticking maps from outside --- .../cca/internal/block/CardinalBlockInternals.java | 3 +-- .../cca/internal/block/StaticBlockComponentPlugin.java | 9 +++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/cardinal-components-block/src/main/java/dev/onyxstudios/cca/internal/block/CardinalBlockInternals.java b/cardinal-components-block/src/main/java/dev/onyxstudios/cca/internal/block/CardinalBlockInternals.java index 01cd5778..454976ab 100644 --- a/cardinal-components-block/src/main/java/dev/onyxstudios/cca/internal/block/CardinalBlockInternals.java +++ b/cardinal-components-block/src/main/java/dev/onyxstudios/cca/internal/block/CardinalBlockInternals.java @@ -58,8 +58,7 @@ private static synchronized ComponentContainer.Factory getBeCompone factory = /* recursive call */ getBeComponentFactory(superclass); // if parent class needs to tick, this one does, too! - if(StaticBlockComponentPlugin.INSTANCE.clientTicking.contains(superclass)) StaticBlockComponentPlugin.INSTANCE.clientTicking.add(entityClass); - if(StaticBlockComponentPlugin.INSTANCE.serverTicking.contains(superclass)) StaticBlockComponentPlugin.INSTANCE.serverTicking.add(entityClass); + StaticBlockComponentPlugin.INSTANCE.registerTickersFor(entityClass, superclass); } entityContainerFactories.put(entityClass, factory); return factory; diff --git a/cardinal-components-block/src/main/java/dev/onyxstudios/cca/internal/block/StaticBlockComponentPlugin.java b/cardinal-components-block/src/main/java/dev/onyxstudios/cca/internal/block/StaticBlockComponentPlugin.java index 7f832f96..9613a18d 100644 --- a/cardinal-components-block/src/main/java/dev/onyxstudios/cca/internal/block/StaticBlockComponentPlugin.java +++ b/cardinal-components-block/src/main/java/dev/onyxstudios/cca/internal/block/StaticBlockComponentPlugin.java @@ -122,6 +122,15 @@ public ComponentContainer.Factory buildDedicatedFactory(Class entityClass, Class parentClass) { + if(this.clientTicking.contains(parentClass)) { + this.clientTicking.add(entityClass); + } + if(this.serverTicking.contains(parentClass)) { + this.serverTicking.add(entityClass); + } + } + private void addToBuilder(ComponentContainer.Factory.Builder builder, Map.Entry, QualifiedComponentFactory>> entry) { @SuppressWarnings("unchecked") var key = (ComponentKey) entry.getKey(); @SuppressWarnings("unchecked") var factory = (ComponentFactory) entry.getValue().factory();