Send biome updates for chunks when possible (#2443)

* Send biome updates [WIP]

* Add other platforms

* Ensure that the iterable won't be iterated twice
This commit is contained in:
Maddy Miller 2023-12-06 21:39:06 +10:00 committed by GitHub
parent e7c9e64645
commit 1a12db4fbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 111 additions and 1 deletions

View File

@ -23,6 +23,7 @@ import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Futures;
import com.mojang.datafixers.util.Either;
@ -174,6 +175,7 @@ import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@ -969,6 +971,17 @@ public final class PaperweightAdapter implements BukkitImplAdapter {
}
}
@Override
public void sendBiomeUpdates(World world, Iterable<BlockVector2> chunks) {
ServerLevel originalWorld = ((CraftWorld) world).getHandle();
List<ChunkAccess> nativeChunks = chunks instanceof Collection<BlockVector2> chunkCollection ? Lists.newArrayListWithCapacity(chunkCollection.size()) : Lists.newArrayList();
for (BlockVector2 chunk : chunks) {
nativeChunks.add(originalWorld.getChunk(chunk.getBlockX(), chunk.getBlockZ(), ChunkStatus.BIOMES, false));
}
originalWorld.getChunkSource().chunkMap.resendBiomesForChunks(nativeChunks);
}
// ------------------------------------------------------------------------
// Code that is less likely to break
// ------------------------------------------------------------------------

View File

@ -23,6 +23,7 @@ import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Futures;
import com.mojang.datafixers.util.Either;
@ -174,6 +175,7 @@ import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@ -970,6 +972,17 @@ public final class PaperweightAdapter implements BukkitImplAdapter {
}
}
@Override
public void sendBiomeUpdates(World world, Iterable<BlockVector2> chunks) {
ServerLevel originalWorld = ((CraftWorld) world).getHandle();
List<ChunkAccess> nativeChunks = chunks instanceof Collection<BlockVector2> chunkCollection ? Lists.newArrayListWithCapacity(chunkCollection.size()) : Lists.newArrayList();
for (BlockVector2 chunk : chunks) {
nativeChunks.add(originalWorld.getChunk(chunk.getBlockX(), chunk.getBlockZ(), ChunkStatus.BIOMES, false));
}
originalWorld.getChunkSource().chunkMap.resendBiomesForChunks(nativeChunks);
}
// ------------------------------------------------------------------------
// Code that is less likely to break
// ------------------------------------------------------------------------

View File

@ -23,6 +23,7 @@ import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Futures;
import com.mojang.datafixers.util.Either;
@ -174,6 +175,7 @@ import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@ -970,6 +972,17 @@ public final class PaperweightAdapter implements BukkitImplAdapter {
}
}
@Override
public void sendBiomeUpdates(World world, Iterable<BlockVector2> chunks) {
ServerLevel originalWorld = ((CraftWorld) world).getHandle();
List<ChunkAccess> nativeChunks = chunks instanceof Collection<BlockVector2> chunkCollection ? Lists.newArrayListWithCapacity(chunkCollection.size()) : Lists.newArrayList();
for (BlockVector2 chunk : chunks) {
nativeChunks.add(originalWorld.getChunk(chunk.getBlockX(), chunk.getBlockZ(), ChunkStatus.BIOMES, false));
}
originalWorld.getChunkSource().chunkMap.resendBiomesForChunks(nativeChunks);
}
// ------------------------------------------------------------------------
// Code that is less likely to break
// ------------------------------------------------------------------------

View File

