Added InstanceContainer#copy and Chunk#copy

This commit is contained in:
themode 2020-10-31 19:22:23 +01:00
parent 0184ada9a0
commit 6e36f3242d
11 changed files with 172 additions and 32 deletions

View File

@ -230,6 +230,19 @@ public abstract class Chunk implements Viewable, DataContainer {
@NotNull
protected abstract ChunkDataPacket createFreshPacket();
/**
* Creates a copy of this chunk, including blocks state id, custom block id, biomes, update data.
* <p>
* The instance and chunk position (X/Z) can be modified using the given arguments.
*
* @param instance the instance of the new chunk
* @param chunkX the new chunk X
* @param chunkZ the new chunK Z
* @return a copy of this chunk with a potentially new instance and position
*/
@NotNull
public abstract Chunk copy(@NotNull Instance instance, int chunkX, int chunkZ);
/**
* Gets the {@link CustomBlock} at a position.
*

View File

@ -11,6 +11,7 @@ import net.minestom.server.data.SerializableDataImpl;
import net.minestom.server.entity.pathfinding.PFBlockDescription;
import net.minestom.server.instance.block.CustomBlock;
import net.minestom.server.network.packet.server.play.ChunkDataPacket;
import net.minestom.server.utils.ArrayUtils;
import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.binary.BinaryReader;
@ -404,4 +405,17 @@ public class DynamicChunk extends Chunk {
return fullDataPacket;
}
@NotNull
@Override
public Chunk copy(@NotNull Instance instance, int chunkX, int chunkZ) {
DynamicChunk dynamicChunk = new DynamicChunk(instance, biomes.clone(), chunkX, chunkZ);
ArrayUtils.copyToDestination(dynamicChunk.blocksStateId, blocksStateId);
ArrayUtils.copyToDestination(dynamicChunk.customBlocksId, customBlocksId);
dynamicChunk.blocksData.putAll(blocksData);
dynamicChunk.updatableBlocks.addAll(updatableBlocks);
dynamicChunk.updatableBlocksLastUpdate.putAll(updatableBlocksLastUpdate);
dynamicChunk.blockEntities.addAll(blockEntities);
return dynamicChunk;
}
}

View File

@ -72,6 +72,10 @@ public class InstanceContainer extends Instance {
// used to supply a new chunk object at a position when requested
private ChunkSupplier chunkSupplier;
// Fields for instance copy
protected InstanceContainer srcInstance; // only present if this instance has been created using a copy
private long lastBlockChangeTime; // Time at which the last block change happened (#setBlock)
/**
* Creates an {@link InstanceContainer}.
*
@ -169,6 +173,9 @@ public class InstanceContainer extends Instance {
synchronized (chunk) {
// Refresh the last block change time
this.lastBlockChangeTime = System.currentTimeMillis();
final boolean isCustomBlock = customBlock != null;
final BlockPosition blockPosition = new BlockPosition(x, y, z);
@ -609,6 +616,63 @@ public class InstanceContainer extends Instance {
this.sharedInstances.add(sharedInstance);
}
/**
* Copies all the chunks of this instance and create a new instance container with all of them.
* <p>
* Chunks are copied with {@link Chunk#copy(Instance, int, int)},
* {@link UUID} is randomized, {@link DimensionType} is passed over and the {@link StorageLocation} is null.
*
* @return an {@link InstanceContainer} with the exact same chunks as 'this'
*/
public synchronized InstanceContainer copy() {
InstanceContainer copiedInstance = new InstanceContainer(UUID.randomUUID(), getDimensionType(), null);
copiedInstance.srcInstance = this;
copiedInstance.lastBlockChangeTime = lastBlockChangeTime;
ConcurrentHashMap<Long, Chunk> copiedChunks = copiedInstance.chunks;
for (Map.Entry<Long, Chunk> entry : chunks.entrySet()) {
final long index = entry.getKey();
final Chunk chunk = entry.getValue();
final Chunk copiedChunk = chunk.copy(copiedInstance, chunk.getChunkX(), chunk.getChunkZ());
copiedChunks.put(index, copiedChunk);
UPDATE_MANAGER.signalChunkLoad(copiedInstance, chunk.getChunkX(), chunk.getChunkZ());
}
return copiedInstance;
}
/**
* Gets the instance from which this one has been copied.
* <p>
* Only present if this instance has been created with {@link InstanceContainer#copy()}.
*
* @return the instance source, null if not created by a copy
*/
@Nullable
public InstanceContainer getSrcInstance() {
return srcInstance;
}
/**
* Gets the last time at which a block changed.
*
* @return the time at which the last block changed in milliseconds, 0 if never
*/
public long getLastBlockChangeTime() {
return lastBlockChangeTime;
}
/**
* Signals the instance that a block changed.
* <p>
* Useful if you change blocks values directly using a {@link Chunk} object.
*/
public void refreshLastBlockChangeTime() {
this.lastBlockChangeTime = System.currentTimeMillis();
}
/**
* Adds a {@link Chunk} to the internal instance map.
*

View File

@ -1,5 +1,6 @@
package net.minestom.server.instance;
import io.netty.buffer.ByteBuf;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minestom.server.data.Data;
import net.minestom.server.instance.block.BlockProvider;
@ -111,4 +112,16 @@ public class StaticChunk extends Chunk {
return fullDataPacket;
}
@NotNull
@Override
public Chunk copy(@NotNull Instance instance, int chunkX, int chunkZ) {
StaticChunk staticChunk = new StaticChunk(instance, biomes.clone(), chunkX, chunkZ, blockProvider);
// Prevent re-writing the whole packet since it is static anyway
final ByteBuf packetBuffer = getFullDataPacket();
if (packetBuffer != null) {
staticChunk.setFullDataPacket(packetBuffer);
}
return staticChunk;
}
}

View File

@ -89,8 +89,9 @@ public class BlockBatch implements InstanceBatch {
// Execute the callback if this was the last chunk to process
if (isLast) {
this.instance.refreshLastBlockChangeTime();
if (callback != null) {
instance.scheduleNextTick(inst -> callback.run());
this.instance.scheduleNextTick(inst -> callback.run());
}
}

View File

@ -130,8 +130,10 @@ public class ChunkBatch implements InstanceBatch {
chunk.sendChunkUpdate();
if (callback != null) {
this.instance.refreshLastBlockChangeTime();
if (safeCallback) {
instance.scheduleNextTick(inst -> callback.accept(chunk));
this.instance.scheduleNextTick(inst -> callback.accept(chunk));
} else {
callback.accept(chunk);
}

View File

@ -6,6 +6,7 @@ import it.unimi.dsi.fastutil.longs.LongArraySet;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minestom.server.instance.Instance;
import net.minestom.server.utils.chunk.ChunkUtils;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@ -30,19 +31,19 @@ public class PerGroupChunkProvider extends ThreadProvider {
private final Map<Instance, Map<LongSet, Instance>> instanceInstanceMap = new ConcurrentHashMap<>();
@Override
public void onInstanceCreate(Instance instance) {
this.instanceChunksGroupMap.put(instance, new Long2ObjectOpenHashMap<>());
this.instanceInstanceMap.put(instance, new HashMap<>());
public void onInstanceCreate(@NotNull Instance instance) {
this.instanceChunksGroupMap.putIfAbsent(instance, new Long2ObjectOpenHashMap<>());
this.instanceInstanceMap.putIfAbsent(instance, new HashMap<>());
}
@Override
public void onInstanceDelete(Instance instance) {
public void onInstanceDelete(@NotNull Instance instance) {
this.instanceChunksGroupMap.remove(instance);
this.instanceInstanceMap.remove(instance);
}
@Override
public void onChunkLoad(Instance instance, int chunkX, int chunkZ) {
public void onChunkLoad(@NotNull Instance instance, int chunkX, int chunkZ) {
final long loadedChunkIndex = ChunkUtils.getChunkIndex(chunkX, chunkZ);
Long2ObjectMap<LongSet> chunksGroupMap = getChunksGroupMap(instance);
@ -98,7 +99,7 @@ public class PerGroupChunkProvider extends ThreadProvider {
}
@Override
public void onChunkUnload(Instance instance, int chunkX, int chunkZ) {
public void onChunkUnload(@NotNull Instance instance, int chunkX, int chunkZ) {
Long2ObjectMap<LongSet> chunksGroupMap = getChunksGroupMap(instance);
Map<LongSet, Instance> instanceMap = getInstanceMap(instance);
@ -116,6 +117,7 @@ public class PerGroupChunkProvider extends ThreadProvider {
}
}
@NotNull
@Override
public List<Future<?>> update(long time) {
List<Future<?>> futures = new ArrayList<>();
@ -149,11 +151,11 @@ public class PerGroupChunkProvider extends ThreadProvider {
}
private Long2ObjectMap<LongSet> getChunksGroupMap(Instance instance) {
return instanceChunksGroupMap.get(instance);
return instanceChunksGroupMap.computeIfAbsent(instance, inst -> new Long2ObjectOpenHashMap<>());
}
private Map<LongSet, Instance> getInstanceMap(Instance instance) {
return instanceInstanceMap.get(instance);
return instanceInstanceMap.computeIfAbsent(instance, inst -> new HashMap<>());
}
}

View File

@ -4,6 +4,7 @@ import it.unimi.dsi.fastutil.longs.LongArraySet;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minestom.server.instance.Instance;
import net.minestom.server.utils.chunk.ChunkUtils;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashMap;
@ -19,17 +20,17 @@ public class PerInstanceThreadProvider extends ThreadProvider {
private final Map<Instance, LongSet> instanceChunkMap = new HashMap<>();
@Override
public void onInstanceCreate(Instance instance) {
this.instanceChunkMap.put(instance, new LongArraySet());
public void onInstanceCreate(@NotNull Instance instance) {
this.instanceChunkMap.putIfAbsent(instance, new LongArraySet());
}
@Override
public void onInstanceDelete(Instance instance) {
public void onInstanceDelete(@NotNull Instance instance) {
this.instanceChunkMap.remove(instance);
}
@Override
public void onChunkLoad(Instance instance, int chunkX, int chunkZ) {
public void onChunkLoad(@NotNull Instance instance, int chunkX, int chunkZ) {
// Add the loaded chunk to the instance chunks list
LongSet chunkCoordinates = getChunkCoordinates(instance);
final long index = ChunkUtils.getChunkIndex(chunkX, chunkZ);
@ -37,7 +38,7 @@ public class PerInstanceThreadProvider extends ThreadProvider {
}
@Override
public void onChunkUnload(Instance instance, int chunkX, int chunkZ) {
public void onChunkUnload(@NotNull Instance instance, int chunkX, int chunkZ) {
LongSet chunkCoordinates = getChunkCoordinates(instance);
final long index = ChunkUtils.getChunkIndex(chunkX, chunkZ);
// Remove the unloaded chunk from the instance list
@ -45,6 +46,7 @@ public class PerInstanceThreadProvider extends ThreadProvider {
}
@NotNull
@Override
public List<Future<?>> update(long time) {
List<Future<?>> futures = new ArrayList<>();
@ -59,7 +61,7 @@ public class PerInstanceThreadProvider extends ThreadProvider {
}
private LongSet getChunkCoordinates(Instance instance) {
return instanceChunkMap.get(instance);
return instanceChunkMap.computeIfAbsent(instance, inst -> new LongArraySet());
}
}

View File

@ -8,6 +8,8 @@ import net.minestom.server.instance.InstanceContainer;
import net.minestom.server.instance.SharedInstance;
import net.minestom.server.utils.chunk.ChunkUtils;
import net.minestom.server.utils.thread.MinestomThread;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Set;
@ -39,27 +41,29 @@ public abstract class ThreadProvider {
}
/**
* Called when an {@link Instance} is created.
* Called when an {@link Instance} is registered.
*
* @param instance the newly create {@link Instance}
*/
public abstract void onInstanceCreate(Instance instance);
public abstract void onInstanceCreate(@NotNull Instance instance);
/**
* Called when an {@link Instance} is deleted.
* Called when an {@link Instance} is unregistered.
*
* @param instance the deleted {@link Instance}
*/
public abstract void onInstanceDelete(Instance instance);
public abstract void onInstanceDelete(@NotNull Instance instance);
/**
* Called when a chunk is loaded.
* <p>
* Be aware that this is possible for an instance to load chunks before being registered.
*
* @param instance the instance of the chunk
* @param chunkX the chunk X
* @param chunkZ the chunk Z
*/
public abstract void onChunkLoad(Instance instance, int chunkX, int chunkZ);
public abstract void onChunkLoad(@NotNull Instance instance, int chunkX, int chunkZ);
/**
* Called when a chunk is unloaded.
@ -68,7 +72,7 @@ public abstract class ThreadProvider {
* @param chunkX the chunk X
* @param chunkZ the chunk Z
*/
public abstract void onChunkUnload(Instance instance, int chunkX, int chunkZ);
public abstract void onChunkUnload(@NotNull Instance instance, int chunkX, int chunkZ);
/**
* Performs a server tick for all chunks based on their linked thread.
@ -76,6 +80,7 @@ public abstract class ThreadProvider {
* @param time the update time in milliseconds
* @return the futures to execute to complete the tick
*/
@NotNull
public abstract List<Future<?>> update(long time);
/**
@ -110,7 +115,7 @@ public abstract class ThreadProvider {
* @param chunkIndex the index of the chunk {@link ChunkUtils#getChunkIndex(int, int)}
* @param time the time of the update in milliseconds
*/
protected void processChunkTick(Instance instance, long chunkIndex, long time) {
protected void processChunkTick(@NotNull Instance instance, long chunkIndex, long time) {
final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex);
final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex);
@ -129,7 +134,7 @@ public abstract class ThreadProvider {
* @param instance the instance
* @param time the current time in ms
*/
protected void updateInstance(Instance instance, long time) {
protected void updateInstance(@NotNull Instance instance, long time) {
// The instance
instance.tick(time);
}
@ -141,7 +146,7 @@ public abstract class ThreadProvider {
* @param chunk the chunk
* @param time the current time in ms
*/
protected void updateChunk(Instance instance, Chunk chunk, long time) {
protected void updateChunk(@NotNull Instance instance, @NotNull Chunk chunk, long time) {
chunk.tick(time, instance);
}
@ -154,7 +159,7 @@ public abstract class ThreadProvider {
* @param chunk the chunk
* @param time the current time in ms
*/
protected void updateEntities(Instance instance, Chunk chunk, long time) {
protected void updateEntities(@NotNull Instance instance, @NotNull Chunk chunk, long time) {
conditionalEntityUpdate(instance, chunk, time, null);
}
@ -165,7 +170,7 @@ public abstract class ThreadProvider {
* @param chunk the chunk
* @param time the current time in ms
*/
protected void updateObjectEntities(Instance instance, Chunk chunk, long time) {
protected void updateObjectEntities(@NotNull Instance instance, @NotNull Chunk chunk, long time) {
conditionalEntityUpdate(instance, chunk, time, entity -> entity instanceof ObjectEntity);
}
@ -176,7 +181,7 @@ public abstract class ThreadProvider {
* @param chunk the chunk
* @param time the current time in ms
*/
protected void updateLivingEntities(Instance instance, Chunk chunk, long time) {
protected void updateLivingEntities(@NotNull Instance instance, @NotNull Chunk chunk, long time) {
conditionalEntityUpdate(instance, chunk, time, entity -> entity instanceof LivingEntity);
}
@ -187,7 +192,7 @@ public abstract class ThreadProvider {
* @param chunk the chunk
* @param time the current time in ms
*/
protected void updateCreatures(Instance instance, Chunk chunk, long time) {
protected void updateCreatures(@NotNull Instance instance, @NotNull Chunk chunk, long time) {
conditionalEntityUpdate(instance, chunk, time, entity -> entity instanceof EntityCreature);
}
@ -198,7 +203,7 @@ public abstract class ThreadProvider {
* @param chunk the chunk
* @param time the current time in ms
*/
protected void updatePlayers(Instance instance, Chunk chunk, long time) {
protected void updatePlayers(@NotNull Instance instance, @NotNull Chunk chunk, long time) {
conditionalEntityUpdate(instance, chunk, time, entity -> entity instanceof Player);
}
@ -210,7 +215,8 @@ public abstract class ThreadProvider {
* @param time the current time in ms
* @param condition the condition which confirm if the update happens or not
*/
protected void conditionalEntityUpdate(Instance instance, Chunk chunk, long time, Function<Entity, Boolean> condition) {
protected void conditionalEntityUpdate(@NotNull Instance instance, @NotNull Chunk chunk, long time,
@Nullable Function<Entity, Boolean> condition) {
final Set<Entity> entities = instance.getChunkEntities(chunk);
if (!entities.isEmpty()) {
@ -231,7 +237,7 @@ public abstract class ThreadProvider {
* @param instance the instance
* @param callback the callback to run for all the {@link SharedInstance}
*/
private void updateSharedInstances(Instance instance, Consumer<SharedInstance> callback) {
private void updateSharedInstances(@NotNull Instance instance, @NotNull Consumer<SharedInstance> callback) {
if (instance instanceof InstanceContainer) {
final InstanceContainer instanceContainer = (InstanceContainer) instance;
for (SharedInstance sharedInstance : instanceContainer.getSharedInstances()) {

View File

@ -1,6 +1,7 @@
package net.minestom.server.utils;
import it.unimi.dsi.fastutil.ints.IntList;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull;
import java.util.function.Supplier;
@ -31,6 +32,11 @@ public final class ArrayUtils {
System.arraycopy(arr, index + 1, arr, index, arr.length - 1 - index);
}
public static void copyToDestination(short[] dest, short[] src) {
Check.argCondition(dest.length != src.length, "The two arrays need to have the same length");
System.arraycopy(src, 0, dest, 0, src.length);
}
/**
* Gets the differences between 2 arrays.
*

View File

@ -14,6 +14,7 @@ public final class InstanceUtils {
* @return true if the two instances share the same chunks
*/
public static boolean areLinked(Instance instance1, Instance instance2) {
// SharedInstance check
if (instance1 instanceof InstanceContainer && instance2 instanceof SharedInstance) {
return ((SharedInstance) instance2).getInstanceContainer().equals(instance1);
} else if (instance2 instanceof InstanceContainer && instance1 instanceof SharedInstance) {
@ -23,6 +24,22 @@ public final class InstanceUtils {
final InstanceContainer container2 = ((SharedInstance) instance2).getInstanceContainer();
return container1.equals(container2);
}
// InstanceContainer check (copied from)
if (instance1 instanceof InstanceContainer && instance2 instanceof InstanceContainer) {
final InstanceContainer container1 = (InstanceContainer) instance1;
final InstanceContainer container2 = (InstanceContainer) instance2;
if (container1.getSrcInstance() != null) {
return container1.getSrcInstance().equals(container2)
&& container1.getLastBlockChangeTime() == container2.getLastBlockChangeTime();
} else if (container2.getSrcInstance() != null) {
return container2.getSrcInstance().equals(container1)
&& container2.getLastBlockChangeTime() == container1.getLastBlockChangeTime();
}
}
return false;
}