Skip to content

Commit b620b40

Browse files
authored
Merge pull request #194 from Ladysnake/patch/1.20.1-tickers
Fix ticking behavior for child BlockEntity classes
2 parents c23d4eb + db91ba3 commit b620b40

File tree

6 files changed

+144
-52
lines changed

6 files changed

+144
-52
lines changed

cardinal-components-block/src/main/java/dev/onyxstudios/cca/internal/block/CardinalBlockInternals.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ private static synchronized ComponentContainer.Factory<BlockEntity> getBeCompone
5656
@SuppressWarnings("unchecked") var superclass = (Class<? extends BlockEntity>) entityClass.getSuperclass();
5757
assert BlockEntity.class.isAssignableFrom(superclass) : "requiresStaticFactory returned false on BlockEntity?";
5858
factory = /* recursive call */ getBeComponentFactory(superclass);
59+
60+
// if parent class needs to tick, this one does, too!
61+
StaticBlockComponentPlugin.INSTANCE.registerTickersFor(entityClass, superclass);
5962
}
6063
entityContainerFactories.put(entityClass, factory);
6164
return factory;

cardinal-components-block/src/main/java/dev/onyxstudios/cca/internal/block/StaticBlockComponentPlugin.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import net.minecraft.block.entity.BlockEntityTicker;
4141
import net.minecraft.world.World;
4242
import org.jetbrains.annotations.Nullable;
43+
import org.jetbrains.annotations.VisibleForTesting;
4344

