Skip to content

Commit

Permalink
Implement event for scheduled block updates
Browse files Browse the repository at this point in the history
  • Loading branch information
aromaa committed Nov 2, 2024
1 parent f131f23 commit 33537f0
Show file tree
Hide file tree
Showing 15 changed files with 203 additions and 125 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* This file is part of Sponge, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.spongepowered.common.block;

import org.spongepowered.api.block.transaction.ScheduleUpdateTicket;
import org.spongepowered.api.scheduler.TaskPriority;
import org.spongepowered.api.world.LocatableBlock;

import java.time.Duration;

public final class SpongeScheduleUpdateTicket<T> implements ScheduleUpdateTicket<T> {

private final LocatableBlock block;
private final T target;
private final Duration delay;
private final TaskPriority priority;
private boolean valid = true;

public SpongeScheduleUpdateTicket(final LocatableBlock block, final T target, final Duration delay, final TaskPriority priority) {
this.block = block;
this.target = target;
this.delay = delay;
this.priority = priority;
}

@Override
public LocatableBlock block() {
return this.block;
}

@Override
public T target() {
return this.target;
}

@Override
public Duration delay() {
return this.delay;
}

@Override
public TaskPriority priority() {
return this.priority;
}

@Override
public boolean valid() {
return this.valid;
}

@Override
public void setValid(final boolean valid) {
this.valid = valid;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ default void foldContextForThread(final C context, final TickTaskBridge returnVa
default void associateScheduledTickUpdate(final C asContext, final ServerLevel level,
final ScheduledTick<?> entry
) {

asContext.getTransactor().logScheduledUpdate(level, entry);
}

default boolean isApplyingStreams() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
@DefaultQualifier(NonNull.class)
public abstract class GameTransaction<E extends Event & Cancellable> implements TransactionFlow, StatefulTransaction {

private final TransactionType<? extends E> transactionType;
private final TransactionType<? super E> transactionType;
protected boolean cancelled = false;

// Children Definitions
Expand All @@ -61,7 +61,7 @@ public abstract class GameTransaction<E extends Event & Cancellable> implements
@Nullable GameTransaction<@NonNull ?> next;
private boolean recorded = false;

protected GameTransaction(final TransactionType<? extends E> transactionType) {
protected GameTransaction(final TransactionType<? super E> transactionType) {
this.transactionType = transactionType;
}

Expand All @@ -71,7 +71,7 @@ public String toString() {
.toString();
}

public final TransactionType<? extends E> getTransactionType() {
public final TransactionType<? super E> getTransactionType() {
return this.transactionType;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import org.spongepowered.api.item.inventory.Slot;
import org.spongepowered.api.item.inventory.crafting.CraftingInventory;
import org.spongepowered.api.item.inventory.transaction.SlotTransaction;
import org.spongepowered.api.scheduler.ScheduledUpdate;
import org.spongepowered.api.world.BlockChangeFlag;
import org.spongepowered.api.world.BlockChangeFlags;
import org.spongepowered.common.SpongeCommon;
Expand Down Expand Up @@ -178,23 +179,14 @@ default EffectTransactor logBlockDrops(
return this.pushEffect(new ResultingTransactionBySideEffect(PrepareBlockDrops.getInstance()));
}

@SuppressWarnings("ConstantConditions")
default void logScheduledUpdate(final ServerLevel serverWorld, final ScheduledTick<?> data) {
@SuppressWarnings({"ConstantConditions", "unchecked"})
default <T> void logScheduledUpdate(final ServerLevel serverWorld, final ScheduledTick<T> data) {
final WeakReference<ServerLevel> worldRef = new WeakReference<>(serverWorld);
final WeakReference<ScheduledTick<T>> dataRef = new WeakReference<>(data);
final Supplier<ServerLevel> worldSupplier = () -> Objects.requireNonNull(worldRef.get(), "ServerWorld dereferenced");
final @Nullable BlockEntity tileEntity = serverWorld.getBlockEntity(data.pos());
final BlockState existing = serverWorld.getBlockState(data.pos());
final SpongeBlockSnapshot original = TrackingUtil.createPooledSnapshot(
existing,
data.pos(),
BlockChangeFlags.NONE,
Constants.World.DEFAULT_BLOCK_CHANGE_LIMIT,
tileEntity,
worldSupplier,
Optional::empty, Optional::empty
);
original.blockChange = BlockChange.MODIFY;
final ScheduleUpdateTransaction transaction = new ScheduleUpdateTransaction(original, data);
final Supplier<ScheduledTick<T>> dataSupplier = () -> Objects.requireNonNull(dataRef.get(), "Data dereferenced");
final ScheduledUpdate<T> spongeData = (ScheduledUpdate<T>) (Object) data;
final ScheduleUpdateTransaction<T> transaction = new ScheduleUpdateTransaction<>(worldSupplier, dataSupplier, data.pos(), data.type(), spongeData.delay(), spongeData.priority());
this.logTransaction(transaction);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,78 +24,145 @@
*/
package org.spongepowered.common.event.tracking.context.transaction.block;

import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import io.leangen.geantyref.TypeToken;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.ticks.ScheduledTick;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.api.block.transaction.ScheduleUpdateTicket;
import org.spongepowered.api.event.Cause;
import org.spongepowered.api.event.CauseStackManager;
import org.spongepowered.api.event.block.ChangeBlockEvent;
import org.spongepowered.common.block.SpongeBlockSnapshot;
import org.spongepowered.api.event.SpongeEventFactory;
import org.spongepowered.api.event.block.ScheduleBlockUpdateEvent;
import org.spongepowered.api.scheduler.TaskPriority;
import org.spongepowered.api.world.LocatableBlock;
import org.spongepowered.common.block.SpongeScheduleUpdateTicket;
import org.spongepowered.common.bridge.world.ticks.TickNextTickDataBridge;
import org.spongepowered.common.event.tracking.PhaseContext;
import org.spongepowered.common.event.tracking.context.transaction.GameTransaction;
import org.spongepowered.common.event.tracking.context.transaction.type.TransactionTypes;
import org.spongepowered.common.event.tracking.context.transaction.world.WorldBasedTransaction;
import org.spongepowered.common.util.PrettyPrinter;
import org.spongepowered.common.world.server.SpongeLocatableBlockBuilder;
import org.spongepowered.math.vector.Vector3i;

import java.time.Duration;
import java.util.Optional;
import java.util.StringJoiner;
import java.util.function.BiConsumer;
import java.util.function.Supplier;

public class ScheduleUpdateTransaction extends BlockEventBasedTransaction {
public class ScheduleUpdateTransaction<T> extends WorldBasedTransaction<ScheduleBlockUpdateEvent<T>> {

private final ScheduledTick<?> data;
private final SpongeBlockSnapshot original;
private final BlockPos affectedPosition;
private final BlockState originalState;
private final TypeToken<T> typeToken;

private Supplier<ScheduledTick<T>> dataSupplier;
private Supplier<ScheduleUpdateTicket<T>> ticketSupplier;

public ScheduleUpdateTransaction(final SpongeBlockSnapshot original, final ScheduledTick<?> data) {
super(original.getBlockPos(), (BlockState) original.state(), original.world());
this.data = data;
this.original = original;
@SuppressWarnings("unchecked")
public ScheduleUpdateTransaction(
final Supplier<ServerLevel> serverWorldSupplier, final Supplier<ScheduledTick<T>> dataSupplier,
final BlockPos affectedPosition, final T target, final Duration delay, final TaskPriority priority
) {
super(TransactionTypes.SCHEDULE_BLOCK_UPDATE.get(), ((org.spongepowered.api.world.server.ServerWorld) serverWorldSupplier.get()).key());
this.affectedPosition = affectedPosition;
this.originalState = serverWorldSupplier.get().getBlockState(affectedPosition);
this.typeToken = TypeToken.get((Class<T>) target.getClass());

this.dataSupplier = dataSupplier;
this.ticketSupplier = Suppliers.memoize(() -> {
final LocatableBlock locatableBlock = new SpongeLocatableBlockBuilder()
.world(serverWorldSupplier)
.position(this.affectedPosition.getX(), this.affectedPosition.getY(), this.affectedPosition.getZ())
.state((org.spongepowered.api.block.BlockState) this.originalState)
.build();
return new SpongeScheduleUpdateTicket<>(locatableBlock, target, delay, priority);
});
}

@Override
protected boolean actualBlockTransaction() {
return false;
public Optional<BiConsumer<PhaseContext<@NonNull ?>, CauseStackManager.StackFrame>> getFrameMutator(final @Nullable GameTransaction<@NonNull ?> parent) {
return Optional.empty();
}

@Override
protected SpongeBlockSnapshot getResultingSnapshot() {
throw new UnsupportedOperationException();
public Optional<ScheduleBlockUpdateEvent<T>> generateEvent(
final PhaseContext<@NonNull ?> context,
final @Nullable GameTransaction<@NonNull ?> parent,
final ImmutableList<GameTransaction<ScheduleBlockUpdateEvent<T>>> gameTransactions,
final Cause currentCause
) {
final ImmutableList<ScheduleUpdateTicket<T>> tickets = gameTransactions.stream()
.map(transaction -> ((ScheduleUpdateTransaction<T>) transaction).ticketSupplier.get())
.collect(ImmutableList.toImmutableList());

return Optional.of(SpongeEventFactory.createScheduleBlockUpdateEvent(currentCause, this.typeToken, tickets));
}

@Override
protected SpongeBlockSnapshot getOriginalSnapshot() {
return this.original;
public void restore(final PhaseContext<?> context, final ScheduleBlockUpdateEvent<T> event) {
((TickNextTickDataBridge<?>) (Object) this.dataSupplier.get()).bridge$cancelForcibly();
}

@Override
public Optional<BiConsumer<PhaseContext<@NonNull ?>, CauseStackManager.StackFrame>> getFrameMutator(
final @Nullable GameTransaction<@NonNull ?> parent
public boolean markCancelledTransactions(
final ScheduleBlockUpdateEvent<T> event,
final ImmutableList<? extends GameTransaction<ScheduleBlockUpdateEvent<T>>> gameTransactions
) {
return Optional.empty();
boolean cancelledAny = false;
for (final ScheduleUpdateTicket<T> transaction : event.tickets()) {
if (!transaction.valid()) {
cancelledAny = true;
for (final GameTransaction<ScheduleBlockUpdateEvent<T>> gameTransaction : gameTransactions) {
final ScheduleUpdateTransaction<T> scheduleUpdateTransaction = (ScheduleUpdateTransaction<T>) gameTransaction;
final Vector3i position = transaction.block().blockPosition();
final BlockPos affectedPosition = scheduleUpdateTransaction.affectedPosition;
if (position.x() == affectedPosition.getX()
&& position.y() == affectedPosition.getY()
&& position.z() == affectedPosition.getZ()
) {
gameTransaction.markCancelled();
}
}
}
}

return cancelledAny;
}

@Override
public void addToPrinter(final PrettyPrinter printer) {
printer.add("AddBlockEvent")
.add(" %s : %s", "Original Block", this.original)
.add(" %s : %s", "Original State", this.originalState)
.add(" %s : %s", "EventData", this.data);
public void markEventAsCancelledIfNecessary(final ScheduleBlockUpdateEvent<T> event) {
super.markEventAsCancelledIfNecessary(event);
event.tickets().forEach(ScheduleUpdateTicket::invalidate);
}

@Override
public void restore(PhaseContext<?> context, ChangeBlockEvent.All event) {
((TickNextTickDataBridge<?>) (Object) this.data).bridge$cancelForcibly();
public void addToPrinter(final PrettyPrinter printer) {
printer.add("ScheduleUpdate")
.add(" %s : %s", "Affected Position", this.affectedPosition)
.add(" %s : %s", "Original State", this.originalState);
}

@Override
public String toString() {
return new StringJoiner(", ", ScheduleUpdateTransaction.class.getSimpleName() + "[", "]")
.add("scheduledUpdate=" + this.data)
.add("original=" + this.original)
.add("affectedPosition=" + this.affectedPosition)
.add("originalState=" + this.originalState)
.add("worldKey=" + this.worldKey)
.add("originalState=" + this.originalState)
.add("cancelled=" + this.cancelled)
.toString();
}

@Override
protected boolean shouldBuildEventAndRestartBatch(
final GameTransaction<@NonNull ?> pointer, final PhaseContext<@NonNull ?> context
) {
return super.shouldBuildEventAndRestartBatch(pointer, context) || !this.typeToken.equals(((ScheduleUpdateTransaction<?>) pointer).typeToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ abstract class MenuBasedTransaction<E extends Event & Cancellable> extends GameT
protected final AbstractContainerMenu menu;

protected MenuBasedTransaction(
final TransactionType<? extends E> transactionType,
final TransactionType<? super E> transactionType,
final AbstractContainerMenu menu
) {
super(transactionType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.spongepowered.api.event.Event;
import org.spongepowered.api.event.block.ChangeBlockEvent;
import org.spongepowered.api.event.block.NotifyNeighborBlockEvent;
import org.spongepowered.api.event.block.ScheduleBlockUpdateEvent;
import org.spongepowered.api.event.entity.HarvestEntityEvent;
import org.spongepowered.api.event.entity.SpawnEntityEvent;
import org.spongepowered.api.event.item.inventory.AffectSlotEvent;
Expand All @@ -52,19 +53,22 @@ public final class TransactionTypes {

public static final DefaultedRegistryReference<TransactionType<ChangeBlockEvent.All>> BLOCK = TransactionTypes.key(ResourceKey.sponge("block"));

public static final DefaultedRegistryReference<TransactionType<HarvestEntityEvent>> ENTITY_DEATH_DROPS = TransactionTypes.key(ResourceKey.sponge("entity_death_drops"));
public static final DefaultedRegistryReference<TransactionType<ChangeInventoryEvent>> CHANGE_INVENTORY_EVENT = TransactionTypes.key(ResourceKey.sponge("change_inventory"));

public static final DefaultedRegistryReference<TransactionType<NotifyNeighborBlockEvent>> NEIGHBOR_NOTIFICATION = TransactionTypes.key(ResourceKey.sponge("neighbor_notification"));
public static final DefaultedRegistryReference<TransactionType<ClickContainerEvent>> CLICK_CONTAINER_EVENT = TransactionTypes.key(ResourceKey.sponge("click_container"));

public static final DefaultedRegistryReference<TransactionType<SpawnEntityEvent>> SPAWN_ENTITY = TransactionTypes.key(ResourceKey.sponge("spawn_entity"));
public static final DefaultedRegistryReference<TransactionType<HarvestEntityEvent>> ENTITY_DEATH_DROPS = TransactionTypes.key(ResourceKey.sponge("entity_death_drops"));

public static final DefaultedRegistryReference<TransactionType<InteractContainerEvent>> INTERACT_CONTAINER_EVENT = TransactionTypes.key(ResourceKey.sponge("interact_container"));

public static final DefaultedRegistryReference<TransactionType<ClickContainerEvent>> CLICK_CONTAINER_EVENT = TransactionTypes.key(ResourceKey.sponge("click_container"));
public static final DefaultedRegistryReference<TransactionType<NotifyNeighborBlockEvent>> NEIGHBOR_NOTIFICATION = TransactionTypes.key(ResourceKey.sponge("neighbor_notification"));

public static final DefaultedRegistryReference<TransactionType<ScheduleBlockUpdateEvent<?>>> SCHEDULE_BLOCK_UPDATE = TransactionTypes.key(ResourceKey.sponge("schedule_block_update"));

public static final DefaultedRegistryReference<TransactionType<ChangeInventoryEvent>> CHANGE_INVENTORY_EVENT = TransactionTypes.key(ResourceKey.sponge("change_inventory"));
public static final DefaultedRegistryReference<TransactionType<AffectSlotEvent>> SLOT_CHANGE = TransactionTypes.key(ResourceKey.sponge("slot_change"));

public static final DefaultedRegistryReference<TransactionType<SpawnEntityEvent>> SPAWN_ENTITY = TransactionTypes.key(ResourceKey.sponge("spawn_entity"));

// SORTFIELDS:OFF

// @formatter:on
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public abstract class WorldBasedTransaction<E extends Event & Cancellable> exten
protected final ResourceKey worldKey;

protected WorldBasedTransaction(
final TransactionType<? extends E> transactionType,
final TransactionType<? super E> transactionType,
final ResourceKey worldKey
) {
super(transactionType);
Expand Down
Loading

0 comments on commit 33537f0

Please sign in to comment.