@ -329,6 +329,14 @@ public class BukkitWorld extends AbstractWorld {
}
}
@Override
public void sendBiomeUpdates(Iterable<BlockVector2> chunks) {
BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter();
if (adapter != null) {
adapter.sendBiomeUpdates(getWorld(), chunks);
}
}
@Override
public boolean playEffect(Vector3 position, int type, int data) {
World world = getWorld();

View File

@ -25,6 +25,7 @@ import com.sk89q.worldedit.blocks.BaseItemStack;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.internal.wna.WorldNativeAccess;
import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.registry.state.Property;
@ -314,4 +315,17 @@ public interface BukkitImplAdapter {
default void initializeRegistries() {
}
/**
* Sends biome updates for the given chunks.
*
* <p>This doesn't modify biomes at all, it just sends the current state of the biomes
* in the world to all of the nearby players, updating the visual representation of the
* biomes on their clients.</p>
*
* @param world the world
* @param chunks a list of chunk coordinates to send biome updates for
*/
default void sendBiomeUpdates(World world, Iterable<BlockVector2> chunks) {
}
}

View File

@ -29,6 +29,7 @@ import com.sk89q.worldedit.util.SideEffect;
import com.sk89q.worldedit.util.SideEffectSet;
import com.sk89q.worldedit.util.collection.BlockMap;
import com.sk89q.worldedit.world.World;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockStateHolder;
@ -50,6 +51,7 @@ public class SideEffectExtent extends AbstractDelegateExtent {
private final World world;
private final Map<BlockVector3, BlockState> positions = BlockMap.create();
private final Set<BlockVector2> dirtyChunks = new HashSet<>();
private final Set<BlockVector2> dirtyBiomes = new HashSet<>();
private SideEffectSet sideEffectSet = SideEffectSet.defaults();
private boolean postEditSimulation;
@ -97,8 +99,14 @@ public class SideEffectExtent extends AbstractDelegateExtent {
return world.setBlock(location, block, postEditSimulation ? INTERNAL_NONE : sideEffectSet);
}
@Override
public boolean setBiome(BlockVector3 position, BiomeType biome) {
dirtyBiomes.add(BlockVector2.at(position.getBlockX() >> 4, position.getBlockZ() >> 4));
return world.setBiome(position, biome);
}
public boolean commitRequired() {
return postEditSimulation || !dirtyChunks.isEmpty();
return postEditSimulation || !dirtyChunks.isEmpty() || !dirtyBiomes.isEmpty();
}
@Override
@ -113,6 +121,10 @@ public class SideEffectExtent extends AbstractDelegateExtent {
world.fixAfterFastMode(dirtyChunks);
}
if (!dirtyBiomes.isEmpty()) {
world.sendBiomeUpdates(dirtyBiomes);
}
if (postEditSimulation) {
Iterator<Map.Entry<BlockVector3, BlockState>> positionIterator = positions.entrySet().iterator();
while (run.shouldContinue() && positionIterator.hasNext()) {

View File

@ -94,6 +94,10 @@ public abstract class AbstractWorld implements World {
public void fixAfterFastMode(Iterable<BlockVector2> chunks) {
}
@Override
public void sendBiomeUpdates(Iterable<BlockVector2> chunks) {
}
@Override
public void fixLighting(Iterable<BlockVector2> chunks) {
}

View File

@ -311,6 +311,17 @@ public interface World extends Extent, Keyed {
*/
void fixAfterFastMode(Iterable<BlockVector2> chunks);
/**
* Sends biome updates for the given chunks.
*
* <p>This doesn't modify biomes at all, it just sends the current state of the biomes
* in the world to all of the nearby players, updating the visual representation of the
* biomes on their clients.</p>
*
* @param chunks a list of chunk coordinates to send biome updates for
*/
void sendBiomeUpdates(Iterable<BlockVector2> chunks);
/**
* Relight the given chunks if possible.
*

View File

@ -23,6 +23,7 @@ import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import com.google.common.util.concurrent.Futures;
@ -115,6 +116,7 @@ import java.lang.ref.WeakReference;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@ -526,6 +528,15 @@ public class FabricWorld extends AbstractWorld {
fixLighting(chunks);
}
@Override
public void sendBiomeUpdates(Iterable<BlockVector2> chunks) {
List<ChunkAccess> nativeChunks = chunks instanceof Collection<BlockVector2> chunkCollection ? Lists.newArrayListWithCapacity(chunkCollection.size()) : Lists.newArrayList();
for (BlockVector2 chunk : chunks) {
nativeChunks.add(getWorld().getChunk(chunk.getBlockX(), chunk.getBlockZ(), ChunkStatus.BIOMES, false));
}
((ServerLevel) getWorld()).getChunkSource().chunkMap.resendBiomesForChunks(nativeChunks);
}
@Override
public void fixLighting(Iterable<BlockVector2> chunks) {
Level world = getWorld();

View File

@ -23,6 +23,7 @@ import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import com.google.common.util.concurrent.Futures;
@ -113,6 +114,7 @@ import java.lang.ref.WeakReference;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -508,6 +510,15 @@ public class ForgeWorld extends AbstractWorld {
fixLighting(chunks);
}
@Override
public void sendBiomeUpdates(Iterable<BlockVector2> chunks) {
List<ChunkAccess> nativeChunks = chunks instanceof Collection<BlockVector2> chunkCollection ? Lists.newArrayListWithCapacity(chunkCollection.size()) : Lists.newArrayList();
for (BlockVector2 chunk : chunks) {
nativeChunks.add(getWorld().getChunk(chunk.getBlockX(), chunk.getBlockZ(), ChunkStatus.BIOMES, false));
}
((ServerLevel) getWorld()).getChunkSource().chunkMap.resendBiomesForChunks(nativeChunks);
}
@Override
public void fixLighting(Iterable<BlockVector2> chunks) {
ServerLevel world = getWorld();