Adding chunk serialization events

This commit is contained in:
derverdox 2023-11-30 14:09:01 +01:00
parent 2182d47792
commit d361728f2a
2 changed files with 430 additions and 0 deletions

View File

@ -0,0 +1,204 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: derverdox <mail.ysp@web.de>
Date: Thu, 30 Nov 2023 01:22:24 +0100
Subject: [PATCH] Adding ChunkSerialization and Generation events.
This patch adds new Chunk events that are called during the serialization / generation process of chunks.
A problem some developers may face while using the ChunkLoad or ChunkUnload events is the fact that they are called synchronously.
These proposed events are called during the process.
The use case of such events is that heavy saving / loading tasks from cache to the chunk nbt container or vice versa are executed asynchronously aswell but in the same thread
that saves / loads / generates the chunk.
diff --git a/src/main/java/io/papermc/paper/event/chunk/ChunkDataEvent.java b/src/main/java/io/papermc/paper/event/chunk/ChunkDataEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..73c94926c975360f9b7c79f910dd09a0f3346058
--- /dev/null
+++ b/src/main/java/io/papermc/paper/event/chunk/ChunkDataEvent.java
@@ -0,0 +1,61 @@
+package io.papermc.paper.event.chunk;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Chunk;
+import org.bukkit.World;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+import org.bukkit.persistence.PersistentDataContainer;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public abstract class ChunkDataEvent extends Event {
+ private static final HandlerList handlers = new HandlerList();
+ private final World world;
+ private final Chunk chunk;
+ private final int chunkX;
+ private final int chunkZ;
+ private final PersistentDataContainer persistentDataContainer;
+
+ public ChunkDataEvent(@NotNull World world, @Nullable Chunk chunk, int chunkX, int chunkZ, @NotNull PersistentDataContainer chunkPersistentDataContainer) {
+ super(!Bukkit.isPrimaryThread());
+ this.world = world;
+ this.chunk = chunk;
+ this.chunkX = chunkX;
+ this.chunkZ = chunkZ;
+ this.persistentDataContainer = chunkPersistentDataContainer;
+ }
+
+ public int getChunkX() {
+ return chunkX;
+ }
+
+ public int getChunkZ() {
+ return chunkZ;
+ }
+
+ @Nullable
+ public Chunk getChunk() {
+ return this.chunk;
+ }
+
+ @NotNull
+ public World getWorld() {
+ return this.world;
+ }
+
+ @NotNull
+ public PersistentDataContainer getPersistentDataContainer() {
+ return this.persistentDataContainer;
+ }
+
+ @NotNull
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+
+ @NotNull
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+}
diff --git a/src/main/java/io/papermc/paper/event/chunk/ChunkDeserializeEvent.java b/src/main/java/io/papermc/paper/event/chunk/ChunkDeserializeEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..e90357708c7d06150f446161d7761b6e22fb64d5
--- /dev/null
+++ b/src/main/java/io/papermc/paper/event/chunk/ChunkDeserializeEvent.java
@@ -0,0 +1,32 @@
+package io.papermc.paper.event.chunk;
+
+import org.bukkit.Chunk;
+import org.bukkit.World;
+import org.bukkit.event.HandlerList;
+import org.bukkit.persistence.PersistentDataContainer;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Called when a chunk is loaded from disk.
+ * You can use this event to manipulate the persistent data container of the chunk after it is loaded.
+ * Since by default chunk loading is asynchronously on paper this might be an alternative to the ChunkLoadEvent in some cases.
+ * This event might get called asynchronously.
+ */
+public class ChunkDeserializeEvent extends ChunkDataEvent {
+ private static final HandlerList handlers = new HandlerList();
+ public ChunkDeserializeEvent(@NotNull final World world, @Nullable final Chunk chunk, final int chunkX, final int chunkZ, @NotNull final PersistentDataContainer chunkPersistentDataContainer) {
+ super(world, chunk, chunkX, chunkZ, chunkPersistentDataContainer);
+ }
+
+ @NotNull
+ @Override
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+ @NotNull
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+}
diff --git a/src/main/java/io/papermc/paper/event/chunk/ChunkGenerateEvent.java b/src/main/java/io/papermc/paper/event/chunk/ChunkGenerateEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..1f279d528ce2be534911686a4574596334ea944a
--- /dev/null
+++ b/src/main/java/io/papermc/paper/event/chunk/ChunkGenerateEvent.java
@@ -0,0 +1,32 @@
+package io.papermc.paper.event.chunk;
+
+import org.bukkit.Chunk;
+import org.bukkit.World;
+import org.bukkit.event.HandlerList;
+import org.bukkit.persistence.PersistentDataContainer;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Called when a chunk is generated.
+ * You can use this event to manipulate the persistent data container of the chunk before it is generated.
+ * Since by default chunk generation is asynchronously on paper this might be an alternative to the ChunkLoadEvent in some cases.
+ * This event is might get called asynchronously.
+ */
+public class ChunkGenerateEvent extends ChunkDataEvent {
+ private static final HandlerList handlers = new HandlerList();
+ public ChunkGenerateEvent(@NotNull final World world, @Nullable final Chunk chunk, final int chunkX, final int chunkZ, @NotNull final PersistentDataContainer chunkPersistentDataContainer) {
+ super(world, chunk, chunkX, chunkZ, chunkPersistentDataContainer);
+ }
+
+ @NotNull
+ @Override
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+ @NotNull
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+}
diff --git a/src/main/java/io/papermc/paper/event/chunk/ChunkSerializeEvent.java b/src/main/java/io/papermc/paper/event/chunk/ChunkSerializeEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..77d7711f1c8513e4f1895a187cc758c376ab93e7
--- /dev/null
+++ b/src/main/java/io/papermc/paper/event/chunk/ChunkSerializeEvent.java
@@ -0,0 +1,43 @@
+package io.papermc.paper.event.chunk;
+
+import org.bukkit.Chunk;
+import org.bukkit.World;
+import org.bukkit.event.HandlerList;
+import org.bukkit.persistence.PersistentDataContainer;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Called when a chunk is saved to disk.
+ * You can use this event to manipulate the persistent data container of the chunk after it is loaded.
+ * Since by default chunk saving is asynchronously on paper this might be an alternative to the ChunkUnloadEvent in some cases.
+ * This event might get called asynchronously.
+ */
+public class ChunkSerializeEvent extends ChunkDataEvent {
+ private static final HandlerList handlers = new HandlerList();
+ private final boolean unloaded;
+
+ public ChunkSerializeEvent(@NotNull final World world, @Nullable final Chunk chunk, final int chunkX, final int chunkZ, @NotNull final PersistentDataContainer chunkPersistentDataContainer, boolean unloaded) {
+ super(world, chunk, chunkX, chunkZ, chunkPersistentDataContainer);
+ this.unloaded = unloaded;
+ }
+
+ /**
+ * Gets if this chunk was unloaded after being saved
+ * @return true if the chunk is also unloaded
+ */
+ public boolean isUnloaded() {
+ return this.unloaded;
+ }
+
+ @NotNull
+ @Override
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+ @NotNull
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+}

