From c573c668ec9baa9f7fe8eceb3074cc4c25576da7 Mon Sep 17 00:00:00 2001 From: Yeregorix Date: Thu, 1 Oct 2026 23:36:28 +0200 Subject: [PATCH 1/2] Update chunk ticket API --- SpongeAPI | 2 +- .../generator/world/WorldRegistries.java | 7 + .../bridge/world/DistanceManagerBridge.java | 15 +- .../bridge/world/server/TicketBridge.java | 2 +- .../spongepowered/common/util/Constants.java | 7 +- .../world/server/SpongeTicketTypeBuilder.java | 60 +++++--- .../server/level/ChunkMapMixin_API.java | 35 +++-- .../server/level/TicketMixin_API.java | 30 +++- .../server/level/TicketTypeMixin_API.java | 37 +++-- .../server/level/DistanceManagerMixin.java | 62 +++----- .../mixin/core/server/level/TicketMixin.java | 8 +- .../core/server/level/TicketStorageMixin.java | 6 +- .../test/chunkmanager/ChunkManagerTest.java | 139 ++++++++---------- 13 files changed, 223 insertions(+), 187 deletions(-) diff --git a/SpongeAPI b/SpongeAPI index 8602c5ef56b..9707dd9fadb 160000 --- a/SpongeAPI +++ b/SpongeAPI @@ -1 +1 @@ -Subproject commit 8602c5ef56b32be40ab9e04bbb69bda77800bc91 +Subproject commit 9707dd9fadb92911fba1a8644088b6d81337cb87 diff --git a/generator/src/main/java/org/spongepowered/vanilla/generator/world/WorldRegistries.java b/generator/src/main/java/org/spongepowered/vanilla/generator/world/WorldRegistries.java index fa6cf6c7e47..b5d211e4ea1 100644 --- a/generator/src/main/java/org/spongepowered/vanilla/generator/world/WorldRegistries.java +++ b/generator/src/main/java/org/spongepowered/vanilla/generator/world/WorldRegistries.java @@ -53,6 +53,13 @@ public static List worldRegistries(final Context context) { "getSerializedName", "sponge" ), + new RegistryEntriesGenerator<>( + "world.server", + "TicketTypes", + "TICKET_TYPE", + context.relativeClass("world.server", "TicketType"), + Registries.TICKET_TYPE + ), new RegistryEntriesGenerator<>( "effect.particle", "ParticleTypes", diff --git a/src/main/java/org/spongepowered/common/bridge/world/DistanceManagerBridge.java b/src/main/java/org/spongepowered/common/bridge/world/DistanceManagerBridge.java index fe01575105d..a397c4e530e 100644 --- a/src/main/java/org/spongepowered/common/bridge/world/DistanceManagerBridge.java +++ b/src/main/java/org/spongepowered/common/bridge/world/DistanceManagerBridge.java @@ -25,26 +25,25 @@ package org.spongepowered.common.bridge.world; import org.spongepowered.api.util.Ticks; -import org.spongepowered.api.world.server.ServerWorld; import org.spongepowered.api.world.server.Ticket; import org.spongepowered.api.world.server.TicketType; import org.spongepowered.math.vector.Vector3i; import java.util.Collection; -import java.util.Optional; +import java.util.function.Predicate; public interface DistanceManagerBridge { - boolean bridge$checkTicketValid(Ticket ticket); + boolean bridge$checkTicketValid(Ticket ticket); - Ticks bridge$timeLeft(Ticket ticket); + Ticks bridge$timeLeft(Ticket ticket); - Optional> bridge$registerTicket(ServerWorld world, TicketType ticketType, Vector3i pos, T value, int distanceLimit); + Ticket bridge$registerTicket(TicketType ticketType, Vector3i pos, int radius); - boolean bridge$renewTicket(Ticket ticket); + boolean bridge$renewTicket(Ticket ticket); - boolean bridge$releaseTicket(Ticket ticket); + boolean bridge$releaseTicket(Ticket ticket); - Collection> bridge$tickets(TicketType ticketType); + Collection bridge$tickets(Predicate typePredicate); } diff --git a/src/main/java/org/spongepowered/common/bridge/world/server/TicketBridge.java b/src/main/java/org/spongepowered/common/bridge/world/server/TicketBridge.java index 5feb31c4a64..a139ae7f64f 100644 --- a/src/main/java/org/spongepowered/common/bridge/world/server/TicketBridge.java +++ b/src/main/java/org/spongepowered/common/bridge/world/server/TicketBridge.java @@ -32,7 +32,7 @@ public interface TicketBridge { void bridge$setChunkPosition(long chunkPos); - org.spongepowered.api.world.server.Ticket bridge$retrieveAppropriateTicket(); + Ticket bridge$retrieveAppropriateTicket(); void bridge$setParentTicket(Ticket parentTicket); diff --git a/src/main/java/org/spongepowered/common/util/Constants.java b/src/main/java/org/spongepowered/common/util/Constants.java index af492696036..8f7256954c3 100644 --- a/src/main/java/org/spongepowered/common/util/Constants.java +++ b/src/main/java/org/spongepowered/common/util/Constants.java @@ -467,12 +467,9 @@ public static final class Chunk { public static final class ChunkTicket { - public static final int MAX_FULL_CHUNK_TICKET_LEVEL = ChunkLevel.byStatus(FullChunkStatus.ENTITY_TICKING); + public static final int FULL_CHUNK_MAX_TICKET_LEVEL = ChunkLevel.byStatus(FullChunkStatus.FULL); - // Highest ticket level that will cause loading a full chunk, plus one. - public static final int MAX_FULL_CHUNK_DISTANCE = ChunkTicket.MAX_FULL_CHUNK_TICKET_LEVEL + 1; - - public static final int INFINITE_TIMEOUT = 0; + public static final long INFINITE_TIMEOUT = 0; } public static final class Networking { diff --git a/src/main/java/org/spongepowered/common/world/server/SpongeTicketTypeBuilder.java b/src/main/java/org/spongepowered/common/world/server/SpongeTicketTypeBuilder.java index 9a522550451..a2a6905f40d 100644 --- a/src/main/java/org/spongepowered/common/world/server/SpongeTicketTypeBuilder.java +++ b/src/main/java/org/spongepowered/common/world/server/SpongeTicketTypeBuilder.java @@ -25,43 +25,64 @@ package org.spongepowered.common.world.server; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.api.util.Ticks; import org.spongepowered.api.world.server.TicketType; import org.spongepowered.common.util.Constants; -import java.util.Comparator; import java.util.Objects; -@SuppressWarnings("unchecked") -public final class SpongeTicketTypeBuilder implements TicketType.Builder { +public final class SpongeTicketTypeBuilder implements TicketType.Builder { - private @MonotonicNonNull String name; - private @Nullable Comparator comparator; private @MonotonicNonNull Ticks lifetime; + private int flags; @Override - public TicketType.Builder reset() { - this.name = null; - this.comparator = null; + public TicketType.Builder reset() { this.lifetime = null; + this.flags = 0; + return this; + } + + private void setFlag(int flag, boolean value) { + if (value) { + this.flags |= flag; + } else { + this.flags &= ~flag; + } + } + + @Override + public TicketType.Builder persists(boolean persists) { + this.setFlag(net.minecraft.server.level.TicketType.FLAG_PERSIST, persists); + return this; + } + + @Override + public TicketType.Builder loadsChunks(boolean loadsChunks) { + this.setFlag(net.minecraft.server.level.TicketType.FLAG_LOADING, loadsChunks); + return this; + } + + @Override + public TicketType.Builder simulatesChunks(boolean simulatesChunks) { + this.setFlag(net.minecraft.server.level.TicketType.FLAG_SIMULATION, simulatesChunks); return this; } @Override - public TicketType.Builder name(final String name) { - this.name = Objects.requireNonNull(name, "Name cannot null"); + public TicketType.Builder keepsWorldActive(boolean keepsWorldActive) { + this.setFlag(net.minecraft.server.level.TicketType.FLAG_KEEP_DIMENSION_ACTIVE, keepsWorldActive); return this; } @Override - public TicketType.Builder comparator(final @Nullable Comparator comparator) { - this.comparator = comparator; + public TicketType.Builder canExpireIfUnloaded(boolean canExpireIfUnloaded) { + this.setFlag(net.minecraft.server.level.TicketType.FLAG_CAN_EXPIRE_IF_UNLOADED, canExpireIfUnloaded); return this; } @Override - public TicketType.Builder lifetime(final Ticks lifetime) { + public TicketType.Builder lifetime(final Ticks lifetime) { Objects.requireNonNull(lifetime, "Lifetime cannot be null"); if (!lifetime.isInfinite() && lifetime.ticks() <= 0) { throw new IllegalArgumentException("The lifetime is required to be a positive integer"); @@ -71,16 +92,9 @@ public TicketType.Builder lifetime(final Ticks lifetime) { } @Override - public TicketType build() { - Objects.requireNonNull(this.name, "Name cannot null"); + public TicketType build() { Objects.requireNonNull(this.lifetime, "Lifetime cannot be null"); - if (this.comparator == null) { - this.comparator = (v1, v2) -> 0; - } - final var flag = net.minecraft.server.level.TicketType.FLAG_LOADING - | net.minecraft.server.level.TicketType.FLAG_SIMULATION - | net.minecraft.server.level.TicketType.FLAG_KEEP_DIMENSION_ACTIVE; final var timeout = this.lifetime.isInfinite() ? Constants.ChunkTicket.INFINITE_TIMEOUT : this.lifetime.ticks(); - return (TicketType) (Object) new net.minecraft.server.level.TicketType(timeout,flag); + return (TicketType) (Object) new net.minecraft.server.level.TicketType(timeout, this.flags); } } diff --git a/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/server/level/ChunkMapMixin_API.java b/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/server/level/ChunkMapMixin_API.java index 951292ef152..08ca52870f5 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/server/level/ChunkMapMixin_API.java +++ b/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/server/level/ChunkMapMixin_API.java @@ -39,8 +39,9 @@ import org.spongepowered.math.vector.Vector3i; import java.util.Collection; -import java.util.Optional; +import java.util.Objects; import java.util.concurrent.CompletableFuture; +import java.util.function.Predicate; @Mixin(ChunkMap.class) public abstract class ChunkMapMixin_API implements org.spongepowered.api.world.server.ChunkManager { @@ -55,41 +56,43 @@ public abstract class ChunkMapMixin_API implements org.spongepowered.api.world.s } @Override - public boolean valid(final @NonNull Ticket ticket) { + public boolean valid(final @NonNull Ticket ticket) { + Objects.requireNonNull(ticket, "ticket"); return ((ChunkMapBridge) this).bridge$distanceManager().bridge$checkTicketValid(ticket); } @Override - public @NonNull Ticks timeLeft(final @NonNull Ticket ticket) { + public @NonNull Ticks timeLeft(final @NonNull Ticket ticket) { + Objects.requireNonNull(ticket, "ticket"); return ((ChunkMapBridge) this).bridge$distanceManager().bridge$timeLeft(ticket); } @Override - public @NonNull Optional> requestTicket(final @NonNull TicketType type, final @NonNull Vector3i chunkPosition, - final @NonNull T value, final int radius) { - if (!((Object) type instanceof net.minecraft.server.level.TicketType)) { - throw new IllegalArgumentException("TicketType must be a Minecraft TicketType"); + public @NonNull Ticket requestTicket(final @NonNull TicketType type, final @NonNull Vector3i chunkPosition, final int radius) { + Objects.requireNonNull(type, "type"); + Objects.requireNonNull(chunkPosition, "chunkPosition"); + if (radius < 0) { + throw new IllegalArgumentException("The radius must be not be negative."); } - if (radius < 1) { - throw new IllegalArgumentException("The radius must be positive."); - } - return ((ChunkMapBridge) this).bridge$distanceManager() - .bridge$registerTicket(this.world(), type, chunkPosition, value, radius); + return ((ChunkMapBridge) this).bridge$distanceManager().bridge$registerTicket(type, chunkPosition, radius); } @Override - public boolean renewTicket(final @NonNull Ticket ticket) { + public boolean renewTicket(final @NonNull Ticket ticket) { + Objects.requireNonNull(ticket, "ticket"); return ((ChunkMapBridge) this).bridge$distanceManager().bridge$renewTicket(ticket); } @Override - public boolean releaseTicket(final @NonNull Ticket ticket) { + public boolean releaseTicket(final @NonNull Ticket ticket) { + Objects.requireNonNull(ticket, "ticket"); return ((ChunkMapBridge) this).bridge$distanceManager().bridge$releaseTicket(ticket); } @Override - public @NonNull Collection> findTickets(final @NonNull TicketType type) { - return ((ChunkMapBridge) this).bridge$distanceManager().bridge$tickets(type); + public @NonNull Collection findTickets(final @NonNull Predicate typePredicate) { + Objects.requireNonNull(typePredicate, "typePredicate"); + return ((ChunkMapBridge) this).bridge$distanceManager().bridge$tickets(typePredicate); } @Override diff --git a/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/server/level/TicketMixin_API.java b/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/server/level/TicketMixin_API.java index 41e00267aa5..c3e79a0db2e 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/server/level/TicketMixin_API.java +++ b/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/server/level/TicketMixin_API.java @@ -25,8 +25,36 @@ package org.spongepowered.common.mixin.api.minecraft.server.level; import net.minecraft.server.level.Ticket; +import net.minecraft.world.level.ChunkPos; +import org.spongepowered.api.world.server.TicketType; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.common.bridge.world.server.TicketBridge; +import org.spongepowered.common.util.Constants; +import org.spongepowered.common.util.VecHelper; +import org.spongepowered.math.vector.Vector3i; @Mixin(Ticket.class) -public abstract class TicketMixin_API implements org.spongepowered.api.world.server.Ticket { +public abstract class TicketMixin_API implements org.spongepowered.api.world.server.Ticket { + + // @formatter:off + @Shadow @Final private net.minecraft.server.level.TicketType type; + @Shadow @Final private int ticketLevel; + // @formatter:on + + @Override + public TicketType type() { + return (TicketType) (Object) this.type; + } + + @Override + public Vector3i chunkOrigin() { + return VecHelper.toVector3i(new ChunkPos(((TicketBridge) this).bridge$chunkPosition())); + } + + @Override + public int radius() { + return Math.max(0, Constants.ChunkTicket.FULL_CHUNK_MAX_TICKET_LEVEL - this.ticketLevel); + } } diff --git a/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/server/level/TicketTypeMixin_API.java b/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/server/level/TicketTypeMixin_API.java index f4b55f7ad58..ed9847518f1 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/server/level/TicketTypeMixin_API.java +++ b/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/server/level/TicketTypeMixin_API.java @@ -24,33 +24,50 @@ */ package org.spongepowered.common.mixin.api.minecraft.server.level; +import net.minecraft.server.level.TicketType; import org.checkerframework.checker.nullness.qual.NonNull; import org.spongepowered.api.util.Ticks; -import org.spongepowered.api.world.server.TicketType; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.common.util.Constants; -@Mixin(net.minecraft.server.level.TicketType.class) -public abstract class TicketTypeMixin_API implements TicketType { +@Mixin(TicketType.class) +public abstract class TicketTypeMixin_API implements org.spongepowered.api.world.server.TicketType { // @formatter:off @Shadow @Final private long timeout; - + @Shadow @Final private int flags; // @formatter:on @Override - public String name() { - // TODO - change this out - return ""; + public boolean persists() { + return (this.flags & TicketType.FLAG_PERSIST) != 0; + } + + @Override + public boolean loadsChunks() { + return (this.flags & TicketType.FLAG_LOADING) != 0; + } + + @Override + public boolean simulatesChunks() { + return (this.flags & TicketType.FLAG_SIMULATION) != 0; + } + + @Override + public boolean keepsWorldActive() { + return (this.flags & TicketType.FLAG_KEEP_DIMENSION_ACTIVE) != 0; + } + + @Override + public boolean canExpireIfUnloaded() { + return (this.flags & TicketType.FLAG_CAN_EXPIRE_IF_UNLOADED) != 0; } @Override @NonNull public Ticks lifetime() { - return this.timeout == Constants.ChunkTicket.INFINITE_TIMEOUT - ? Ticks.infinite() - : Ticks.of(this.timeout); + return this.timeout == Constants.ChunkTicket.INFINITE_TIMEOUT ? Ticks.infinite() : Ticks.of(this.timeout); } } diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/server/level/DistanceManagerMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/server/level/DistanceManagerMixin.java index f3ea9e10d29..29e61b5476b 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/server/level/DistanceManagerMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/server/level/DistanceManagerMixin.java @@ -26,10 +26,8 @@ import net.minecraft.server.level.DistanceManager; import net.minecraft.server.level.TicketType; -import net.minecraft.util.Mth; import net.minecraft.world.level.TicketStorage; import org.spongepowered.api.util.Ticks; -import org.spongepowered.api.world.server.ServerWorld; import org.spongepowered.api.world.server.Ticket; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -44,7 +42,7 @@ import org.spongepowered.math.vector.Vector3i; import java.util.Collection; -import java.util.Optional; +import java.util.function.Predicate; import java.util.stream.Collectors; @Mixin(DistanceManager.class) @@ -55,25 +53,19 @@ public abstract class DistanceManagerMixin implements DistanceManagerBridge { // @formatter:on @Override - @SuppressWarnings({"unchecked", "ConstantConditions"}) - public boolean bridge$checkTicketValid(final Ticket ticket) { + public boolean bridge$checkTicketValid(final Ticket ticket) { // Only report the ticket is valid if it's associated with this manager. - final var nativeTicket = ((net.minecraft.server.level.Ticket) (Object) ticket); - final var bridgeTicket = (TicketBridge) ticket; - final var tickets = this.ticketStorage.getTickets(bridgeTicket.bridge$chunkPosition()); - if (tickets != null && tickets.contains(nativeTicket)) { - return !nativeTicket.isTimedOut(); - } - return false; + final var nativeTicket = (net.minecraft.server.level.Ticket) ticket; + final long chunkPos = ((TicketBridge) ticket).bridge$chunkPosition(); + return !nativeTicket.isTimedOut() && this.ticketStorage.getTickets(chunkPos).contains(nativeTicket); } @Override - @SuppressWarnings("ConstantConditions") - public Ticks bridge$timeLeft(final Ticket ticket) { + public Ticks bridge$timeLeft(final Ticket ticket) { if (this.bridge$checkTicketValid(ticket)) { final var mcTicket = (net.minecraft.server.level.Ticket) ticket; - if (mcTicket.getType().timeout() == 0) { - return Ticks.zero(); + if (mcTicket.getType().timeout() == Constants.ChunkTicket.INFINITE_TIMEOUT) { + return Ticks.infinite(); } final long left = ((TicketAccessor) ticket).accessor$ticksLeft(); return new SpongeTicks(Math.max(0, left)); @@ -82,10 +74,9 @@ public abstract class DistanceManagerMixin implements DistanceManagerBridge { } @Override - @SuppressWarnings({"unchecked", "ConstantConditions"}) - public boolean bridge$renewTicket(final Ticket ticket) { + public boolean bridge$renewTicket(final Ticket ticket) { if (this.bridge$checkTicketValid(ticket)) { - final var nativeTicket = (net.minecraft.server.level.Ticket) (Object) ticket; + final var nativeTicket = (net.minecraft.server.level.Ticket) ticket; nativeTicket.resetTicksLeft(); this.ticketStorage.setDirty(); return true; @@ -94,34 +85,29 @@ public abstract class DistanceManagerMixin implements DistanceManagerBridge { } @Override - @SuppressWarnings("unchecked") - public Optional> bridge$registerTicket( - final ServerWorld world, final org.spongepowered.api.world.server.TicketType ticketType, - final Vector3i pos, final T value, final int distanceLimit) { - final int distance = Mth.clamp(Constants.ChunkTicket.MAX_FULL_CHUNK_DISTANCE - distanceLimit, 0, Constants.ChunkTicket.MAX_FULL_CHUNK_TICKET_LEVEL); + public Ticket bridge$registerTicket(final org.spongepowered.api.world.server.TicketType ticketType, final Vector3i pos, final int radius) { final TicketType type = (TicketType) (Object) ticketType; - final net.minecraft.server.level.Ticket ticketToRequest = new net.minecraft.server.level.Ticket(type, distance); + final int level = Math.max(0, Constants.ChunkTicket.FULL_CHUNK_MAX_TICKET_LEVEL - Math.max(0, radius)); + final net.minecraft.server.level.Ticket ticketToRequest = new net.minecraft.server.level.Ticket(type, level); this.ticketStorage.addTicket(VecHelper.toChunkPos(pos).toLong(), ticketToRequest); - return Optional.of(((TicketBridge) (Object) ticketToRequest).bridge$retrieveAppropriateTicket()); + return (Ticket) ((TicketBridge) ticketToRequest).bridge$retrieveAppropriateTicket(); } @Override - @SuppressWarnings({"ConstantConditions"}) - public boolean bridge$releaseTicket(final Ticket ticket) { - if (this.bridge$checkTicketValid(ticket)) { - final var chunkPos = ((TicketBridge) ticket).bridge$chunkPosition(); - return this.ticketStorage.removeTicket(chunkPos, (net.minecraft.server.level.Ticket) ticket); + public boolean bridge$releaseTicket(final Ticket ticket) { + final var nativeTicket = (net.minecraft.server.level.Ticket) ticket; + if (!nativeTicket.isTimedOut()) { + final long chunkPos = ((TicketBridge) ticket).bridge$chunkPosition(); + return this.ticketStorage.removeTicket(chunkPos, nativeTicket); } return false; } - @SuppressWarnings({"ConstantConditions", "unchecked"}) @Override - public Collection> bridge$tickets(final org.spongepowered.api.world.server.TicketType ticketType) { - return ((TicketStorageAccessor) this.ticketStorage).accessor$tickets().values().stream() - .flatMap(x -> x.stream().filter(ticket -> ticket.getType() == (TicketType) (Object) ticketType)) - .map(x -> (Ticket) (Object) x) - .collect(Collectors.toList()); + public Collection bridge$tickets(final Predicate typePredicate) { + return ((TicketStorageAccessor) this.ticketStorage).accessor$tickets().values() + .stream().flatMap(Collection::stream) + .map(Ticket.class::cast).filter(ticket -> typePredicate.test(ticket.type())) + .collect(Collectors.toList()); } - } diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/server/level/TicketMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/server/level/TicketMixin.java index 81055fd79bc..c3bffd84d73 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/server/level/TicketMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/server/level/TicketMixin.java @@ -47,13 +47,9 @@ public abstract class TicketMixin implements TicketBridge { this.impl$chunkPosition = chunkPos; } - @SuppressWarnings({"unchecked", "ConstantConditions"}) @Override - public org.spongepowered.api.world.server.Ticket bridge$retrieveAppropriateTicket() { - if (this.impl$parent != null) { - return (org.spongepowered.api.world.server.Ticket) (Object) this.impl$parent; - } - return (org.spongepowered.api.world.server.Ticket) this; + public Ticket bridge$retrieveAppropriateTicket() { + return this.impl$parent == null ? (Ticket) (Object) this : this.impl$parent; } @Override diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/server/level/TicketStorageMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/server/level/TicketStorageMixin.java index eea5de41298..2306926bd79 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/server/level/TicketStorageMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/server/level/TicketStorageMixin.java @@ -37,13 +37,11 @@ @Mixin(TicketStorage.class) public class TicketStorageMixin { - @SuppressWarnings("ConstantConditions") @Inject(method = "addTicket(JLnet/minecraft/server/level/Ticket;)Z", at = @At("HEAD")) private void impl$addChunkPosToTicket(final long chunkPos, final net.minecraft.server.level.Ticket ticket, final CallbackInfoReturnable ci) { - ((TicketBridge) (Object) ticket).bridge$setChunkPosition(chunkPos); + ((TicketBridge) ticket).bridge$setChunkPosition(chunkPos); } - @SuppressWarnings("ConstantConditions") @ModifyVariable(method = "addTicket(JLnet/minecraft/server/level/Ticket;)Z", at = @At(value = "LOAD"), slice = @Slice( @@ -58,7 +56,7 @@ public class TicketStorageMixin { // potentially costly search on an array - because addTicket doesn't return the ticket that is actually // in the manager. if (storedTicket != originalTicket) { - ((TicketBridge) (Object) originalTicket).bridge$setParentTicket(storedTicket); + ((TicketBridge) originalTicket).bridge$setParentTicket(storedTicket); } return storedTicket; } diff --git a/testplugins/src/main/java/org/spongepowered/test/chunkmanager/ChunkManagerTest.java b/testplugins/src/main/java/org/spongepowered/test/chunkmanager/ChunkManagerTest.java index 1afab787237..5098d1fff3c 100644 --- a/testplugins/src/main/java/org/spongepowered/test/chunkmanager/ChunkManagerTest.java +++ b/testplugins/src/main/java/org/spongepowered/test/chunkmanager/ChunkManagerTest.java @@ -25,7 +25,7 @@ package org.spongepowered.test.chunkmanager; import com.google.inject.Inject; -import net.kyori.adventure.identity.Identity; +import io.leangen.geantyref.TypeToken; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.LinearComponents; import net.kyori.adventure.text.format.NamedTextColor; @@ -39,12 +39,14 @@ import org.spongepowered.api.event.Listener; import org.spongepowered.api.event.lifecycle.RegisterCommandEvent; import org.spongepowered.api.event.world.chunk.ChunkEvent; +import org.spongepowered.api.registry.RegistryTypes; import org.spongepowered.api.scheduler.Task; import org.spongepowered.api.util.Ticks; import org.spongepowered.api.world.server.ChunkManager; import org.spongepowered.api.world.server.ServerLocation; import org.spongepowered.api.world.server.Ticket; import org.spongepowered.api.world.server.TicketType; +import org.spongepowered.api.world.server.TicketTypes; import org.spongepowered.math.vector.Vector3i; import org.spongepowered.plugin.PluginContainer; import org.spongepowered.plugin.builtin.jvm.Plugin; @@ -53,7 +55,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; -import java.util.Optional; import java.util.Set; @Plugin("chunkmanagertest") @@ -62,7 +63,7 @@ public final class ChunkManagerTest implements LoadableModule { private final PluginContainer pluginContainer; private final ChunkListener listener; - private Map>> ticketsMap = new HashMap<>(); + private final Map> ticketsMap = new HashMap<>(); @Inject public ChunkManagerTest(final PluginContainer pluginContainer, final Logger logger) { @@ -82,81 +83,71 @@ private void registerCommands(final RegisterCommandEvent private Command.Parameterized registerTicketCommand() { final Parameter.Value serverLocationParameter = Parameter.location().key("position").build(); - final Parameter.Value timeParameter = Parameter.integerNumber().key("time").optional().build(); + final Parameter.Value typeParameter = Parameter.registryElement(TypeToken.get(TicketType.class), RegistryTypes.TICKET_TYPE, "minecraft").key("time").optional().build(); return Command.builder() .addParameter(serverLocationParameter) - .addParameter(timeParameter) + .addParameter(typeParameter) .executor(context -> { final ServerLocation location = context.requireOne(serverLocationParameter); - final Optional time = context.one(timeParameter); - - final TicketType ticketType = TicketType.builder() - .name("chunkManagerTest") - .comparator(Vector3i::compareTo) - .lifetime(time.map(Ticks::of).orElse(Ticks.infinite())) - .build(); + final TicketType ticketType = context.one(typeParameter).orElseGet(TicketTypes.FORCED); final ChunkManager manager = location.world().chunkManager(); - final Optional> optionalTicket = manager - .requestTicket(ticketType, location.chunkPosition(), location.chunkPosition(), 5); - if (optionalTicket.isPresent()) { - final Ticket ticket = optionalTicket.get(); - final Set> tickets = this.ticketsMap.computeIfAbsent(location.chunkPosition(), k -> new HashSet<>()); - tickets.add(ticket); - context.sendMessage(Identity.nil(), LinearComponents.linear( - Component.text("Ticket registered. Lifetime - "), - Component.text(manager.timeLeft(ticket).ticks()))); - - context.sendMessage(Identity.nil(), LinearComponents.linear( - Component.text("Ticket validity check: "), - Component.text(manager.valid(ticket)) - )); - - // now find the ticket - if (manager.findTickets(ticketType).contains(ticket)) { - context.sendMessage(Identity.nil(), - Component.text().content("Ticket was found in the chunk manager").color(NamedTextColor.GREEN).build()); - } else { - context.sendMessage(Identity.nil(), - Component.text().content("Ticket was not found in the chunk manager").color(NamedTextColor.RED).build()); - } - - time.ifPresentOrElse(t -> { - Sponge.server().scheduler().submit(Task.builder() - .plugin(this.pluginContainer) - .delay(Ticks.of(t + 1)) - .execute(() -> context.sendMessage(Identity.nil(), LinearComponents.linear( - Component.text("Ticket validity check (1 tick before expiration): "), - Component.text(manager.valid(ticket)) - ))).build()); - - Sponge.server().scheduler().submit(Task.builder() - .plugin(this.pluginContainer) - .delay(Ticks.of(t + 2)) - .execute(() -> { - tickets.remove(ticket); - if (tickets.isEmpty()) { - this.ticketsMap.remove(location.chunkPosition(), tickets); - } - context.sendMessage(Identity.nil(), LinearComponents.linear( - Component.text("Ticket validity check (After expiration): "), - Component.text(manager.valid(ticket)))); - }).build()); - }, () -> Sponge.server().scheduler().submit(Task.builder() - .plugin(this.pluginContainer) - .interval(Ticks.of(20)) - .execute(t -> { - context.sendMessage(Identity.nil(), LinearComponents.linear( - Component.text("Ticket validity check (Repeating 20 ticks): "), - Component.text(manager.valid(ticket)))); - if (!manager.valid(ticket)) { - t.cancel(); - } - }).build())); + final Ticket ticket = manager.requestTicket(ticketType, location.chunkPosition(), 5); + + final Set tickets = this.ticketsMap.computeIfAbsent(location.chunkPosition(), k -> new HashSet<>()); + tickets.add(ticket); + + context.sendMessage(Component.text("Ticket registered.")); + final Ticks timeLeft = manager.timeLeft(ticket); + context.sendMessage(Component.text("Ticks left: " + (timeLeft.isInfinite() ? "Infinite" : timeLeft.ticks()))); + context.sendMessage(Component.text("Chunk origin: " + ticket.chunkOrigin())); + context.sendMessage(Component.text("Radius: " + ticket.radius())); + context.sendMessage(Component.text("Ticket validity check: " + manager.valid(ticket))); + + // now find the ticket + if (manager.findTickets(ticketType).contains(ticket)) { + context.sendMessage(Component.text().content("Ticket was found in the chunk manager").color(NamedTextColor.GREEN).build()); } else { - context.sendMessage(Identity.nil(), Component.text("Ticket was not registered.")); + context.sendMessage(Component.text().content("Ticket was not found in the chunk manager").color(NamedTextColor.RED).build()); } + + final Ticks lifetime = ticketType.lifetime(); + if (lifetime.isInfinite()) { + Sponge.server().scheduler().submit(Task.builder() + .plugin(this.pluginContainer) + .interval(Ticks.of(20)) + .execute(t -> { + context.sendMessage(LinearComponents.linear( + Component.text("Ticket validity check (Repeating 20 ticks): "), + Component.text(manager.valid(ticket)))); + if (!manager.valid(ticket)) { + t.cancel(); + } + }).build()); + } else { + Sponge.server().scheduler().submit(Task.builder() + .plugin(this.pluginContainer) + .delay(Ticks.of(lifetime.ticks() + 1)) + .execute(() -> context.sendMessage(LinearComponents.linear( + Component.text("Ticket validity check (1 tick before expiration): "), + Component.text(manager.valid(ticket)) + ))).build()); + + Sponge.server().scheduler().submit(Task.builder() + .plugin(this.pluginContainer) + .delay(Ticks.of(lifetime.ticks() + 2)) + .execute(() -> { + tickets.remove(ticket); + if (tickets.isEmpty()) { + this.ticketsMap.remove(location.chunkPosition(), tickets); + } + context.sendMessage(LinearComponents.linear( + Component.text("Ticket validity check (After expiration): "), + Component.text(manager.valid(ticket)))); + }).build()); + } + return CommandResult.success(); }) .build(); @@ -169,13 +160,13 @@ private Command.Parameterized unregisterTicketCommand() { .addParameter(serverLocationParameter) .executor(context -> { final ServerLocation location = context.requireOne(serverLocationParameter); - final @Nullable Set> tickets = this.ticketsMap.remove(location.chunkPosition()); + final @Nullable Set tickets = this.ticketsMap.remove(location.chunkPosition()); if (tickets != null) { final ChunkManager manager = location.world().chunkManager(); tickets.forEach(manager::releaseTicket); - context.sendMessage(Identity.nil(), Component.text("Removed tickets.", NamedTextColor.GREEN)); + context.sendMessage(Component.text("Removed tickets.", NamedTextColor.GREEN)); } else { - context.sendMessage(Identity.nil(), Component.text("No valid tickets.", NamedTextColor.RED)); + context.sendMessage(Component.text("No valid tickets.", NamedTextColor.RED)); } return CommandResult.success(); }) @@ -185,13 +176,13 @@ private Command.Parameterized unregisterTicketCommand() { @Override public void disable(final CommandContext ctx) { Sponge.eventManager().unregisterListeners(this.listener); - ctx.sendMessage(Identity.nil(), Component.text("Disabled ChunkManagerTest listener")); + ctx.sendMessage(Component.text("Disabled ChunkManagerTest listener")); } @Override public void enable(final CommandContext ctx) { Sponge.eventManager().registerListeners(this.pluginContainer, this.listener); - ctx.sendMessage(Identity.nil(), Component.text("Enabled ChunkManagerTest listener")); + ctx.sendMessage(Component.text("Enabled ChunkManagerTest listener")); } static class ChunkListener { From 04c3b631f289ed1ae72b6d7a236280d52d055b9e Mon Sep 17 00:00:00 2001 From: Yeregorix Date: Sun, 4 Oct 2026 21:45:49 +0200 Subject: [PATCH 2/2] Ability to query chunk tickets by origin --- SpongeAPI | 2 +- .../bridge/world/DistanceManagerBridge.java | 7 +++++-- .../server/level/ChunkMapMixin_API.java | 18 +++++++++++++---- .../server/level/DistanceManagerMixin.java | 20 ++++++++++++++++--- 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/SpongeAPI b/SpongeAPI index 9707dd9fadb..36df8bdd895 160000 --- a/SpongeAPI +++ b/SpongeAPI @@ -1 +1 @@ -Subproject commit 9707dd9fadb92911fba1a8644088b6d81337cb87 +Subproject commit 36df8bdd89542b2d0211fc1e43b3c2ea7c3a30c7 diff --git a/src/main/java/org/spongepowered/common/bridge/world/DistanceManagerBridge.java b/src/main/java/org/spongepowered/common/bridge/world/DistanceManagerBridge.java index a397c4e530e..9a25743bc0f 100644 --- a/src/main/java/org/spongepowered/common/bridge/world/DistanceManagerBridge.java +++ b/src/main/java/org/spongepowered/common/bridge/world/DistanceManagerBridge.java @@ -30,7 +30,6 @@ import org.spongepowered.math.vector.Vector3i; import java.util.Collection; -import java.util.function.Predicate; public interface DistanceManagerBridge { @@ -44,6 +43,10 @@ public interface DistanceManagerBridge { boolean bridge$releaseTicket(Ticket ticket); - Collection bridge$tickets(Predicate typePredicate); + Collection bridge$tickets(Vector3i pos); + + Collection bridge$tickets(org.spongepowered.api.world.server.TicketType type); + + Collection bridge$tickets(); } diff --git a/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/server/level/ChunkMapMixin_API.java b/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/server/level/ChunkMapMixin_API.java index 08ca52870f5..207a96fccc9 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/server/level/ChunkMapMixin_API.java +++ b/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/server/level/ChunkMapMixin_API.java @@ -41,7 +41,6 @@ import java.util.Collection; import java.util.Objects; import java.util.concurrent.CompletableFuture; -import java.util.function.Predicate; @Mixin(ChunkMap.class) public abstract class ChunkMapMixin_API implements org.spongepowered.api.world.server.ChunkManager { @@ -90,9 +89,20 @@ public boolean releaseTicket(final @NonNull Ticket ticket) { } @Override - public @NonNull Collection findTickets(final @NonNull Predicate typePredicate) { - Objects.requireNonNull(typePredicate, "typePredicate"); - return ((ChunkMapBridge) this).bridge$distanceManager().bridge$tickets(typePredicate); + public @NonNull Collection findTickets(final @NonNull Vector3i chunkOrigin) { + Objects.requireNonNull(chunkOrigin, "chunkOrigin"); + return ((ChunkMapBridge) this).bridge$distanceManager().bridge$tickets(chunkOrigin); + } + + @Override + public @NonNull Collection findTickets(final @NonNull TicketType type) { + Objects.requireNonNull(type, "type"); + return ((ChunkMapBridge) this).bridge$distanceManager().bridge$tickets(type); + } + + @Override + public @NonNull Collection findTickets() { + return ((ChunkMapBridge) this).bridge$distanceManager().bridge$tickets(); } @Override diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/server/level/DistanceManagerMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/server/level/DistanceManagerMixin.java index 29e61b5476b..663955211f5 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/server/level/DistanceManagerMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/server/level/DistanceManagerMixin.java @@ -42,7 +42,7 @@ import org.spongepowered.math.vector.Vector3i; import java.util.Collection; -import java.util.function.Predicate; +import java.util.List; import java.util.stream.Collectors; @Mixin(DistanceManager.class) @@ -104,10 +104,24 @@ public abstract class DistanceManagerMixin implements DistanceManagerBridge { } @Override - public Collection bridge$tickets(final Predicate typePredicate) { + @SuppressWarnings({"rawtypes", "unchecked"}) + public Collection bridge$tickets(final Vector3i pos) { + return (Collection) List.copyOf(this.ticketStorage.getTickets(VecHelper.toChunkPos(pos).toLong())); + } + + @Override + public Collection bridge$tickets(final org.spongepowered.api.world.server.TicketType type) { + return ((TicketStorageAccessor) this.ticketStorage).accessor$tickets().values() + .stream().flatMap(Collection::stream) + .map(Ticket.class::cast).filter(ticket -> ticket.type().equals(type)) + .collect(Collectors.toList()); + } + + @Override + public Collection bridge$tickets() { return ((TicketStorageAccessor) this.ticketStorage).accessor$tickets().values() .stream().flatMap(Collection::stream) - .map(Ticket.class::cast).filter(ticket -> typePredicate.test(ticket.type())) + .map(Ticket.class::cast) .collect(Collectors.toList()); } }