4445
import java.util.ArrayList;
4546
import java.util.Collections;
@@ -63,8 +64,10 @@ private StaticBlockComponentPlugin() {
6364

6465
private final List<PredicatedComponentFactory<?>> dynamicFactories = new ArrayList<>();
6566
private final Map<Class<? extends BlockEntity>, Map<ComponentKey<?>, QualifiedComponentFactory<ComponentFactory<? extends BlockEntity, ?>>>> beComponentFactories = new Reference2ObjectOpenHashMap<>();
66-
private final Set<Class<? extends BlockEntity>> clientTicking = new ReferenceOpenHashSet<>();
67-
private final Set<Class<? extends BlockEntity>> serverTicking = new ReferenceOpenHashSet<>();
67+
@VisibleForTesting
68+
public final Set<Class<? extends BlockEntity>> clientTicking = new ReferenceOpenHashSet<>();
69+
@VisibleForTesting
70+
public final Set<Class<? extends BlockEntity>> serverTicking = new ReferenceOpenHashSet<>();
6871

6972
@Nullable
7073
public <T extends BlockEntity> BlockEntityTicker<T> getComponentTicker(World world, T be, @Nullable BlockEntityTicker<T> base) {
@@ -97,12 +100,12 @@ public boolean requiresStaticFactory(Class<? extends BlockEntity> entityClass) {
97100
public ComponentContainer.Factory<BlockEntity> buildDedicatedFactory(Class<? extends BlockEntity> entityClass) {
98101
StaticBlockComponentPlugin.INSTANCE.ensureInitialized();
99102

100-
var compiled = new LinkedHashMap<>(this.beComponentFactories.getOrDefault(entityClass, Collections.emptyMap()));
103+
var compiled = new LinkedHashMap<>(this.beComponentFactories.getOrDefault(entityClass, Map.of()));
101104
Class<? extends BlockEntity> type = entityClass;
102105

103106
while (type != BlockEntity.class) {
104107
type = type.getSuperclass().asSubclass(BlockEntity.class);
105-
for (var e : this.beComponentFactories.getOrDefault(type, Collections.emptyMap()).entrySet()) {
108+
for (var e : this.beComponentFactories.getOrDefault(type, Map.of()).entrySet()) {
106109
compiled.putIfAbsent(e.getKey(), e.getValue());
107110
}
108111
}
@@ -119,6 +122,15 @@ public ComponentContainer.Factory<BlockEntity> buildDedicatedFactory(Class<? ext
119122
return builder.build();
120123
}
121124

125+
public void registerTickersFor(Class<? extends BlockEntity> entityClass, Class<? extends BlockEntity> parentClass) {
126+
if(this.clientTicking.contains(parentClass)) {
127+
this.clientTicking.add(entityClass);
128+
}
129+
if(this.serverTicking.contains(parentClass)) {
130+
this.serverTicking.add(entityClass);
131+
}
132+
}
133+
122134
private <C extends Component> void addToBuilder(ComponentContainer.Factory.Builder<BlockEntity> builder, Map.Entry<ComponentKey<?>, QualifiedComponentFactory<ComponentFactory<? extends BlockEntity, ?>>> entry) {
123135
@SuppressWarnings("unchecked") var key = (ComponentKey<C>) entry.getKey();
124136
@SuppressWarnings("unchecked") var factory = (ComponentFactory<BlockEntity, C>) entry.getValue().factory();

cardinal-components-block/src/testmod/java/dev/onyxstudios/cca/test/block/CcaBlockTestMod.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,7 @@
3030
import dev.onyxstudios.cca.test.base.Vita;
3131
import net.fabricmc.api.ModInitializer;
3232
import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup;
33-
import net.minecraft.block.entity.BlockEntityType;
34-
import net.minecraft.block.entity.CommandBlockBlockEntity;
35-
import net.minecraft.block.entity.EndGatewayBlockEntity;
36-
import net.minecraft.block.entity.EndPortalBlockEntity;
33+
import net.minecraft.block.entity.*;
3734
import net.minecraft.util.Identifier;
3835
import net.minecraft.util.math.Direction;
3936

@@ -46,6 +43,7 @@ public void registerBlockComponentFactories(BlockComponentFactoryRegistry regist
4643
registry.registerFor(EndGatewayBlockEntity.class, VitaCompound.KEY, VitaCompound::new);
4744
registry.registerFor(EndPortalBlockEntity.class, TickingTestComponent.KEY, be -> new TickingTestComponent());
4845
registry.registerFor(CommandBlockBlockEntity.class, LoadAwareTestComponent.KEY, be -> new LoadAwareTestComponent());
46+
registry.registerFor(BlockEntity.class, GlobalTickingComponent.KEY, GlobalTickingComponent::new);
4947
}
5048

5149
@Override

cardinal-components-block/src/testmod/java/dev/onyxstudios/cca/test/block/CcaBlockTestSuite.java

Lines changed: 73 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
*/
2323
package dev.onyxstudios.cca.test.block;
2424

25+
import dev.onyxstudios.cca.internal.block.StaticBlockComponentPlugin;
2526
import dev.onyxstudios.cca.test.base.LoadAwareTestComponent;
2627
import dev.onyxstudios.cca.test.base.TickingTestComponent;
2728
import dev.onyxstudios.cca.test.base.Vita;
@@ -39,46 +40,28 @@
3940
import org.jetbrains.annotations.NotNull;
4041

4142
import java.util.Objects;
43+
import java.util.concurrent.atomic.AtomicInteger;
44+
import java.util.function.BooleanSupplier;
4245

4346
public class CcaBlockTestSuite implements FabricGameTest {
44-
@GameTest(templateName = EMPTY_STRUCTURE)
45-
public void beSerialize(TestContext ctx) {
47+
@GameTest(templateName = EMPTY_STRUCTURE) public void beSerialize(TestContext ctx) {
4648
BlockPos pos = ctx.getAbsolutePos(BlockPos.ORIGIN);
47-
BlockEntity be = Objects.requireNonNull(
48-
BlockEntityType.END_GATEWAY.instantiate(
49-
pos,
50-
Blocks.END_GATEWAY.getDefaultState()
51-
)
52-
);
49+
BlockEntity be = Objects.requireNonNull(BlockEntityType.END_GATEWAY.instantiate(pos, Blocks.END_GATEWAY.getDefaultState()));
5350
be.getComponent(Vita.KEY).setVitality(42);
5451
NbtCompound nbt = be.createNbt();
55-
BlockEntity be1 = Objects.requireNonNull(
56-
BlockEntityType.END_GATEWAY.instantiate(
57-
pos, Blocks.END_GATEWAY.getDefaultState()
58-
)
59-
);
52+
BlockEntity be1 = Objects.requireNonNull(BlockEntityType.END_GATEWAY.instantiate(pos, Blocks.END_GATEWAY.getDefaultState()));
6053
GameTestUtil.assertTrue("New BlockEntity should have values zeroed", be1.getComponent(Vita.KEY).getVitality() == 0);
6154
be1.readNbt(nbt);
6255
GameTestUtil.assertTrue("BlockEntity component data should survive deserialization", be1.getComponent(Vita.KEY).getVitality() == 42);
6356
ctx.complete();
6457
}
6558

66-
@GameTest(templateName = EMPTY_STRUCTURE)
67-
public void canQueryThroughLookup(TestContext ctx) {
59+
@GameTest(templateName = EMPTY_STRUCTURE) public void canQueryThroughLookup(TestContext ctx) {
6860
BlockPos pos = ctx.getAbsolutePos(BlockPos.ORIGIN);
69-
BlockEntity be = Objects.requireNonNull(
70-
BlockEntityType.END_GATEWAY.instantiate(
71-
pos,
72-
Blocks.END_GATEWAY.getDefaultState()
73-
)
74-
);
61+
BlockEntity be = Objects.requireNonNull(BlockEntityType.END_GATEWAY.instantiate(pos, Blocks.END_GATEWAY.getDefaultState()));
7562
getVita(ctx, pos, be).setVitality(42);
7663
NbtCompound nbt = be.createNbt();
77-
BlockEntity be1 = Objects.requireNonNull(
78-
BlockEntityType.END_GATEWAY.instantiate(
79-
pos, Blocks.END_GATEWAY.getDefaultState()
80-
)
81-
);
64+
BlockEntity be1 = Objects.requireNonNull(BlockEntityType.END_GATEWAY.instantiate(pos, Blocks.END_GATEWAY.getDefaultState()));
8265
GameTestUtil.assertTrue("New BlockEntity should have values zeroed", getVita(ctx, pos, be1).getVitality() == 0);
8366
be1.readNbt(nbt);
8467
GameTestUtil.assertTrue("BlockEntity component data should survive deserialization", getVita(ctx, pos, be1).getVitality() == 42);
@@ -89,36 +72,83 @@ public void canQueryThroughLookup(TestContext ctx) {
8972
return Objects.requireNonNull(CcaBlockTestMod.VITA_API_LOOKUP.find(ctx.getWorld(), pos, null, be, Direction.DOWN));
9073
}
9174

92-
@GameTest(templateName = EMPTY_STRUCTURE)
93-
public void beComponentsTick(TestContext ctx) {
75+
@GameTest(templateName = EMPTY_STRUCTURE) public void beComponentsTick(TestContext ctx) {
9476
ctx.setBlockState(BlockPos.ORIGIN, Blocks.END_PORTAL);
77+
78+
var blockentity = ctx.getBlockEntity(BlockPos.ORIGIN);
79+
GameTestUtil.assertTrue("Block entity should not be null", blockentity != null);
80+
GameTestUtil.assertTrue("BlockEntity should have TickingTestComponent", TickingTestComponent.KEY.getNullable(blockentity) != null);
81+
GameTestUtil.assertTrue("Class should be registered as server ticker", StaticBlockComponentPlugin.INSTANCE.serverTicking.contains(blockentity.getClass()));
82+
GameTestUtil.assertTrue("Class should be registered as client ticker", StaticBlockComponentPlugin.INSTANCE.clientTicking.contains(blockentity.getClass()));
83+
9584
ctx.waitAndRun(5, () -> {
96-
int ticks = Objects.requireNonNull(ctx.getBlockEntity(BlockPos.ORIGIN)).getComponent(TickingTestComponent.KEY).serverTicks();
85+
var blockentity2 = ctx.getBlockEntity(BlockPos.ORIGIN);
86+
GameTestUtil.assertTrue("Block entity should still exist", blockentity2 != null);
87+
int ticks = blockentity2.getComponent(TickingTestComponent.KEY).serverTicks();
9788
GameTestUtil.assertTrue("Component should tick 5 times", ticks == 5);
9889
ctx.complete();
9990
});
10091
}
10192

102-
@GameTest(templateName = EMPTY_STRUCTURE)
103-
public void beComponentsLoadUnload(TestContext ctx) {
93+
@GameTest(templateName = EMPTY_STRUCTURE) public void beComponentsLoadUnload(TestContext ctx) {
10494
BlockEntity firstCommandBlock = new CommandBlockBlockEntity(ctx.getAbsolutePos(BlockPos.ORIGIN), Blocks.CHAIN_COMMAND_BLOCK.getDefaultState());
105-
GameTestUtil.assertTrue(
106-
"Load counter should not be incremented until the block entity joins the world",
107-
LoadAwareTestComponent.KEY.get(firstCommandBlock).getLoadCounter() == 0
108-
);
95+
GameTestUtil.assertTrue("Load counter should not be incremented until the block entity joins the world", LoadAwareTestComponent.KEY.get(firstCommandBlock).getLoadCounter() == 0);
10996
ctx.setBlockState(BlockPos.ORIGIN, Blocks.CHAIN_COMMAND_BLOCK);
11097
BlockEntity commandBlock = Objects.requireNonNull(ctx.getBlockEntity(BlockPos.ORIGIN));
111-
GameTestUtil.assertTrue(
112-
"Load counter should be incremented once when the block entity joins the world",
113-
LoadAwareTestComponent.KEY.get(commandBlock).getLoadCounter() == 1
114-
);
98+
GameTestUtil.assertTrue("Load counter should be incremented once when the block entity joins the world", LoadAwareTestComponent.KEY.get(commandBlock).getLoadCounter() == 1);
11599
ctx.setBlockState(BlockPos.ORIGIN, Blocks.AIR);
116100
ctx.waitAndRun(1, () -> {
117-
GameTestUtil.assertTrue(
118-
"Load counter should be decremented when the block entity leaves the world",
119-
LoadAwareTestComponent.KEY.get(commandBlock).getLoadCounter() == 0
120-
);
101+
GameTestUtil.assertTrue("Load counter should be decremented when the block entity leaves the world", LoadAwareTestComponent.KEY.get(commandBlock).getLoadCounter() == 0);
102+
ctx.complete();
103+
});
104+
}
105+
106+
@GameTest(templateName = EMPTY_STRUCTURE) public void rootClassServerTicker(TestContext ctx) {
107+
ctx.setBlockState(BlockPos.ORIGIN, Blocks.BARREL);
108+
109+
var blockentity = ctx.getBlockEntity(BlockPos.ORIGIN);
110+
GameTestUtil.assertTrue("Block entity should not be null", blockentity != null);
111+
112+
GameTestUtil.assertTrue("Class should be registered as server ticker", StaticBlockComponentPlugin.INSTANCE.serverTicking.contains(blockentity.getClass()));
113+
GameTestUtil.assertFalse("Class should NOT be registered as client ticker", StaticBlockComponentPlugin.INSTANCE.clientTicking.contains(blockentity.getClass()));
114+
115+
var component = GlobalTickingComponent.KEY.getNullable(blockentity);
116+
GameTestUtil.assertTrue("Component should exist", component != null);
117+
118+
AtomicInteger flag = new AtomicInteger(0);
119+
BooleanSupplier action = () -> {
120+
flag.getAndIncrement();
121+
return false;
122+
};
123+
component.setTickAction(action);
124+
GameTestUtil.assertTrue("Tick action should be set", component.getTickAction().isPresent());
125+
126+
ctx.waitAndRun(5, () -> {
127+
var blockentity2 = ctx.getBlockEntity(BlockPos.ORIGIN);
128+
GameTestUtil.assertTrue("Block entity should still exist", blockentity2 != null);
129+
GameTestUtil.assertTrue("Tick action should be cleared", blockentity2.getComponent(GlobalTickingComponent.KEY).getTickAction().isEmpty());
130+
GameTestUtil.assertTrue("Tick action should have run exactly once", flag.get() == 1);
131+
121132
ctx.complete();
122133
});
123134
}
135+
136+
/**
137+
* same as {@link CcaBlockTestSuite#rootClassServerTicker(TestContext)} but for a BlockEntity that has an explicit
138+
* component registered, so that {@link StaticBlockComponentPlugin#requiresStaticFactory(Class)} returns true for
139+
* the class itself rather than delegating to the parent class.
140+
*/
141+
@GameTest(templateName = EMPTY_STRUCTURE)
142+
public void rootClassServerTickerWithExplicitRegistration(TestContext ctx) {
143+
ctx.setBlockState(BlockPos.ORIGIN, Blocks.COMMAND_BLOCK);
144+
145+
var blockentity = ctx.getBlockEntity(BlockPos.ORIGIN);
146+
GameTestUtil.assertTrue("Block entity should not be null", blockentity != null);
147+
GameTestUtil.assertTrue("Class should be registered as server ticker", StaticBlockComponentPlugin.INSTANCE.serverTicking.contains(blockentity.getClass()));
148+
149+
var component = GlobalTickingComponent.KEY.getNullable(blockentity);
150+
GameTestUtil.assertTrue("Component should exist", component != null);
151+
152+
ctx.complete();
153+
}
124154
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package dev.onyxstudios.cca.test.block;
2+
3+
import dev.onyxstudios.cca.api.v3.component.Component;
4+
import dev.onyxstudios.cca.api.v3.component.ComponentKey;
5+
import dev.onyxstudios.cca.api.v3.component.ComponentRegistry;
6+
import dev.onyxstudios.cca.api.v3.component.tick.ServerTickingComponent;
7+
import net.minecraft.nbt.NbtCompound;
8+
import net.minecraft.util.Identifier;
9+
import org.jetbrains.annotations.Nullable;
10+
11+
import java.util.Optional;
12+
import java.util.function.BooleanSupplier;
13+
14+
public class GlobalTickingComponent implements Component, ServerTickingComponent {
15+
16+
public static final ComponentKey<GlobalTickingComponent> KEY = ComponentRegistry.getOrCreate(new Identifier(CcaBlockTestMod.MOD_ID, "global_ticking_test"), GlobalTickingComponent.class);
17+
18+
public GlobalTickingComponent(Object provider) {
19+
// NO-OP
20+
}
21+
22+
@Nullable
23+
private BooleanSupplier onTick;
24+
25+
void setTickAction(@Nullable BooleanSupplier onTick) {
26+
this.onTick = onTick;
27+
}
28+
29+
@Override public void serverTick() {
30+
if(this.onTick != null) {
31+
if(!this.onTick.getAsBoolean()) {
32+
this.onTick = null;
33+
}
34+
}
35+
}
36+
37+
public Optional<BooleanSupplier> getTickAction() {
38+
return Optional.ofNullable(this.onTick);
39+
}
40+
41+
@Override public void readFromNbt(NbtCompound tag) {
42+
// NO-OP
43+
}
44+
45+
@Override public void writeToNbt(NbtCompound tag) {
46+
// NO-OP
47+
}
48+
}

cardinal-components-block/src/testmod/resources/fabric.mod.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
"license": "MIT",
2626
"custom": {
2727
"cardinal-components": [
28-
"cca-block-test:vita_compound"
28+
"cca-block-test:vita_compound",
29+
"cca-block-test:global_ticking_test"
2930
]
3031
}
3132
}

0 commit comments

Comments
 (0)