View File

@ -0,0 +1,226 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: derverdox <mail.ysp@web.de>
Date: Thu, 30 Nov 2023 01:22:23 +0100
Subject: [PATCH] Adding ChunkSerialization and Generation events.
diff --git a/src/main/java/de/verdox/mccreativelab/worldgen/ChunkDataUtil.java b/src/main/java/de/verdox/mccreativelab/worldgen/ChunkDataUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..682f4ac0708ba8bff474f8dedb14df03dd8e2189
--- /dev/null
+++ b/src/main/java/de/verdox/mccreativelab/worldgen/ChunkDataUtil.java
@@ -0,0 +1,37 @@
+package de.verdox.mccreativelab.worldgen;
+
+import io.papermc.paper.event.chunk.ChunkGenerateEvent;
+import io.papermc.paper.event.chunk.ChunkDeserializeEvent;
+import io.papermc.paper.event.chunk.ChunkSerializeEvent;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.level.chunk.ChunkAccess;
+import net.minecraft.world.level.chunk.LevelChunk;
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.CraftChunk;
+import org.bukkit.craftbukkit.event.CraftEventFactory;
+import org.bukkit.persistence.PersistentDataContainer;
+
+public class ChunkDataUtil {
+
+ public static void callChunkDataCreateEvent(ServerLevel serverLevel, ChunkAccess chunkAccess, PersistentDataContainer persistentDataContainer) {
+ Bukkit.getPluginManager()
+ .callEvent(new ChunkGenerateEvent(serverLevel.getWorld(), getCraftChunk(chunkAccess), chunkAccess.locX, chunkAccess.locZ, persistentDataContainer));
+ chunkAccess.persistentDataContainer.dirty(true);
+ }
+
+ public static void callChunkDataLoadEvent(ServerLevel serverLevel, ChunkAccess chunkAccess, PersistentDataContainer persistentDataContainer) {
+ Bukkit.getPluginManager().
+ callEvent(new ChunkDeserializeEvent(serverLevel.getWorld(), getCraftChunk(chunkAccess), chunkAccess.locX, chunkAccess.locZ, persistentDataContainer));
+ }
+
+ public static void callChunkDataSaveEvent(ServerLevel serverLevel, ChunkAccess chunkAccess, PersistentDataContainer persistentDataContainer, boolean unloadingChunk) {
+ Bukkit.getPluginManager()
+ .callEvent(new ChunkSerializeEvent(serverLevel.getWorld(), getCraftChunk(chunkAccess), chunkAccess.locX, chunkAccess.locZ, persistentDataContainer, unloadingChunk));
+ chunkAccess.persistentDataContainer.dirty(true);
+ }
+
+ private static CraftChunk getCraftChunk(ChunkAccess chunkAccess) {
+ return chunkAccess instanceof LevelChunk chunk ? new CraftChunk(chunk) : null;
+ }
+
+}
diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java
index b66a7d4aab887309579154815a0d4abf9de506b0..9c403423551db5280edaca5869e42e23ae6aa5c2 100644
--- a/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java
+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java
@@ -1817,7 +1817,9 @@ public final class NewChunkHolder {
public void run() {
final CompoundTag toSerialize;
try {
- toSerialize = ChunkSerializer.saveChunk(this.world, this.chunk, this.asyncSaveData);
+ // Paper start - add ChunkDataEvents
+ toSerialize = ChunkSerializer.saveChunk(this.world, this.chunk, this.asyncSaveData, false);
+ // Paper end - add ChunkDataEvents
} catch (final ThreadDeath death) {
throw death;
} catch (final Throwable throwable) {
@@ -1825,7 +1827,9 @@ public final class NewChunkHolder {
this.world.chunkTaskScheduler.scheduleChunkTask(this.chunk.locX, this.chunk.locZ, () -> {
final CompoundTag synchronousSave;
try {
- synchronousSave = ChunkSerializer.saveChunk(AsyncChunkSerializeTask.this.world, AsyncChunkSerializeTask.this.chunk, AsyncChunkSerializeTask.this.asyncSaveData);
+ // Paper start - add ChunkDataEvents
+ synchronousSave = ChunkSerializer.saveChunk(AsyncChunkSerializeTask.this.world, AsyncChunkSerializeTask.this.chunk, AsyncChunkSerializeTask.this.asyncSaveData, false);
+ // Paper end - add ChunkDataEvents
} catch (final ThreadDeath death) {
throw death;
} catch (final Throwable throwable2) {
@@ -1881,7 +1885,9 @@ public final class NewChunkHolder {
}
}
- final CompoundTag save = ChunkSerializer.saveChunk(this.world, chunk, null);
+ // Paper start - add ChunkDataEvents
+ final CompoundTag save = ChunkSerializer.saveChunk(this.world, chunk, null, unloading);
+ // Paper end - add ChunkDataEvents
if (unloading) {
completing = true;
diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java
index a907b79fd8291a0e92db138f37239d17424188a1..802735bf4f34ed225ab08252c0f10b4c5bb915a0 100644
--- a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java
+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java
@@ -27,6 +27,7 @@ import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.blending.Blender;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
+import org.bukkit.craftbukkit.event.CraftEventFactory;
public class ChunkStatus {
@@ -68,6 +69,9 @@ public class ChunkStatus {
}
worldserver.onStructureStartsAvailable(ichunkaccess);
+ // Paper start - add ChunkDataEvents
+ CraftEventFactory.callChunkGenerateEvent(worldserver, ichunkaccess, ichunkaccess.persistentDataContainer);
+ // Paper end - add ChunkDataEvents
return CompletableFuture.completedFuture(Either.left(ichunkaccess));
}, (chunkstatus, worldserver, structuretemplatemanager, lightenginethreaded, function, ichunkaccess) -> {
worldserver.onStructureStartsAvailable(ichunkaccess);
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
index 1379084a80ce25644f13736b4a5ee5fabbd9ec1f..fedfdcf893478d9df9e35851d0ab4edb9a30856f 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
@@ -67,6 +67,7 @@ import net.minecraft.world.level.lighting.LevelLightEngine;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.ticks.LevelChunkTicks;
import net.minecraft.world.ticks.ProtoChunkTicks;
+import org.bukkit.craftbukkit.event.CraftEventFactory;
import org.slf4j.Logger;
public class ChunkSerializer {
@@ -366,6 +367,9 @@ public class ChunkSerializer {
}
if (chunkstatus_type == ChunkStatus.ChunkType.LEVELCHUNK) {
+ // Paper start - add ChunkDataEvents
+ CraftEventFactory.callChunkDeserializeEvent(world, (ChunkAccess) object1, ((ChunkAccess) object1).persistentDataContainer);
+ // Paper start - add ChunkDataEvents
return new InProgressChunkHolder(new ImposterProtoChunk((LevelChunk) object1, false)); // Paper - Async chunk loading
} else {
ProtoChunk protochunk1 = (ProtoChunk) object1;
@@ -401,6 +405,9 @@ public class ChunkSerializer {
protochunk1.setCarvingMask(worldgenstage_features, new CarvingMask(nbttagcompound5.getLongArray(s1), ((ChunkAccess) object1).getMinBuildHeight()));
}
+ // Paper start - add ChunkDataEvents
+ CraftEventFactory.callChunkDeserializeEvent(world, (ChunkAccess) object1, ((ChunkAccess) object1).persistentDataContainer);
+ // Paper end - add ChunkDataEvents
return new InProgressChunkHolder(protochunk1); // Paper - Async chunk loading
}
}
@@ -452,11 +459,12 @@ public class ChunkSerializer {
// CraftBukkit end
public static CompoundTag write(ServerLevel world, ChunkAccess chunk) {
- // Paper start
- return saveChunk(world, chunk, null);
+ // Paper start - add ChunkDataEvents
+ return saveChunk(world, chunk, null, false);
+
}
- public static CompoundTag saveChunk(ServerLevel world, ChunkAccess chunk, @org.checkerframework.checker.nullness.qual.Nullable AsyncSaveData asyncsavedata) {
- // Paper end
+ public static CompoundTag saveChunk(ServerLevel world, ChunkAccess chunk, @org.checkerframework.checker.nullness.qual.Nullable AsyncSaveData asyncsavedata, boolean unloadingChunk) {
+ // Paper end - add ChunkDataEvents
// Paper start - rewrite light impl
final int minSection = io.papermc.paper.util.WorldUtil.getMinLightSection(world);
final int maxSection = io.papermc.paper.util.WorldUtil.getMaxLightSection(world);
@@ -637,6 +645,9 @@ public class ChunkSerializer {
nbttagcompound.put("Heightmaps", nbttagcompound3);
nbttagcompound.put("structures", ChunkSerializer.packStructureData(StructurePieceSerializationContext.fromLevel(world), chunkcoordintpair, chunk.getAllStarts(), chunk.getAllReferences()));
+ // Paper start - add ChunkDataEvents
+ CraftEventFactory.callChunkSaveEvent(world, chunk, chunk.persistentDataContainer, unloadingChunk);
+ // Paper end - add ChunkDataEvents
// CraftBukkit start - store chunk persistent data in nbt
if (!chunk.persistentDataContainer.isEmpty()) { // SPIGOT-6814: Always save PDC to account for 1.17 to 1.18 chunk upgrading.
nbttagcompound.put("ChunkBukkitValues", chunk.persistentDataContainer.toTagCompound());
diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
index 5dc160b743534665c6b3efb10b10f7c36e2da5ab..039383f4a17c7ace34a2cf93b75e89bf519c1612 100644
--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
@@ -16,6 +16,9 @@ import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nullable;
+import io.papermc.paper.event.chunk.ChunkDeserializeEvent;
+import io.papermc.paper.event.chunk.ChunkGenerateEvent;
+import io.papermc.paper.event.chunk.ChunkSerializeEvent;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.protocol.game.ServerboundContainerClosePacket;
@@ -56,6 +59,8 @@ import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.entity.SignBlockEntity;
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument;
+import net.minecraft.world.level.chunk.ChunkAccess;
+import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.level.storage.loot.LootTable;
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
@@ -242,6 +247,7 @@ import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.InventoryView;
import org.bukkit.inventory.Recipe;
import org.bukkit.inventory.meta.BookMeta;
+import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.potion.PotionEffect;
import org.bukkit.util.Vector;
@@ -2166,4 +2172,27 @@ public class CraftEventFactory {
return event;
}
// Paper end - add EntityFertilizeEggEvent
+
+ // Paper start - add ChunkDataEvents
+ public static void callChunkGenerateEvent(ServerLevel serverLevel, ChunkAccess chunkAccess, PersistentDataContainer persistentDataContainer) {
+ Bukkit.getPluginManager()
+ .callEvent(new ChunkGenerateEvent(serverLevel.getWorld(), getCraftChunk(chunkAccess), chunkAccess.locX, chunkAccess.locZ, persistentDataContainer));
+ chunkAccess.persistentDataContainer.dirty(true);
+ }
+
+ public static void callChunkDeserializeEvent(ServerLevel serverLevel, ChunkAccess chunkAccess, PersistentDataContainer persistentDataContainer) {
+ Bukkit.getPluginManager().
+ callEvent(new ChunkDeserializeEvent(serverLevel.getWorld(), getCraftChunk(chunkAccess), chunkAccess.locX, chunkAccess.locZ, persistentDataContainer));
+ }
+
+ public static void callChunkSaveEvent(ServerLevel serverLevel, ChunkAccess chunkAccess, PersistentDataContainer persistentDataContainer, boolean unloadingChunk) {
+ Bukkit.getPluginManager()
+ .callEvent(new ChunkSerializeEvent(serverLevel.getWorld(), getCraftChunk(chunkAccess), chunkAccess.locX, chunkAccess.locZ, persistentDataContainer, unloadingChunk));
+ chunkAccess.persistentDataContainer.dirty(true);
+ }
+
+ private static CraftChunk getCraftChunk(ChunkAccess chunkAccess) {
+ return chunkAccess instanceof LevelChunk chunk ? new CraftChunk(chunk) : null;
+ }
+ // Paper end - add ChunkDataEvents
}