mirror of
https://github.com/Minestom/Minestom.git
synced 2024-12-26 02:57:37 +01:00
Snapshot API (#722)
This commit is contained in:
parent
3e184abc0f
commit
f7d44c4774
@ -18,6 +18,7 @@ import net.minestom.server.network.PacketProcessor;
|
|||||||
import net.minestom.server.network.socket.Server;
|
import net.minestom.server.network.socket.Server;
|
||||||
import net.minestom.server.recipe.RecipeManager;
|
import net.minestom.server.recipe.RecipeManager;
|
||||||
import net.minestom.server.scoreboard.TeamManager;
|
import net.minestom.server.scoreboard.TeamManager;
|
||||||
|
import net.minestom.server.snapshot.Snapshotable;
|
||||||
import net.minestom.server.thread.ThreadDispatcher;
|
import net.minestom.server.thread.ThreadDispatcher;
|
||||||
import net.minestom.server.timer.SchedulerManager;
|
import net.minestom.server.timer.SchedulerManager;
|
||||||
import net.minestom.server.world.DimensionTypeManager;
|
import net.minestom.server.world.DimensionTypeManager;
|
||||||
@ -29,7 +30,7 @@ import java.net.SocketAddress;
|
|||||||
|
|
||||||
@ApiStatus.Experimental
|
@ApiStatus.Experimental
|
||||||
@ApiStatus.NonExtendable
|
@ApiStatus.NonExtendable
|
||||||
public interface ServerProcess {
|
public interface ServerProcess extends Snapshotable {
|
||||||
/**
|
/**
|
||||||
* Handles incoming connections/players.
|
* Handles incoming connections/players.
|
||||||
*/
|
*/
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package net.minestom.server;
|
package net.minestom.server;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||||
import net.minestom.server.advancements.AdvancementManager;
|
import net.minestom.server.advancements.AdvancementManager;
|
||||||
import net.minestom.server.adventure.bossbar.BossBarManager;
|
import net.minestom.server.adventure.bossbar.BossBarManager;
|
||||||
import net.minestom.server.command.CommandManager;
|
import net.minestom.server.command.CommandManager;
|
||||||
|
import net.minestom.server.entity.Entity;
|
||||||
import net.minestom.server.event.EventDispatcher;
|
import net.minestom.server.event.EventDispatcher;
|
||||||
import net.minestom.server.event.GlobalEventHandler;
|
import net.minestom.server.event.GlobalEventHandler;
|
||||||
import net.minestom.server.event.server.ServerTickMonitorEvent;
|
import net.minestom.server.event.server.ServerTickMonitorEvent;
|
||||||
@ -21,20 +23,27 @@ import net.minestom.server.network.PacketProcessor;
|
|||||||
import net.minestom.server.network.socket.Server;
|
import net.minestom.server.network.socket.Server;
|
||||||
import net.minestom.server.recipe.RecipeManager;
|
import net.minestom.server.recipe.RecipeManager;
|
||||||
import net.minestom.server.scoreboard.TeamManager;
|
import net.minestom.server.scoreboard.TeamManager;
|
||||||
|
import net.minestom.server.snapshot.*;
|
||||||
import net.minestom.server.terminal.MinestomTerminal;
|
import net.minestom.server.terminal.MinestomTerminal;
|
||||||
import net.minestom.server.thread.Acquirable;
|
import net.minestom.server.thread.Acquirable;
|
||||||
import net.minestom.server.thread.ThreadDispatcher;
|
import net.minestom.server.thread.ThreadDispatcher;
|
||||||
import net.minestom.server.timer.SchedulerManager;
|
import net.minestom.server.timer.SchedulerManager;
|
||||||
import net.minestom.server.utils.PacketUtils;
|
import net.minestom.server.utils.PacketUtils;
|
||||||
|
import net.minestom.server.utils.collection.MappedCollection;
|
||||||
import net.minestom.server.world.DimensionTypeManager;
|
import net.minestom.server.world.DimensionTypeManager;
|
||||||
import net.minestom.server.world.biomes.BiomeManager;
|
import net.minestom.server.world.biomes.BiomeManager;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.UnknownNullability;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
final class ServerProcessImpl implements ServerProcess {
|
final class ServerProcessImpl implements ServerProcess {
|
||||||
private final static Logger LOGGER = LoggerFactory.getLogger(ServerProcessImpl.class);
|
private final static Logger LOGGER = LoggerFactory.getLogger(ServerProcessImpl.class);
|
||||||
@ -252,6 +261,33 @@ final class ServerProcessImpl implements ServerProcess {
|
|||||||
return started.get() && !stopped.get();
|
return started.get() && !stopped.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Snapshot updateSnapshot(@NotNull SnapshotUpdater updater) {
|
||||||
|
List<AtomicReference<InstanceSnapshot>> instanceRefs = new ArrayList<>();
|
||||||
|
Int2ObjectOpenHashMap<AtomicReference<EntitySnapshot>> entityRefs = new Int2ObjectOpenHashMap<>();
|
||||||
|
for (Instance instance : instance.getInstances()) {
|
||||||
|
instanceRefs.add(updater.reference(instance));
|
||||||
|
for (Entity entity : instance.getEntities()) {
|
||||||
|
entityRefs.put(entity.getEntityId(), updater.reference(entity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new SnapshotImpl(MappedCollection.plainReferences(instanceRefs), entityRefs);
|
||||||
|
}
|
||||||
|
|
||||||
|
record SnapshotImpl(Collection<InstanceSnapshot> instances,
|
||||||
|
Int2ObjectOpenHashMap<AtomicReference<EntitySnapshot>> entityRefs) implements ServerSnapshot {
|
||||||
|
@Override
|
||||||
|
public @NotNull Collection<EntitySnapshot> entities() {
|
||||||
|
return MappedCollection.plainReferences(entityRefs.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @UnknownNullability EntitySnapshot entity(int id) {
|
||||||
|
var ref = entityRefs.get(id);
|
||||||
|
return ref != null ? ref.getPlain() : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final class TickerImpl implements Ticker {
|
private final class TickerImpl implements Ticker {
|
||||||
@Override
|
@Override
|
||||||
public void tick(long nanoTime) {
|
public void tick(long nanoTime) {
|
||||||
|
@ -33,12 +33,17 @@ import net.minestom.server.permission.PermissionHandler;
|
|||||||
import net.minestom.server.potion.Potion;
|
import net.minestom.server.potion.Potion;
|
||||||
import net.minestom.server.potion.PotionEffect;
|
import net.minestom.server.potion.PotionEffect;
|
||||||
import net.minestom.server.potion.TimedPotion;
|
import net.minestom.server.potion.TimedPotion;
|
||||||
|
import net.minestom.server.snapshot.EntitySnapshot;
|
||||||
|
import net.minestom.server.snapshot.SnapshotUpdater;
|
||||||
|
import net.minestom.server.snapshot.Snapshotable;
|
||||||
import net.minestom.server.tag.Tag;
|
import net.minestom.server.tag.Tag;
|
||||||
import net.minestom.server.tag.TagHandler;
|
import net.minestom.server.tag.TagHandler;
|
||||||
|
import net.minestom.server.tag.TagReadable;
|
||||||
import net.minestom.server.thread.Acquirable;
|
import net.minestom.server.thread.Acquirable;
|
||||||
import net.minestom.server.timer.Schedulable;
|
import net.minestom.server.timer.Schedulable;
|
||||||
import net.minestom.server.timer.Scheduler;
|
import net.minestom.server.timer.Scheduler;
|
||||||
import net.minestom.server.timer.TaskSchedule;
|
import net.minestom.server.timer.TaskSchedule;
|
||||||
|
import net.minestom.server.utils.ArrayUtils;
|
||||||
import net.minestom.server.utils.PacketUtils;
|
import net.minestom.server.utils.PacketUtils;
|
||||||
import net.minestom.server.utils.async.AsyncUtils;
|
import net.minestom.server.utils.async.AsyncUtils;
|
||||||
import net.minestom.server.utils.block.BlockIterator;
|
import net.minestom.server.utils.block.BlockIterator;
|
||||||
@ -72,7 +77,7 @@ import java.util.function.UnaryOperator;
|
|||||||
* <p>
|
* <p>
|
||||||
* To create your own entity you probably want to extends {@link LivingEntity} or {@link EntityCreature} instead.
|
* To create your own entity you probably want to extends {@link LivingEntity} or {@link EntityCreature} instead.
|
||||||
*/
|
*/
|
||||||
public class Entity implements Viewable, Tickable, Schedulable, TagHandler, PermissionHandler, HoverEventSource<ShowEntity>, Sound.Emitter {
|
public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, TagHandler, PermissionHandler, HoverEventSource<ShowEntity>, Sound.Emitter {
|
||||||
|
|
||||||
private static final Int2ObjectSyncMap<Entity> ENTITY_BY_ID = Int2ObjectSyncMap.hashmap();
|
private static final Int2ObjectSyncMap<Entity> ENTITY_BY_ID = Int2ObjectSyncMap.hashmap();
|
||||||
private static final Map<UUID, Entity> ENTITY_BY_UUID = new ConcurrentHashMap<>();
|
private static final Map<UUID, Entity> ENTITY_BY_UUID = new ConcurrentHashMap<>();
|
||||||
@ -1526,6 +1531,18 @@ public class Entity implements Viewable, Tickable, Schedulable, TagHandler, Perm
|
|||||||
return scheduler;
|
return scheduler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull EntitySnapshot updateSnapshot(@NotNull SnapshotUpdater updater) {
|
||||||
|
final Chunk chunk = currentChunk;
|
||||||
|
final int[] viewersId = this.viewEngine.viewableOption.bitSet.toIntArray();
|
||||||
|
final int[] passengersId = ArrayUtils.mapToIntArray(passengers, Entity::getEntityId);
|
||||||
|
final Entity vehicle = this.vehicle;
|
||||||
|
return new EntitySnapshotImpl.Entity(entityType, uuid, id, position, velocity,
|
||||||
|
updater.reference(instance), chunk.getChunkX(), chunk.getChunkZ(),
|
||||||
|
viewersId, passengersId, vehicle == null ? -1 : vehicle.getEntityId(),
|
||||||
|
TagReadable.fromCompound(nbtCompound.toCompound()));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies knockback to the entity
|
* Applies knockback to the entity
|
||||||
*
|
*
|
||||||
|
115
src/main/java/net/minestom/server/entity/EntitySnapshotImpl.java
Normal file
115
src/main/java/net/minestom/server/entity/EntitySnapshotImpl.java
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package net.minestom.server.entity;
|
||||||
|
|
||||||
|
import net.minestom.server.coordinate.Pos;
|
||||||
|
import net.minestom.server.coordinate.Vec;
|
||||||
|
import net.minestom.server.snapshot.ChunkSnapshot;
|
||||||
|
import net.minestom.server.snapshot.EntitySnapshot;
|
||||||
|
import net.minestom.server.snapshot.InstanceSnapshot;
|
||||||
|
import net.minestom.server.snapshot.PlayerSnapshot;
|
||||||
|
import net.minestom.server.tag.Tag;
|
||||||
|
import net.minestom.server.tag.TagReadable;
|
||||||
|
import net.minestom.server.utils.collection.IntMappedArray;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
final class EntitySnapshotImpl {
|
||||||
|
|
||||||
|
record Entity(EntityType type, UUID uuid, int id, Pos position, Vec velocity,
|
||||||
|
AtomicReference<InstanceSnapshot> instanceRef, int chunkX, int chunkZ,
|
||||||
|
int[] viewersId, int[] passengersId, int vehicleId,
|
||||||
|
TagReadable tagReadable) implements EntitySnapshot {
|
||||||
|
@Override
|
||||||
|
public <T> @Nullable T getTag(@NotNull Tag<T> tag) {
|
||||||
|
return tagReadable.getTag(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull InstanceSnapshot instance() {
|
||||||
|
return instanceRef.getPlain();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull ChunkSnapshot chunk() {
|
||||||
|
return Objects.requireNonNull(instance().chunk(chunkX, chunkZ));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Collection<@NotNull PlayerSnapshot> viewers() {
|
||||||
|
return new IntMappedArray<>(viewersId, id -> (PlayerSnapshot) instance().server().entity(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Collection<@NotNull EntitySnapshot> passengers() {
|
||||||
|
return new IntMappedArray<>(passengersId, id -> instance().server().entity(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable EntitySnapshot vehicle() {
|
||||||
|
if (vehicleId == -1) return null;
|
||||||
|
return instance().server().entity(vehicleId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record Player(EntitySnapshot snapshot, String username,
|
||||||
|
GameMode gameMode) implements PlayerSnapshot {
|
||||||
|
@Override
|
||||||
|
public @NotNull EntityType type() {
|
||||||
|
return snapshot.type();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull UUID uuid() {
|
||||||
|
return snapshot.uuid();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int id() {
|
||||||
|
return snapshot.id();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Pos position() {
|
||||||
|
return snapshot.position();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Vec velocity() {
|
||||||
|
return snapshot.velocity();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull InstanceSnapshot instance() {
|
||||||
|
return snapshot.instance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull ChunkSnapshot chunk() {
|
||||||
|
return snapshot.chunk();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Collection<@NotNull PlayerSnapshot> viewers() {
|
||||||
|
return snapshot.viewers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Collection<@NotNull EntitySnapshot> passengers() {
|
||||||
|
return snapshot.passengers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable EntitySnapshot vehicle() {
|
||||||
|
return snapshot.vehicle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> @Nullable T getTag(@NotNull Tag<T> tag) {
|
||||||
|
return snapshot.getTag(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -64,6 +64,9 @@ import net.minestom.server.recipe.RecipeManager;
|
|||||||
import net.minestom.server.resourcepack.ResourcePack;
|
import net.minestom.server.resourcepack.ResourcePack;
|
||||||
import net.minestom.server.scoreboard.BelowNameTag;
|
import net.minestom.server.scoreboard.BelowNameTag;
|
||||||
import net.minestom.server.scoreboard.Team;
|
import net.minestom.server.scoreboard.Team;
|
||||||
|
import net.minestom.server.snapshot.EntitySnapshot;
|
||||||
|
import net.minestom.server.snapshot.PlayerSnapshot;
|
||||||
|
import net.minestom.server.snapshot.SnapshotUpdater;
|
||||||
import net.minestom.server.statistic.PlayerStatistic;
|
import net.minestom.server.statistic.PlayerStatistic;
|
||||||
import net.minestom.server.timer.SchedulerManager;
|
import net.minestom.server.timer.SchedulerManager;
|
||||||
import net.minestom.server.utils.MathUtils;
|
import net.minestom.server.utils.MathUtils;
|
||||||
@ -1958,6 +1961,12 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||||||
return Locale.forLanguageTag(locale.replace("_", "-"));
|
return Locale.forLanguageTag(locale.replace("_", "-"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull PlayerSnapshot updateSnapshot(@NotNull SnapshotUpdater updater) {
|
||||||
|
final EntitySnapshot snapshot = super.updateSnapshot(updater);
|
||||||
|
return new EntitySnapshotImpl.Player(snapshot, username, gameMode);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the player's locale. This will only set the locale of the player as it
|
* Sets the player's locale. This will only set the locale of the player as it
|
||||||
* is stored in the server. This will also be reset if the settings are refreshed.
|
* is stored in the server. This will also be reset if the settings are refreshed.
|
||||||
|
@ -8,6 +8,7 @@ import net.minestom.server.entity.Player;
|
|||||||
import net.minestom.server.entity.pathfinding.PFColumnarSpace;
|
import net.minestom.server.entity.pathfinding.PFColumnarSpace;
|
||||||
import net.minestom.server.instance.block.Block;
|
import net.minestom.server.instance.block.Block;
|
||||||
import net.minestom.server.network.packet.server.play.ChunkDataPacket;
|
import net.minestom.server.network.packet.server.play.ChunkDataPacket;
|
||||||
|
import net.minestom.server.snapshot.Snapshotable;
|
||||||
import net.minestom.server.tag.Tag;
|
import net.minestom.server.tag.Tag;
|
||||||
import net.minestom.server.tag.TagHandler;
|
import net.minestom.server.tag.TagHandler;
|
||||||
import net.minestom.server.utils.chunk.ChunkSupplier;
|
import net.minestom.server.utils.chunk.ChunkSupplier;
|
||||||
@ -34,7 +35,7 @@ import java.util.UUID;
|
|||||||
* You generally want to avoid storing references of this object as this could lead to a huge memory leak,
|
* You generally want to avoid storing references of this object as this could lead to a huge memory leak,
|
||||||
* you should store the chunk coordinates instead.
|
* you should store the chunk coordinates instead.
|
||||||
*/
|
*/
|
||||||
public abstract class Chunk implements Block.Getter, Block.Setter, Biome.Getter, Biome.Setter, Viewable, Tickable, TagHandler {
|
public abstract class Chunk implements Block.Getter, Block.Setter, Biome.Getter, Biome.Setter, Viewable, Tickable, TagHandler, Snapshotable {
|
||||||
public static final int CHUNK_SIZE_X = 16;
|
public static final int CHUNK_SIZE_X = 16;
|
||||||
public static final int CHUNK_SIZE_Z = 16;
|
public static final int CHUNK_SIZE_Z = 16;
|
||||||
public static final int CHUNK_SECTION_SIZE = 16;
|
public static final int CHUNK_SECTION_SIZE = 16;
|
||||||
|
@ -4,6 +4,7 @@ import com.extollit.gaming.ai.path.model.ColumnarOcclusionFieldList;
|
|||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||||
import net.minestom.server.MinecraftServer;
|
import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.server.coordinate.Point;
|
import net.minestom.server.coordinate.Point;
|
||||||
|
import net.minestom.server.entity.Entity;
|
||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
import net.minestom.server.entity.pathfinding.PFBlock;
|
import net.minestom.server.entity.pathfinding.PFBlock;
|
||||||
import net.minestom.server.instance.block.Block;
|
import net.minestom.server.instance.block.Block;
|
||||||
@ -13,6 +14,11 @@ import net.minestom.server.network.packet.server.play.ChunkDataPacket;
|
|||||||
import net.minestom.server.network.packet.server.play.UpdateLightPacket;
|
import net.minestom.server.network.packet.server.play.UpdateLightPacket;
|
||||||
import net.minestom.server.network.packet.server.play.data.ChunkData;
|
import net.minestom.server.network.packet.server.play.data.ChunkData;
|
||||||
import net.minestom.server.network.packet.server.play.data.LightData;
|
import net.minestom.server.network.packet.server.play.data.LightData;
|
||||||
|
import net.minestom.server.snapshot.ChunkSnapshot;
|
||||||
|
import net.minestom.server.snapshot.SnapshotUpdater;
|
||||||
|
import net.minestom.server.tag.Tag;
|
||||||
|
import net.minestom.server.tag.TagReadable;
|
||||||
|
import net.minestom.server.utils.ArrayUtils;
|
||||||
import net.minestom.server.utils.MathUtils;
|
import net.minestom.server.utils.MathUtils;
|
||||||
import net.minestom.server.utils.Utils;
|
import net.minestom.server.utils.Utils;
|
||||||
import net.minestom.server.utils.binary.BinaryWriter;
|
import net.minestom.server.utils.binary.BinaryWriter;
|
||||||
@ -238,4 +244,15 @@ public class DynamicChunk extends Chunk {
|
|||||||
skyLights, blockLights);
|
skyLights, blockLights);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull ChunkSnapshot updateSnapshot(@NotNull SnapshotUpdater updater) {
|
||||||
|
Section[] clonedSections = new Section[sections.size()];
|
||||||
|
for (int i = 0; i < clonedSections.length; i++)
|
||||||
|
clonedSections[i] = sections.get(i).clone();
|
||||||
|
var entities = instance.getEntityTracker().chunkEntities(chunkX, chunkZ, EntityTracker.Target.ENTITIES);
|
||||||
|
final int[] entityIds = ArrayUtils.mapToIntArray(entities, Entity::getEntityId);
|
||||||
|
return new InstanceSnapshotImpl.Chunk(minSection, chunkX, chunkZ,
|
||||||
|
clonedSections, entries.clone(), entityIds, updater.reference(instance),
|
||||||
|
TagReadable.fromCompound(Objects.requireNonNull(getTag(Tag.NBT))));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package net.minestom.server.instance;
|
|||||||
import it.unimi.dsi.fastutil.objects.ObjectArraySet;
|
import it.unimi.dsi.fastutil.objects.ObjectArraySet;
|
||||||
import net.kyori.adventure.identity.Identity;
|
import net.kyori.adventure.identity.Identity;
|
||||||
import net.kyori.adventure.pointer.Pointers;
|
import net.kyori.adventure.pointer.Pointers;
|
||||||
|
import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.server.Tickable;
|
import net.minestom.server.Tickable;
|
||||||
import net.minestom.server.adventure.audience.PacketGroupingAudience;
|
import net.minestom.server.adventure.audience.PacketGroupingAudience;
|
||||||
import net.minestom.server.coordinate.Point;
|
import net.minestom.server.coordinate.Point;
|
||||||
@ -17,11 +18,17 @@ import net.minestom.server.instance.block.Block;
|
|||||||
import net.minestom.server.instance.block.BlockHandler;
|
import net.minestom.server.instance.block.BlockHandler;
|
||||||
import net.minestom.server.network.packet.server.play.BlockActionPacket;
|
import net.minestom.server.network.packet.server.play.BlockActionPacket;
|
||||||
import net.minestom.server.network.packet.server.play.TimeUpdatePacket;
|
import net.minestom.server.network.packet.server.play.TimeUpdatePacket;
|
||||||
|
import net.minestom.server.snapshot.ChunkSnapshot;
|
||||||
|
import net.minestom.server.snapshot.InstanceSnapshot;
|
||||||
|
import net.minestom.server.snapshot.SnapshotUpdater;
|
||||||
|
import net.minestom.server.snapshot.Snapshotable;
|
||||||
import net.minestom.server.tag.Tag;
|
import net.minestom.server.tag.Tag;
|
||||||
import net.minestom.server.tag.TagHandler;
|
import net.minestom.server.tag.TagHandler;
|
||||||
|
import net.minestom.server.tag.TagReadable;
|
||||||
import net.minestom.server.thread.ThreadDispatcher;
|
import net.minestom.server.thread.ThreadDispatcher;
|
||||||
import net.minestom.server.timer.Schedulable;
|
import net.minestom.server.timer.Schedulable;
|
||||||
import net.minestom.server.timer.Scheduler;
|
import net.minestom.server.timer.Scheduler;
|
||||||
|
import net.minestom.server.utils.ArrayUtils;
|
||||||
import net.minestom.server.utils.PacketUtils;
|
import net.minestom.server.utils.PacketUtils;
|
||||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||||
import net.minestom.server.utils.time.Cooldown;
|
import net.minestom.server.utils.time.Cooldown;
|
||||||
@ -37,6 +44,7 @@ import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
|
|||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -50,7 +58,7 @@ import java.util.stream.Collectors;
|
|||||||
* with {@link InstanceManager#registerInstance(Instance)}, and
|
* with {@link InstanceManager#registerInstance(Instance)}, and
|
||||||
* you need to be sure to signal the {@link ThreadDispatcher} of every partition/element changes.
|
* you need to be sure to signal the {@link ThreadDispatcher} of every partition/element changes.
|
||||||
*/
|
*/
|
||||||
public abstract class Instance implements Block.Getter, Block.Setter, Tickable, Schedulable, TagHandler, PacketGroupingAudience {
|
public abstract class Instance implements Block.Getter, Block.Setter, Tickable, Schedulable, Snapshotable, TagHandler, PacketGroupingAudience {
|
||||||
|
|
||||||
private boolean registered;
|
private boolean registered;
|
||||||
|
|
||||||
@ -608,6 +616,15 @@ public abstract class Instance implements Block.Getter, Block.Setter, Tickable,
|
|||||||
return scheduler;
|
return scheduler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull InstanceSnapshot updateSnapshot(@NotNull SnapshotUpdater updater) {
|
||||||
|
final Map<Long, AtomicReference<ChunkSnapshot>> chunksMap = updater.referencesMapLong(getChunks(), ChunkUtils::getChunkIndex);
|
||||||
|
final int[] entities = ArrayUtils.mapToIntArray(entityTracker.entities(), Entity::getEntityId);
|
||||||
|
return new InstanceSnapshotImpl.Instance(updater.reference(MinecraftServer.process()),
|
||||||
|
getDimensionType(), getWorldAge(), getTime(), chunksMap, entities,
|
||||||
|
TagReadable.fromCompound(Objects.requireNonNull(getTag(Tag.NBT))));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an explosion at the given position with the given strength.
|
* Creates an explosion at the given position with the given strength.
|
||||||
* The algorithm used to compute damages is provided by {@link #getExplosionSupplier()}.
|
* The algorithm used to compute damages is provided by {@link #getExplosionSupplier()}.
|
||||||
|
@ -0,0 +1,107 @@
|
|||||||
|
package net.minestom.server.instance;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||||
|
import net.minestom.server.MinecraftServer;
|
||||||
|
import net.minestom.server.instance.block.Block;
|
||||||
|
import net.minestom.server.snapshot.ChunkSnapshot;
|
||||||
|
import net.minestom.server.snapshot.EntitySnapshot;
|
||||||
|
import net.minestom.server.snapshot.InstanceSnapshot;
|
||||||
|
import net.minestom.server.snapshot.ServerSnapshot;
|
||||||
|
import net.minestom.server.tag.Tag;
|
||||||
|
import net.minestom.server.tag.TagReadable;
|
||||||
|
import net.minestom.server.utils.collection.IntMappedArray;
|
||||||
|
import net.minestom.server.utils.collection.MappedCollection;
|
||||||
|
import net.minestom.server.world.DimensionType;
|
||||||
|
import net.minestom.server.world.biomes.Biome;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.jetbrains.annotations.UnknownNullability;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import static net.minestom.server.utils.chunk.ChunkUtils.*;
|
||||||
|
|
||||||
|
final class InstanceSnapshotImpl {
|
||||||
|
|
||||||
|
record Instance(AtomicReference<ServerSnapshot> serverRef,
|
||||||
|
DimensionType dimensionType, long worldAge, long time,
|
||||||
|
Map<Long, AtomicReference<ChunkSnapshot>> chunksMap,
|
||||||
|
int[] entitiesIds,
|
||||||
|
TagReadable tagReadable) implements InstanceSnapshot {
|
||||||
|
@Override
|
||||||
|
public @Nullable ChunkSnapshot chunk(int chunkX, int chunkZ) {
|
||||||
|
var ref = chunksMap.get(getChunkIndex(chunkX, chunkZ));
|
||||||
|
return Objects.requireNonNull(ref, "Chunk not found").getPlain();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Collection<@NotNull ChunkSnapshot> chunks() {
|
||||||
|
return MappedCollection.plainReferences(chunksMap.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Collection<EntitySnapshot> entities() {
|
||||||
|
return new IntMappedArray<>(entitiesIds, id -> server().entity(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull ServerSnapshot server() {
|
||||||
|
return serverRef.getPlain();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> @Nullable T getTag(@NotNull Tag<T> tag) {
|
||||||
|
return tagReadable.getTag(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record Chunk(int minSection, int chunkX, int chunkZ,
|
||||||
|
Section[] sections,
|
||||||
|
Int2ObjectOpenHashMap<Block> blockEntries,
|
||||||
|
int[] entitiesIds,
|
||||||
|
AtomicReference<InstanceSnapshot> instanceRef,
|
||||||
|
TagReadable tagReadable) implements ChunkSnapshot {
|
||||||
|
@Override
|
||||||
|
public @UnknownNullability Block getBlock(int x, int y, int z, @NotNull Condition condition) {
|
||||||
|
// Verify if the block object is present
|
||||||
|
if (condition != Condition.TYPE) {
|
||||||
|
final Block entry = !blockEntries.isEmpty() ?
|
||||||
|
blockEntries.get(getBlockIndex(x, y, z)) : null;
|
||||||
|
if (entry != null || condition == Condition.CACHED) {
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Retrieve the block from state id
|
||||||
|
final Section section = sections[getChunkCoordinate(y) - minSection];
|
||||||
|
final int blockStateId = section.blockPalette()
|
||||||
|
.get(toSectionRelativeCoordinate(x), toSectionRelativeCoordinate(y), toSectionRelativeCoordinate(z));
|
||||||
|
return Objects.requireNonNullElse(Block.fromStateId((short) blockStateId), Block.AIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Biome getBiome(int x, int y, int z) {
|
||||||
|
final Section section = sections[getChunkCoordinate(y) - minSection];
|
||||||
|
final int id = section.biomePalette()
|
||||||
|
.get(toSectionRelativeCoordinate(x) / 4, toSectionRelativeCoordinate(y) / 4, toSectionRelativeCoordinate(z) / 4);
|
||||||
|
return MinecraftServer.getBiomeManager().getById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> @Nullable T getTag(@NotNull Tag<T> tag) {
|
||||||
|
return tagReadable.getTag(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull InstanceSnapshot instance() {
|
||||||
|
return instanceRef.getPlain();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Collection<@NotNull EntitySnapshot> entities() {
|
||||||
|
return new IntMappedArray<>(entitiesIds, id -> instance().server().entity(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,7 @@ import java.util.function.IntUnaryOperator;
|
|||||||
/**
|
/**
|
||||||
* Palette that switches between its backend based on the use case.
|
* Palette that switches between its backend based on the use case.
|
||||||
*/
|
*/
|
||||||
final class AdaptivePalette implements Palette {
|
final class AdaptivePalette implements Palette, Cloneable {
|
||||||
final byte dimension, defaultBitsPerEntry, maxBitsPerEntry;
|
final byte dimension, defaultBitsPerEntry, maxBitsPerEntry;
|
||||||
SpecializedPalette palette;
|
SpecializedPalette palette;
|
||||||
|
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package net.minestom.server.snapshot;
|
||||||
|
|
||||||
|
import net.minestom.server.instance.block.Block;
|
||||||
|
import net.minestom.server.tag.TagReadable;
|
||||||
|
import net.minestom.server.world.biomes.Biome;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
public interface ChunkSnapshot extends Snapshot, Block.Getter, Biome.Getter, TagReadable {
|
||||||
|
int chunkX();
|
||||||
|
|
||||||
|
int chunkZ();
|
||||||
|
|
||||||
|
@NotNull InstanceSnapshot instance();
|
||||||
|
|
||||||
|
@NotNull Collection<@NotNull EntitySnapshot> entities();
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package net.minestom.server.snapshot;
|
||||||
|
|
||||||
|
import net.minestom.server.coordinate.Pos;
|
||||||
|
import net.minestom.server.coordinate.Vec;
|
||||||
|
import net.minestom.server.entity.EntityType;
|
||||||
|
import net.minestom.server.tag.TagReadable;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface EntitySnapshot extends Snapshot, TagReadable {
|
||||||
|
@NotNull EntityType type();
|
||||||
|
|
||||||
|
@NotNull UUID uuid();
|
||||||
|
|
||||||
|
int id();
|
||||||
|
|
||||||
|
@NotNull Pos position();
|
||||||
|
|
||||||
|
@NotNull Vec velocity();
|
||||||
|
|
||||||
|
@NotNull InstanceSnapshot instance();
|
||||||
|
|
||||||
|
@NotNull ChunkSnapshot chunk();
|
||||||
|
|
||||||
|
@NotNull Collection<@NotNull PlayerSnapshot> viewers();
|
||||||
|
|
||||||
|
@NotNull Collection<@NotNull EntitySnapshot> passengers();
|
||||||
|
|
||||||
|
@Nullable EntitySnapshot vehicle();
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package net.minestom.server.snapshot;
|
||||||
|
|
||||||
|
import net.minestom.server.coordinate.Point;
|
||||||
|
import net.minestom.server.instance.block.Block;
|
||||||
|
import net.minestom.server.tag.TagReadable;
|
||||||
|
import net.minestom.server.world.DimensionType;
|
||||||
|
import net.minestom.server.world.biomes.Biome;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.jetbrains.annotations.UnknownNullability;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static net.minestom.server.utils.chunk.ChunkUtils.getChunkCoordinate;
|
||||||
|
|
||||||
|
public interface InstanceSnapshot extends Snapshot, Block.Getter, Biome.Getter, TagReadable {
|
||||||
|
@NotNull DimensionType dimensionType();
|
||||||
|
|
||||||
|
long worldAge();
|
||||||
|
|
||||||
|
long time();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default @UnknownNullability Block getBlock(int x, int y, int z, @NotNull Condition condition) {
|
||||||
|
ChunkSnapshot chunk = chunk(getChunkCoordinate(x), getChunkCoordinate(z));
|
||||||
|
return Objects.requireNonNull(chunk).getBlock(x, y, z, condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default @NotNull Biome getBiome(int x, int y, int z) {
|
||||||
|
ChunkSnapshot chunk = chunk(getChunkCoordinate(x), getChunkCoordinate(z));
|
||||||
|
return Objects.requireNonNull(chunk).getBiome(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable ChunkSnapshot chunk(int chunkX, int chunkZ);
|
||||||
|
|
||||||
|
default @Nullable ChunkSnapshot chunkAt(@NotNull Point point) {
|
||||||
|
return chunk(point.chunkX(), point.chunkZ());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull Collection<@NotNull ChunkSnapshot> chunks();
|
||||||
|
|
||||||
|
@NotNull Collection<@NotNull EntitySnapshot> entities();
|
||||||
|
|
||||||
|
@NotNull ServerSnapshot server();
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package net.minestom.server.snapshot;
|
||||||
|
|
||||||
|
import net.minestom.server.entity.GameMode;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public interface PlayerSnapshot extends EntitySnapshot {
|
||||||
|
@NotNull String username();
|
||||||
|
|
||||||
|
@NotNull GameMode gameMode();
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
|
||||||
|
package net.minestom.server.snapshot;
|
||||||
|
|
||||||
|
import net.minestom.server.MinecraftServer;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.UnknownNullability;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the complete state of the server at a given moment.
|
||||||
|
*/
|
||||||
|
public interface ServerSnapshot extends Snapshot {
|
||||||
|
@NotNull Collection<@NotNull InstanceSnapshot> instances();
|
||||||
|
|
||||||
|
@NotNull Collection<EntitySnapshot> entities();
|
||||||
|
|
||||||
|
@UnknownNullability EntitySnapshot entity(int id);
|
||||||
|
|
||||||
|
@ApiStatus.Experimental
|
||||||
|
static ServerSnapshot update() {
|
||||||
|
return SnapshotUpdater.update(MinecraftServer.process());
|
||||||
|
}
|
||||||
|
}
|
12
src/main/java/net/minestom/server/snapshot/Snapshot.java
Normal file
12
src/main/java/net/minestom/server/snapshot/Snapshot.java
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package net.minestom.server.snapshot;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a snapshot of a game object.
|
||||||
|
* <p>
|
||||||
|
* Implementations must be valued-based (immutable and not relying on identity).
|
||||||
|
*/
|
||||||
|
@ApiStatus.Experimental
|
||||||
|
public interface Snapshot {
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
package net.minestom.server.snapshot;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jetbrains.annotations.Contract;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.ToIntFunction;
|
||||||
|
import java.util.function.ToLongFunction;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the context of a snapshot build.
|
||||||
|
* Used in {@link Snapshotable#updateSnapshot(SnapshotUpdater)} to create snapshot references and avoid circular dependencies.
|
||||||
|
* Updaters must never leave scope, as its data may be state related (change according to the currently processed snapshot).
|
||||||
|
* <p>
|
||||||
|
* Implementations do not need to be thread-safe and cannot be re-used.
|
||||||
|
*/
|
||||||
|
@ApiStatus.Experimental
|
||||||
|
public sealed interface SnapshotUpdater permits SnapshotUpdaterImpl {
|
||||||
|
/**
|
||||||
|
* Updates the snapshot of the given snapshotable.
|
||||||
|
* <p>
|
||||||
|
* Method must be called during a safe-point (when the server state is stable).
|
||||||
|
*
|
||||||
|
* @param snapshotable the snapshot container
|
||||||
|
* @param <T> the snapshot type
|
||||||
|
* @return the new updated snapshot
|
||||||
|
*/
|
||||||
|
static <T extends Snapshot> @NotNull T update(@NotNull Snapshotable snapshotable) {
|
||||||
|
return SnapshotUpdaterImpl.update(snapshotable);
|
||||||
|
}
|
||||||
|
|
||||||
|
<T extends Snapshot> @NotNull AtomicReference<T> reference(@NotNull Snapshotable snapshotable);
|
||||||
|
|
||||||
|
@Contract("!null -> !null")
|
||||||
|
default <T extends Snapshot> AtomicReference<T> optionalReference(Snapshotable snapshotable) {
|
||||||
|
return snapshotable != null ? reference(snapshotable) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
default <T extends Snapshot, S extends Snapshotable, K> @NotNull Map<K, AtomicReference<T>> referencesMap(@NotNull Collection<S> snapshotables,
|
||||||
|
@NotNull Function<S, K> mappingFunction) {
|
||||||
|
return snapshotables.stream().collect(Collectors.toUnmodifiableMap(mappingFunction, this::reference));
|
||||||
|
}
|
||||||
|
|
||||||
|
default <T extends Snapshot, S extends Snapshotable> @NotNull Map<Long, AtomicReference<T>> referencesMapLong(@NotNull Collection<S> snapshotables,
|
||||||
|
@NotNull ToLongFunction<S> mappingFunction) {
|
||||||
|
Long2ObjectOpenHashMap<AtomicReference<T>> map = new Long2ObjectOpenHashMap<>(snapshotables.size());
|
||||||
|
for (S snapshotable : snapshotables) {
|
||||||
|
map.put(mappingFunction.applyAsLong(snapshotable), reference(snapshotable));
|
||||||
|
}
|
||||||
|
map.trim();
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
default <T extends Snapshot, S extends Snapshotable> @NotNull Map<Integer, AtomicReference<T>> referencesMapInt(@NotNull Collection<S> snapshotables,
|
||||||
|
@NotNull ToIntFunction<S> mappingFunction) {
|
||||||
|
Int2ObjectOpenHashMap<AtomicReference<T>> map = new Int2ObjectOpenHashMap<>(snapshotables.size());
|
||||||
|
for (S snapshotable : snapshotables) {
|
||||||
|
map.put(mappingFunction.applyAsInt(snapshotable), reference(snapshotable));
|
||||||
|
}
|
||||||
|
map.trim();
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
|
||||||
|
|
||||||
|
package net.minestom.server.snapshot;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
final class SnapshotUpdaterImpl implements SnapshotUpdater {
|
||||||
|
private final Map<Snapshotable, AtomicReference<Snapshot>> referenceMap = new ConcurrentHashMap<>();
|
||||||
|
private List<Entry> queue = new ArrayList<>();
|
||||||
|
|
||||||
|
static <T extends Snapshot> @NotNull T update(@NotNull Snapshotable snapshotable) {
|
||||||
|
var updater = new SnapshotUpdaterImpl();
|
||||||
|
var ref = updater.reference(snapshotable);
|
||||||
|
updater.update();
|
||||||
|
return (T) ref.getPlain();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends Snapshot> @NotNull AtomicReference<T> reference(@NotNull Snapshotable snapshotable) {
|
||||||
|
AtomicReference<Snapshot> ref = new AtomicReference<>();
|
||||||
|
var prev = referenceMap.putIfAbsent(snapshotable, ref);
|
||||||
|
if (prev == null) {
|
||||||
|
synchronized (this) {
|
||||||
|
queue.add(new Entry(snapshotable, ref));
|
||||||
|
}
|
||||||
|
return (AtomicReference<T>) ref;
|
||||||
|
}
|
||||||
|
return (AtomicReference<T>) prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
record Entry(Snapshotable snapshotable, AtomicReference<Snapshot> ref) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void update() {
|
||||||
|
List<Entry> temp;
|
||||||
|
while (!(temp = new ArrayList<>(queue)).isEmpty()) {
|
||||||
|
queue = new ArrayList<>();
|
||||||
|
temp.parallelStream().forEach(entry -> {
|
||||||
|
Snapshotable snap = entry.snapshotable;
|
||||||
|
entry.ref.setPlain(Objects.requireNonNull(snap.updateSnapshot(this), "Snapshot must not be null after an update!"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
src/main/java/net/minestom/server/snapshot/Snapshotable.java
Normal file
26
src/main/java/net/minestom/server/snapshot/Snapshotable.java
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package net.minestom.server.snapshot;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an object which is regularly saved into a snapshot.
|
||||||
|
*/
|
||||||
|
@ApiStatus.Experimental
|
||||||
|
public interface Snapshotable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the currently cached snapshot if required.
|
||||||
|
* The updater can be used to retrieve references to other snapshots while avoiding circular dependency.
|
||||||
|
* Be careful to do not store {@code updater} anywhere as its data will change when building requested references.
|
||||||
|
* <p>
|
||||||
|
* This method is not thread-safe, and targeted at internal use
|
||||||
|
* since its execution rely on safe-points (e.g. end of ticks)
|
||||||
|
*
|
||||||
|
* @param updater the snapshot updater/context
|
||||||
|
* @return the updated snapshot
|
||||||
|
*/
|
||||||
|
default @NotNull Snapshot updateSnapshot(@NotNull SnapshotUpdater updater) {
|
||||||
|
throw new UnsupportedOperationException("Snapshot is not supported for this object");
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,9 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
|
|||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.ToIntFunction;
|
||||||
|
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
public final class ArrayUtils {
|
public final class ArrayUtils {
|
||||||
@ -26,6 +28,19 @@ public final class ArrayUtils {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> int[] mapToIntArray(Collection<T> collection, ToIntFunction<T> function) {
|
||||||
|
final int size = collection.size();
|
||||||
|
if (size == 0)
|
||||||
|
return new int[0];
|
||||||
|
int[] result = new int[size];
|
||||||
|
int i = 0;
|
||||||
|
for (T object : collection) {
|
||||||
|
result[i++] = function.applyAsInt(object);
|
||||||
|
}
|
||||||
|
assert i == size;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public static <K, V> Map<K, V> toMap(@NotNull K[] keys, @NotNull V[] values, int length) {
|
public static <K, V> Map<K, V> toMap(@NotNull K[] keys, @NotNull V[] values, int length) {
|
||||||
assert keys.length >= length && keys.length == values.length;
|
assert keys.length >= length && keys.length == values.length;
|
||||||
return switch (length) {
|
return switch (length) {
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
package net.minestom.server.utils.collection;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
|
import java.util.AbstractList;
|
||||||
|
import java.util.function.IntFunction;
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
public final class IntMappedArray<R> extends AbstractList<R> {
|
||||||
|
private final int[] elements;
|
||||||
|
private final IntFunction<R> function;
|
||||||
|
|
||||||
|
public IntMappedArray(int[] elements, IntFunction<R> function) {
|
||||||
|
this.elements = elements;
|
||||||
|
this.function = function;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R get(int index) {
|
||||||
|
final int[] elements = this.elements;
|
||||||
|
if (index < 0 || index >= elements.length)
|
||||||
|
throw new IndexOutOfBoundsException("Index " + index + " is out of bounds for length " + elements.length);
|
||||||
|
return function.apply(elements[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return elements.length;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
package net.minestom.server.utils.collection;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
public record MappedCollection<O, R>(@NotNull Collection<O> original,
|
||||||
|
@NotNull Function<O, R> mapper) implements Collection<R> {
|
||||||
|
public static <O extends AtomicReference<R>, R> MappedCollection<O, R> plainReferences(@NotNull Collection<O> original) {
|
||||||
|
return new MappedCollection<>(original, AtomicReference::getPlain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return original.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return original.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(Object o) {
|
||||||
|
for (var entry : original) {
|
||||||
|
if (mapper.apply(entry).equals(o)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Iterator<R> iterator() {
|
||||||
|
var iterator = original.iterator();
|
||||||
|
return new Iterator<>() {
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return iterator.hasNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R next() {
|
||||||
|
return mapper.apply(iterator.next());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Object @NotNull [] toArray() {
|
||||||
|
// TODO
|
||||||
|
throw new UnsupportedOperationException("Unsupported array object");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> @NotNull T @NotNull [] toArray(@NotNull T @NotNull [] a) {
|
||||||
|
// TODO
|
||||||
|
throw new UnsupportedOperationException("Unsupported array generic");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsAll(@NotNull Collection<?> c) {
|
||||||
|
if (c.size() > original.size()) return false;
|
||||||
|
for (var entry : c) {
|
||||||
|
if (!contains(entry)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean add(R t) {
|
||||||
|
throw new UnsupportedOperationException("Unmodifiable collection");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean remove(Object o) {
|
||||||
|
throw new UnsupportedOperationException("Unmodifiable collection");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean addAll(@NotNull Collection<? extends R> c) {
|
||||||
|
throw new UnsupportedOperationException("Unmodifiable collection");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeAll(@NotNull Collection<?> c) {
|
||||||
|
throw new UnsupportedOperationException("Unmodifiable collection");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean retainAll(@NotNull Collection<?> c) {
|
||||||
|
throw new UnsupportedOperationException("Unmodifiable collection");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
throw new UnsupportedOperationException("Unmodifiable collection");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
package net.minestom.server.snapshot;
|
||||||
|
|
||||||
|
import net.minestom.server.api.Env;
|
||||||
|
import net.minestom.server.api.EnvTest;
|
||||||
|
import net.minestom.server.entity.Entity;
|
||||||
|
import net.minestom.server.entity.EntityType;
|
||||||
|
import net.minestom.server.instance.block.Block;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
|
||||||
|
@EnvTest
|
||||||
|
public class SnapshotIntegrationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void instance(Env env) {
|
||||||
|
env.createFlatInstance();
|
||||||
|
var snapshot = ServerSnapshot.update();
|
||||||
|
|
||||||
|
// Ensure that the collection is immutable
|
||||||
|
{
|
||||||
|
var instances = snapshot.instances();
|
||||||
|
assertEquals(1, instances.size());
|
||||||
|
|
||||||
|
env.createFlatInstance();
|
||||||
|
instances = snapshot.instances();
|
||||||
|
assertEquals(1, instances.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
var inst = snapshot.instances().iterator().next();
|
||||||
|
|
||||||
|
assertEquals(snapshot, inst.server(), "Instance must have access to the server snapshot");
|
||||||
|
|
||||||
|
assertEquals(0, inst.time());
|
||||||
|
assertEquals(0, inst.worldAge());
|
||||||
|
|
||||||
|
assertEquals(0, inst.chunks().size());
|
||||||
|
assertEquals(0, inst.entities().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void chunk(Env env) {
|
||||||
|
var instance = env.createFlatInstance();
|
||||||
|
instance.setBlock(0, 0, 0, Block.STONE);
|
||||||
|
var snapshot = ServerSnapshot.update();
|
||||||
|
|
||||||
|
var inst = snapshot.instances().iterator().next();
|
||||||
|
assertEquals(Block.STONE, inst.getBlock(0, 0, 0));
|
||||||
|
|
||||||
|
assertEquals(1, inst.chunks().size());
|
||||||
|
var chunk = inst.chunks().iterator().next();
|
||||||
|
assertEquals(Block.STONE, chunk.getBlock(0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void entity(Env env) {
|
||||||
|
var instance = env.createFlatInstance();
|
||||||
|
var ent = new Entity(EntityType.ZOMBIE);
|
||||||
|
ent.setInstance(instance).join();
|
||||||
|
var snapshot = ServerSnapshot.update();
|
||||||
|
|
||||||
|
var inst = snapshot.instances().iterator().next();
|
||||||
|
var entities = inst.entities();
|
||||||
|
assertEquals(1, entities.size());
|
||||||
|
|
||||||
|
var entity = entities.iterator().next();
|
||||||
|
assertEquals(EntityType.ZOMBIE, entity.type());
|
||||||
|
assertEquals(ent.getUuid(), entity.uuid());
|
||||||
|
assertEquals(ent.getEntityId(), entity.id());
|
||||||
|
assertEquals(ent.getPosition(), entity.position());
|
||||||
|
assertEquals(ent.getVelocity(), entity.velocity());
|
||||||
|
assertEquals(inst, entity.instance());
|
||||||
|
assertEquals(inst.chunkAt(entity.position()), entity.chunk());
|
||||||
|
assertEquals(ent.getViewers().size(), entity.viewers().size());
|
||||||
|
assertEquals(ent.getPassengers().size(), entity.passengers().size());
|
||||||
|
assertNull(ent.getVehicle());
|
||||||
|
assertNull(entity.vehicle());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user