Skip to content

Commit

Permalink
Adjust PhaseTracker to support multithreaded levels
Browse files Browse the repository at this point in the history
  • Loading branch information
aromaa committed Nov 6, 2024
1 parent 8e39e2b commit 1b07a5e
Show file tree
Hide file tree
Showing 60 changed files with 379 additions and 221 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ public boolean restore(final boolean force, final BlockChangeFlag flag) {
final ServerLevel world = optionalWorld.get();
// We need to deterministically define the context as nullable if we don't need to enter.
// this way we guarantee an exit.
try (final PhaseContext<?> context = BlockPhase.State.RESTORING_BLOCKS.createPhaseContext(PhaseTracker.SERVER)) {
try (final PhaseContext<?> context = BlockPhase.State.RESTORING_BLOCKS.createPhaseContext(PhaseTracker.getWorldInstance(world))) {
context.buildAndSwitch();
final BlockPos pos = VecHelper.toBlockPos(this.pos);
if (!world.isInWorldBounds(pos)) { // Invalid position. Inline this check
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public class SpongeChatDecorator implements ChatDecorator {
@Override
public Component decorate(@Nullable final ServerPlayer player, final Component message) {

try (final CauseStackManager.StackFrame frame = PhaseTracker.SERVER.pushCauseFrame()) {
try (final CauseStackManager.StackFrame frame = PhaseTracker.getServerInstanceExplicitly().pushCauseFrame()) {
if (player != null) {
frame.pushCause(player);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
package org.spongepowered.common.data.provider;

import io.leangen.geantyref.GenericTypeReflector;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.entity.EntityInLevelCallback;
import org.spongepowered.api.data.DataHolder;
import org.spongepowered.api.data.DataTransactionResult;
Expand Down Expand Up @@ -260,7 +261,7 @@ private Optional<Value.Immutable<E>> callReplacementEvent(
.result(DataTransactionResult.Type.SUCCESS);
originalValue.ifPresent(transaction::replace);
final ChangeDataHolderEvent.ValueChange valueChange = SpongeEventFactory.createChangeDataHolderEventValueChange(
PhaseTracker.SERVER.currentCause(),
PhaseTracker.getWorldInstance((ServerLevel) entity.world()).currentCause(),
transaction.build(),
dataHolder);
if (SpongeCommon.post(valueChange)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
package org.spongepowered.common.event.inventory;

import net.kyori.adventure.text.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.MenuProvider;
Expand Down Expand Up @@ -91,9 +92,10 @@ public class InventoryEventFactory {
public static boolean callPlayerInventoryPickupEvent(final Player player, final ItemEntity itemToPickup) {
final ItemStack stack = itemToPickup.getItem();
final ItemStackSnapshot snapshot = ItemStackUtil.snapshotOf(stack);
final PhaseTracker phaseTracker = PhaseTracker.getWorldInstance((ServerLevel) player.level());
final ChangeInventoryEvent.Pickup.Pre event =
SpongeEventFactory.createChangeInventoryEventPickupPre(
PhaseTracker.getCauseStackManager().currentCause(),
phaseTracker.currentCause(),
Optional.empty(), Collections.singletonList(snapshot), ((Inventory) player.containerMenu), (Item) itemToPickup, snapshot);
SpongeCommon.post(event);
if (event.isCancelled()) {
Expand All @@ -106,7 +108,7 @@ public static boolean callPlayerInventoryPickupEvent(final Player player, final
return true;
}

final PhaseContext<@NonNull ?> context = PhaseTracker.SERVER.getPhaseContext();
final PhaseContext<@NonNull ?> context = phaseTracker.getPhaseContext();
final TransactionalCaptureSupplier transactor = context.getTransactor();
try (final EffectTransactor ignored = transactor.logPlayerInventoryChangeWithEffect(player, PlayerInventoryTransaction.EventCreator.PICKUP)) {
for (final ItemStackSnapshot item : list) {
Expand All @@ -126,7 +128,7 @@ public static boolean callPlayerInventoryPickupEvent(final Player player, final
stack.setCount(0);
return true;
} else {
final PhaseContext<@NonNull ?> context = PhaseTracker.SERVER.getPhaseContext();
final PhaseContext<@NonNull ?> context = phaseTracker.getPhaseContext();
final TransactionalCaptureSupplier transactor = context.getTransactor();
final boolean added;
try (final EffectTransactor ignored = transactor.logPlayerInventoryChangeWithEffect(player, PlayerInventoryTransaction.EventCreator.PICKUP)) {
Expand All @@ -141,7 +143,8 @@ public static boolean callPlayerInventoryPickupEvent(final Player player, final
}

public static ItemStack callHopperInventoryPickupEvent(final Container inventory, final ItemEntity item, final ItemStack stack) {
try (final CauseStackManager.StackFrame frame = PhaseTracker.getCauseStackManager().pushCauseFrame()) {
final PhaseTracker phaseTracker = PhaseTracker.getWorldInstance((ServerLevel) item.level());
try (final CauseStackManager.StackFrame frame = phaseTracker.pushCauseFrame()) {
frame.pushCause(inventory);

final ItemStackSnapshot snapshot = ItemStackUtil.snapshotOf(stack);
Expand Down Expand Up @@ -227,7 +230,7 @@ public static boolean callInteractContainerOpenEvent(final ServerPlayer player)
final ItemStackSnapshot newCursor = ItemStackUtil.snapshotOf(player.containerMenu.getCarried());
final Transaction<ItemStackSnapshot> cursorTransaction = new Transaction<>(ItemStackSnapshot.empty(), newCursor);
final InteractContainerEvent.Open event =
SpongeEventFactory.createInteractContainerEventOpen(PhaseTracker.getCauseStackManager().currentCause(),
SpongeEventFactory.createInteractContainerEventOpen(PhaseTracker.getWorldInstance(player.serverLevel()).currentCause(),
(org.spongepowered.api.item.inventory.Container) player.containerMenu, cursorTransaction);
SpongeCommon.post(event);
if (event.isCancelled()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ public P buildAndSwitch() {
if (SpongeConfigs.getCommon().get().phaseTracker.generateStackTracePerPhase) {
this.stackTrace = new Exception("Debug Trace").getStackTrace();
}
PhaseTracker.getInstance().switchToPhase(this.state, this);
this.createdTracker.switchToPhase(this.state, this);
return (P) this;
}

Expand Down Expand Up @@ -321,16 +321,15 @@ public void close() { // Should never throw an exception
this.state, this, new IllegalStateException("Closing empty phase context"));
return;
}
final PhaseTracker instance = PhaseTracker.getInstance();
instance.completePhase(this);
this.createdTracker.completePhase(this);
if (this.usedFrame != null) {
this.usedFrame.iterator().forEachRemaining(instance::popCauseFrame);
this.usedFrame.iterator().forEachRemaining(this.createdTracker::popCauseFrame);
this.usedFrame = null;
} else if (this.shouldProvideModifiers()) {
// So, this part is interesting... Since the used frame is null, that means
// the cause stack manager still has the reference of this context/phase, we have
// to "pop off" the list.
instance.popFrameMutator(this);
this.createdTracker.popFrameMutator(this);
}
this.reset();
this.isCompleted = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
Expand Down Expand Up @@ -88,7 +89,7 @@
public final class PhaseTracker implements CauseStackManager {

public static final PhaseTracker CLIENT = new PhaseTracker();
public static final PhaseTracker SERVER = new PhaseTracker();
private static final PhaseTracker SERVER = new PhaseTracker();
public static final Logger LOGGER = LogManager.getLogger(PhaseTracker.class);
static final CopyOnWriteArrayList<net.minecraft.world.entity.Entity> ASYNC_CAPTURED_ENTITIES = new CopyOnWriteArrayList<>();
private static final Map<Thread, PhaseTracker> SPINOFF_TRACKERS = new MapMaker().weakKeys().concurrencyLevel(8).makeMap();
Expand Down Expand Up @@ -118,6 +119,44 @@ public static PhaseTracker getInstance() {
});
}

/**
* The server phase tracker should be used for actions
* that are not tied to any specific {@link ServerLevel}.
*
* <p>For actions that are specific to a {@link ServerLevel},
* the {@link #getWorldInstance(ServerLevel)} should be
* preferred.</p>
*
* <p>For actions that could be performed inside a world
* generation thread, the {@link #getWorldInstance(LevelAccessor)}
* should be used instead.</p>
*
* <p>If the current world instance is unknown, the
* implementation can perform more expensive lookup
* using the parameterless {@link #getWorldInstance}.</p>
*/
public static PhaseTracker getServerInstanceExplicitly() {
return PhaseTracker.SERVER;
}

public static PhaseTracker getWorldInstance() {
//This is reserved for a mod that wishes to implement
//multithreaded levels.
return PhaseTracker.SERVER;
}

public static PhaseTracker getWorldInstance(final LevelAccessor level) {
//This is reserved for a mod that wishes to implement
//multithreaded levels.
return PhaseTracker.SERVER;
}

public static PhaseTracker getWorldInstance(final ServerLevel level) {
//This is reserved for a mod that wishes to implement
//multithreaded levels.
return PhaseTracker.SERVER;
}

public static CauseStackManager getCauseStackManager() {
return PhaseTracker.getInstance();
}
Expand Down Expand Up @@ -231,7 +270,7 @@ public static Block validateBlockForNeighborNotification(final ServerLevel world
}

public void init() {
if (this != PhaseTracker.SERVER) {
if (this == PhaseTracker.CLIENT) {
return;
}
if (this.hasRun) {
Expand Down Expand Up @@ -302,14 +341,14 @@ public boolean onSidedThread() {
// ----------------- SIMPLE GETTERS --------------------------------------

public IPhaseState<?> getCurrentState() {
if (Thread.currentThread() != this.getSidedThread()) {
if (!this.onSidedThread()) {
throw new UnsupportedOperationException("Cannot access the PhaseTracker off-thread, please use the respective PhaseTracker for their proper thread.");
}
return this.stack.peekState();
}

public PhaseContext<?> getPhaseContext() {
if (Thread.currentThread() != this.getSidedThread()) {
if (!this.onSidedThread()) {
throw new UnsupportedOperationException("Cannot access the PhaseTracker off-thread, please use the respective PhaseTracker for their proper thread.");
}
return this.stack.peekContext();
Expand All @@ -322,7 +361,7 @@ public PhaseContext<?> getPhaseContext() {
// ----------------- STATE ACCESS ----------------------------------

void switchToPhase(final IPhaseState<?> state, final PhaseContext<?> phaseContext) {
if (phaseContext.createdTracker != this && Thread.currentThread() != this.getSidedThread()) {
if (phaseContext.createdTracker != this && !this.onSidedThread()) {
// lol no, report the block change properly
new PrettyPrinter(60).add("Illegal Async PhaseTracker Access").centre().hr()
.addWrapped(PhasePrinter.ASYNC_TRACKER_ACCESS)
Expand All @@ -335,7 +374,7 @@ void switchToPhase(final IPhaseState<?> state, final PhaseContext<?> phaseContex
Objects.requireNonNull(state, "State cannot be null!");
Objects.requireNonNull(phaseContext, "PhaseContext cannot be null!");
Preconditions.checkArgument(phaseContext.isComplete(), "PhaseContext must be complete!");
if (this == PhaseTracker.SERVER && SpongeConfigs.getCommon().get().phaseTracker.verbose) {
if (this != PhaseTracker.CLIENT && SpongeConfigs.getCommon().get().phaseTracker.verbose) {
if (this.stack.size() > 6) {
if (this.stack.checkForRunaways(state, phaseContext)) {
PhasePrinter.printRunawayPhase(this.stack, state, phaseContext);
Expand All @@ -352,7 +391,7 @@ void switchToPhase(final IPhaseState<?> state, final PhaseContext<?> phaseContex

@SuppressWarnings({"rawtypes", "unused", "try"})
void completePhase(final PhaseContext<?> context) {
if (context.createdTracker != this && Thread.currentThread() != this.getSidedThread()) {
if (context.createdTracker != this && !this.onSidedThread()) {
// lol no, report the block change properly
new PrettyPrinter(60).add("Illegal Async PhaseTracker Access").centre().hr()
.addWrapped(PhasePrinter.ASYNC_TRACKER_ACCESS)
Expand Down Expand Up @@ -741,7 +780,7 @@ public <T> Optional<T> removeContext(final EventContextKey<T> key) {

private void enforceMainThread() {
// On clients, this may not be available immediately, we can't bomb out that early.
if (Thread.currentThread() != this.getSidedThread()) {
if (!this.onSidedThread()) {
throw new IllegalStateException(String.format(
"CauseStackManager called from off main thread (current='%s', expected='%s')!",
ThreadUtil.getDescription(Thread.currentThread()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ protected PooledPhaseState() {

@Override
public final C createPhaseContext(final PhaseTracker tracker) {
if (Thread.currentThread() != tracker.getSidedThread()) {
if (!tracker.onSidedThread()) {
throw new IllegalStateException("Asynchronous Thread Access to PhaseTracker: " + tracker);
}

if (tracker == PhaseTracker.SERVER) {
if (tracker == PhaseTracker.getServerInstanceExplicitly()) {
if (this.serverCached != null) {
final C cached = this.serverCached;
this.serverCached = null;
Expand All @@ -64,10 +64,10 @@ public final C createPhaseContext(final PhaseTracker tracker) {

final void releaseContextFromPool(final C context) {
final PhaseTracker createdTracker = context.createdTracker;
if (Thread.currentThread() != createdTracker.getSidedThread()) {
if (!createdTracker.onSidedThread()) {
throw new IllegalStateException("Asynchronous Thread Access to PhaseTracker: " + createdTracker);
}
if (createdTracker == PhaseTracker.SERVER) {
if (createdTracker == PhaseTracker.getServerInstanceExplicitly()) {
if (this.serverCached == null) {
this.serverCached = context;
return;
Expand Down
Loading

0 comments on commit 1b07a5e

Please sign in to comment.