visions = Collections.synchronizedMap(new WeakHashMap<>());
-
- public EntityManager(Server server) {
- this.server = server;
- this.loaded = server.getWorld().getData().getLoadedEntities();
- }
-
- public void tick() {
- synchronized (getServer().getWorld().getData()) {
- synchronized (visions) {
- gatherRequests();
- updateQueues();
- processQueues();
- }
- }
- }
-
- private void gatherRequests() {
- server.getPlayerManager().getPlayers().forEach(p -> {
- PlayerVision vision = getVision(p, true);
- vision.gatherRequests(p);
- });
- }
-
- private void updateQueues() {
- visions.forEach((p, v) -> {
- v.updateQueues(p);
- });
- }
-
- private void processQueues() {
- visions.forEach((p, v) -> {
- v.processQueues(p);
- });
- }
-
- private PlayerVision getVision(Player player, boolean createIfMissing) {
- return createIfMissing ? visions.computeIfAbsent(player, k -> new PlayerVision()) : visions.get(player);
- }
-
- public void sendEntity(Player player, long entityId) {
-
- EntityData entity = server.getWorld().getData().getEntity(entityId);
-
- if (entity == null) {
- throw new IllegalStateException(
- "Entity with entity ID " + new String(StringUtil.toFullHex(entityId)) + " is not loaded, cannot send"
- );
- }
-
- PacketSendEntity packet = new PacketSendEntity();
- packet.set(entity);
- player.getClient().sendPacket(packet);
-
- getVision(player, true).visible.add(entityId);
- }
-
- public void revokeEntity(Player player, long entityId) {
- PacketRevokeEntity packet = new PacketRevokeEntity();
- packet.set(entityId);
- player.getClient().sendPacket(packet);
-
- PlayerVision vision = getVision(player, false);
- if (vision != null) {
- vision.visible.remove(entityId);
- }
- }
-
- public boolean isEntityVisible(long entityId, Player player) {
- PlayerVision vision = getVision(player, false);
-
- if (vision == null) {
- return false;
- }
-
- return vision.isEntityVisible(entityId);
- }
-
- private static final TLongSet EMPTY_LONG_SET = TCollections.unmodifiableSet(new TLongHashSet());
-
- public TLongSet getVisibleEntities(Player player) {
- PlayerVision vision = getVision(player, false);
-
- if (vision == null) {
- return EMPTY_LONG_SET;
- }
-
- return vision.visible;
- }
-
- public Server getServer() {
- return server;
- }
-
-}
diff --git a/src/main/java/ru/windcorp/progressia/server/PlayerManager.java b/src/main/java/ru/windcorp/progressia/server/PlayerManager.java
index 60b07f1..10799e0 100644
--- a/src/main/java/ru/windcorp/progressia/server/PlayerManager.java
+++ b/src/main/java/ru/windcorp/progressia/server/PlayerManager.java
@@ -26,6 +26,7 @@ import glm.vec._3.Vec3;
import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.entity.EntityDataRegistry;
+import ru.windcorp.progressia.server.events.PlayerJoinedEvent;
import ru.windcorp.progressia.test.TestContent;
public class PlayerManager {
@@ -44,6 +45,8 @@ public class PlayerManager {
public void addPlayer(Player player) {
this.players.add(player);
+ System.out.println("PlayerManager.addPlayer()");
+ getServer().postEvent(new PlayerJoinedEvent.Immutable(getServer(), player));
}
public EntityData conjurePlayerEntity(String login) {
@@ -69,6 +72,10 @@ public class PlayerManager {
return player;
}
+
+ public Object getMutex() {
+ return players;
+ }
public Server getServer() {
return server;
diff --git a/src/main/java/ru/windcorp/progressia/server/Server.java b/src/main/java/ru/windcorp/progressia/server/Server.java
index 8ca9383..d4d1883 100644
--- a/src/main/java/ru/windcorp/progressia/server/Server.java
+++ b/src/main/java/ru/windcorp/progressia/server/Server.java
@@ -15,18 +15,25 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package ru.windcorp.progressia.server;
import java.util.function.Consumer;
import org.apache.logging.log4j.LogManager;
+import com.google.common.eventbus.EventBus;
+
import ru.windcorp.jputil.functions.ThrowingRunnable;
import ru.windcorp.progressia.common.Units;
import ru.windcorp.progressia.common.util.TaskQueue;
+import ru.windcorp.progressia.common.util.crash.ReportingEventBus;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.server.comms.ClientManager;
+import ru.windcorp.progressia.server.events.ServerEvent;
+import ru.windcorp.progressia.server.management.load.ChunkRequestDaemon;
+import ru.windcorp.progressia.server.management.load.EntityRequestDaemon;
+import ru.windcorp.progressia.server.management.load.LoadManager;
import ru.windcorp.progressia.server.world.WorldLogic;
import ru.windcorp.progressia.server.world.tasks.WorldAccessor;
import ru.windcorp.progressia.server.world.ticking.Change;
@@ -53,25 +60,32 @@ public class Server {
private final ClientManager clientManager;
private final PlayerManager playerManager;
- private final ChunkManager chunkManager;
- private final EntityManager entityManager;
+ private final LoadManager loadManager;
private final TaskQueue taskQueue = new TaskQueue(this::isServerThread);
+ private final EventBus eventBus = ReportingEventBus.create("ServerEvents");
+
private final TickingSettings tickingSettings = new TickingSettings();
public Server(WorldData world) {
- this.world = new WorldLogic(world, this, w -> new TestPlanetGenerator("Test:PlanetGenerator", new Planet(4, 9.8f, 16f, 16f), w));
+ this.world = new WorldLogic(
+ world,
+ this,
+ w -> new TestPlanetGenerator("Test:PlanetGenerator", new Planet(4, 9.8f, 16f, 16f), w)
+ );
this.serverThread = new ServerThread(this);
this.clientManager = new ClientManager(this);
this.playerManager = new PlayerManager(this);
- this.chunkManager = new ChunkManager(this);
- this.entityManager = new EntityManager(this);
+ this.loadManager = new LoadManager(this);
- schedule(chunkManager::tick);
- schedule(entityManager::tick);
- schedule(this::scheduleWorldTicks); // Must run after chunkManager so it only schedules chunks that hadn't unloaded
+ schedule(new ChunkRequestDaemon(loadManager.getChunkManager())::tick);
+ schedule(new EntityRequestDaemon(loadManager.getEntityManager())::tick);
+
+ // Must run after request daemons so it only schedules chunks that
+ // hadn't unloaded
+ schedule(this::scheduleWorldTicks);
}
/**
@@ -84,8 +98,8 @@ public class Server {
}
/**
- * Returns this server's {@link ClientManager}.
- * Use this to deal with communications, e.g. send packets.
+ * Returns this server's {@link ClientManager}. Use this to deal with
+ * communications, e.g. send packets.
*
* @return the {@link ClientManager} that handles this server
*/
@@ -97,8 +111,8 @@ public class Server {
return playerManager;
}
- public ChunkManager getChunkManager() {
- return chunkManager;
+ public LoadManager getLoadManager() {
+ return loadManager;
}
/**
@@ -111,9 +125,9 @@ public class Server {
}
/**
- * Requests that the provided task is executed once on next server tick.
- * The task will be run in the main server thread. The task object is
- * discarded after execution.
+ * Requests that the provided task is executed once on next server tick. The
+ * task will be run in the main server thread. The task object is discarded
+ * after execution.
*
* Use this method to request a one-time (rare) action that must necessarily
* happen in the main server thread, such as initialization tasks or
@@ -130,13 +144,12 @@ public class Server {
/**
* Executes the tasks in the server main thread as soon as possible.
*
- * If this method is invoked in the server main thread, then the task is
- * run immediately (the method blocks until the task finishes). Otherwise
- * this method behaves exactly like {@link #invokeLater(Runnable)}.
+ * If this method is invoked in the server main thread, then the task is run
+ * immediately (the method blocks until the task finishes). Otherwise this
+ * method behaves exactly like {@link #invokeLater(Runnable)}.
*
* Use this method to make sure that a piece of code is run in the main
- * server
- * thread.
+ * server thread.
*
* @param task the task to run
* @see #invokeLater(Runnable)
@@ -146,11 +159,7 @@ public class Server {
taskQueue.invokeNow(task);
}
- public void waitAndInvoke(
- ThrowingRunnable task
- )
- throws InterruptedException,
- E {
+ public void waitAndInvoke(ThrowingRunnable task) throws InterruptedException, E {
taskQueue.waitAndInvoke(task);
}
@@ -170,6 +179,20 @@ public class Server {
serverThread.getTicker().requestEvaluation(evaluation);
}
+ public void subscribe(Object object) {
+ eventBus.register(object);
+ }
+
+ public void unsubscribe(Object object) {
+ eventBus.unregister(object);
+ }
+
+ public void postEvent(ServerEvent event) {
+ event.setServer(this);
+ eventBus.post(event);
+ event.setServer(null);
+ }
+
/**
* Returns the duration of the last server tick. Server logic should assume
* that this much in-world time has passed.
@@ -186,8 +209,8 @@ public class Server {
/**
* Returns the {@link WorldAccessor} object for this server. Use the
- * provided accessor to
- * request common {@link Evaluation}s and {@link Change}s.
+ * provided accessor to request common {@link Evaluation}s and
+ * {@link Change}s.
*
* @return a {@link WorldAccessor}
* @see #requestChange(Change)
@@ -227,8 +250,7 @@ public class Server {
/**
* Shuts the server down, disconnecting the clients with the provided
- * message.
- * This method blocks until the shutdown is complete.
+ * message. This method blocks until the shutdown is complete.
*
* @param message the message to send to the clients as the disconnect
* reason
@@ -245,10 +267,9 @@ public class Server {
/**
* Returns an instance of {@link java.util.Random Random} that can be used
- * as a source of indeterministic
- * randomness. World generation and other algorithms that must have random
- * but reproducible results should
- * not use this.
+ * as a source of indeterministic randomness. World generation and other
+ * algorithms that must have random but reproducible results should not use
+ * this.
*
* @return a thread-safe indeterministic instance of
* {@link java.util.Random}.
diff --git a/src/main/java/ru/windcorp/progressia/server/comms/ClientManager.java b/src/main/java/ru/windcorp/progressia/server/comms/ClientManager.java
index 74c636d..dfa631a 100644
--- a/src/main/java/ru/windcorp/progressia/server/comms/ClientManager.java
+++ b/src/main/java/ru/windcorp/progressia/server/comms/ClientManager.java
@@ -81,7 +81,7 @@ public class ClientManager {
EntityData entity = getServer().getPlayerManager().conjurePlayerEntity(login);
Player player = new Player(entity, getServer(), client);
- getServer().getPlayerManager().getPlayers().add(player);
+ getServer().getPlayerManager().addPlayer(player);
PacketSetLocalPlayer packet = new PacketSetLocalPlayer();
packet.set(entity.getEntityId());
diff --git a/src/main/java/ru/windcorp/progressia/server/comms/ClientPlayer.java b/src/main/java/ru/windcorp/progressia/server/comms/ClientPlayer.java
index c455b8e..c05d952 100644
--- a/src/main/java/ru/windcorp/progressia/server/comms/ClientPlayer.java
+++ b/src/main/java/ru/windcorp/progressia/server/comms/ClientPlayer.java
@@ -44,13 +44,13 @@ public abstract class ClientPlayer extends ClientChat {
public boolean isChunkVisible(Vec3i chunkPos) {
if (player == null)
return false;
- return player.getServer().getChunkManager().isChunkVisible(chunkPos, player);
+ return player.getServer().getLoadManager().getVisionManager().isChunkVisible(chunkPos, player);
}
public ChunkSet getVisibleChunks() {
if (player == null)
return ChunkSets.empty();
- return player.getServer().getChunkManager().getVisibleChunks(player);
+ return player.getServer().getLoadManager().getVisionManager().getVisibleChunks(player);
}
public boolean isChunkVisible(long entityId) {
diff --git a/src/main/java/ru/windcorp/progressia/server/events/ClientEvent.java b/src/main/java/ru/windcorp/progressia/server/events/ClientEvent.java
new file mode 100644
index 0000000..c795fa8
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/server/events/ClientEvent.java
@@ -0,0 +1,46 @@
+/*
+ * Progressia
+ * Copyright (C) 2020-2021 Wind Corporation and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package ru.windcorp.progressia.server.events;
+
+import ru.windcorp.progressia.server.Server;
+import ru.windcorp.progressia.server.comms.Client;
+
+public interface ClientEvent extends ServerEvent {
+
+ Client getClient();
+
+ public static abstract class Immutable extends ServerEvent.Default implements ClientEvent {
+
+ private final Client client;
+
+ public Immutable(Server server, Client client) {
+ super(server);
+ this.client = client;
+ }
+
+ /**
+ * @return the client
+ */
+ @Override
+ public Client getClient() {
+ return client;
+ }
+
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/server/events/PlayerEvent.java b/src/main/java/ru/windcorp/progressia/server/events/PlayerEvent.java
new file mode 100644
index 0000000..544418e
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/server/events/PlayerEvent.java
@@ -0,0 +1,49 @@
+/*
+ * Progressia
+ * Copyright (C) 2020-2021 Wind Corporation and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package ru.windcorp.progressia.server.events;
+
+import ru.windcorp.progressia.server.Player;
+import ru.windcorp.progressia.server.Server;
+import ru.windcorp.progressia.server.comms.Client;
+
+public interface PlayerEvent extends ClientEvent {
+
+ Player getPlayer();
+
+ public static abstract class Immutable extends ServerEvent.Default implements PlayerEvent {
+
+ private final Player player;
+
+ public Immutable(Server server, Player player) {
+ super(server);
+ this.player = player;
+ }
+
+ @Override
+ public Player getPlayer() {
+ return player;
+ }
+
+ @Override
+ public Client getClient() {
+ return player.getClient();
+ }
+
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/server/events/PlayerJoinedEvent.java b/src/main/java/ru/windcorp/progressia/server/events/PlayerJoinedEvent.java
new file mode 100644
index 0000000..eed2a90
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/server/events/PlayerJoinedEvent.java
@@ -0,0 +1,33 @@
+/*
+ * Progressia
+ * Copyright (C) 2020-2021 Wind Corporation and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package ru.windcorp.progressia.server.events;
+
+import ru.windcorp.progressia.server.Player;
+import ru.windcorp.progressia.server.Server;
+
+public interface PlayerJoinedEvent extends PlayerEvent {
+
+ public static class Immutable extends PlayerEvent.Immutable implements PlayerJoinedEvent {
+
+ public Immutable(Server server, Player player) {
+ super(server, player);
+ }
+
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/server/events/PlayerLeftEvent.java b/src/main/java/ru/windcorp/progressia/server/events/PlayerLeftEvent.java
new file mode 100644
index 0000000..d6e4417
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/server/events/PlayerLeftEvent.java
@@ -0,0 +1,33 @@
+/*
+ * Progressia
+ * Copyright (C) 2020-2021 Wind Corporation and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package ru.windcorp.progressia.server.events;
+
+import ru.windcorp.progressia.server.Player;
+import ru.windcorp.progressia.server.Server;
+
+public interface PlayerLeftEvent extends PlayerEvent {
+
+ public static class Immutable extends PlayerEvent.Immutable implements PlayerLeftEvent {
+
+ public Immutable(Server server, Player player) {
+ super(server, player);
+ }
+
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/server/events/ServerEvent.java b/src/main/java/ru/windcorp/progressia/server/events/ServerEvent.java
new file mode 100644
index 0000000..b63f727
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/server/events/ServerEvent.java
@@ -0,0 +1,68 @@
+/*
+ * Progressia
+ * Copyright (C) 2020-2021 Wind Corporation and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package ru.windcorp.progressia.server.events;
+
+import ru.windcorp.progressia.server.Server;
+
+/**
+ * An interface for all events issued by a {@link Server}.
+ */
+public interface ServerEvent {
+
+ /**
+ * Returns the server instance that this event happened on.
+ *
+ * @return the relevant server
+ */
+ Server getServer();
+
+ /**
+ * Sets the server instance that the event is posted on. The value provided
+ * to this method must be returned by subsequent calls to
+ * {@link #getServer()}. Do not call this method when handling the event.
+ *
+ * @param server the server dispatching the event or {@code null} to unbind
+ * any previously bound server
+ */
+ void setServer(Server server);
+
+ /**
+ * A default implementation of {@link ServerEvent}. This is not necessarily
+ * extended by server events.
+ */
+ public static abstract class Default implements ServerEvent {
+
+ private Server server;
+
+ public Default(Server server) {
+ this.server = server;
+ }
+
+ @Override
+ public Server getServer() {
+ return server;
+ }
+
+ @Override
+ public void setServer(Server server) {
+ this.server = server;
+ }
+
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/server/management/load/ChunkManager.java b/src/main/java/ru/windcorp/progressia/server/management/load/ChunkManager.java
new file mode 100644
index 0000000..a31a9cd
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/server/management/load/ChunkManager.java
@@ -0,0 +1,192 @@
+/*
+ * Progressia
+ * Copyright (C) 2020-2021 Wind Corporation and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package ru.windcorp.progressia.server.management.load;
+
+import glm.vec._3.i.Vec3i;
+import ru.windcorp.progressia.common.world.ChunkData;
+import ru.windcorp.progressia.common.world.PacketRevokeChunk;
+import ru.windcorp.progressia.common.world.PacketSendChunk;
+import ru.windcorp.progressia.common.world.WorldData;
+import ru.windcorp.progressia.server.Player;
+import ru.windcorp.progressia.server.Server;
+import ru.windcorp.progressia.test.TestWorldDiskIO;
+
+/**
+ * Chunk manager provides facilities to load, unload and generate chunks for a
+ * {@link Server} on demand.
+ */
+public class ChunkManager {
+
+ private final LoadManager loadManager;
+
+ public ChunkManager(LoadManager loadManager) {
+ this.loadManager = loadManager;
+ }
+
+ /**
+ * @return the loadManager
+ */
+ public LoadManager getLoadManager() {
+ return loadManager;
+ }
+
+ /**
+ * @return the server
+ */
+ public Server getServer() {
+ return getLoadManager().getServer();
+ }
+
+ /**
+ * Describes the result of an attempt to load a chunk.
+ */
+ public static enum LoadResult {
+ /**
+ * A chunk has successfully been read from disk and is now loaded.
+ */
+ LOADED_FROM_DISK,
+
+ /**
+ * A chunk has successfully been generated and is now loaded.
+ */
+ GENERATED,
+
+ /**
+ * A chunk has already been loaded and so no action has been taken.
+ */
+ ALREADY_LOADED,
+
+ /**
+ * A chunk has not been loaded previously and the operation has failed
+ * to load it. It is not currently loaded.
+ */
+ NOT_LOADED
+ }
+
+ /**
+ * Loads or generates the chunk at the given location unless it is already
+ * loaded. The chunk is loaded after this method completes normally.
+ *
+ * @param chunkPos the position of the chunk
+ * @return one of {@link LoadResult#LOADED_FROM_DISK LOADED_FROM_DISK},
+ * {@link LoadResult#GENERATED GENERATED} or
+ * {@link LoadResult#ALREADY_LOADED ALREADY_LOADED}
+ */
+ public LoadResult loadOrGenerateChunk(Vec3i chunkPos) {
+ LoadResult loadResult = loadChunk(chunkPos);
+
+ if (loadResult == LoadResult.NOT_LOADED) {
+ getServer().getWorld().generate(chunkPos);
+ return LoadResult.GENERATED;
+ } else {
+ return loadResult;
+ }
+ }
+
+ /**
+ * Attempts to load the chunk from disk unless it is already loaded. If the
+ * chunk is not currently loaded and it is not available on the disk this
+ * method does nothing.
+ *
+ * @param chunkPos the position of the chunk
+ * @return one of {@link LoadResult#LOADED_FROM_DISK LOADED_FROM_DISK},
+ * {@link LoadResult#NOT_LOADED NOT_LOADED} or
+ * {@link LoadResult#ALREADY_LOADED ALREADY_LOADED}
+ */
+ public LoadResult loadChunk(Vec3i chunkPos) {
+ if (isChunkLoaded(chunkPos)) {
+ return LoadResult.ALREADY_LOADED;
+ }
+
+ WorldData world = getServer().getWorld().getData();
+
+ ChunkData chunk = TestWorldDiskIO.tryToLoad(chunkPos, world, getServer());
+ if (chunk != null) {
+ world.addChunk(chunk);
+ return LoadResult.LOADED_FROM_DISK;
+ } else {
+ return LoadResult.NOT_LOADED;
+ }
+ }
+
+ /**
+ * Unloads the chunk and saves it to disk if the chunk is loaded, otherwise
+ * does nothing.
+ *
+ * @param chunkPos the position of the chunk
+ * @return {@code true} iff the chunk had been loaded and was unloaded by
+ * this method
+ */
+ public boolean unloadChunk(Vec3i chunkPos) {
+ WorldData world = getServer().getWorld().getData();
+ ChunkData chunk = world.getChunk(chunkPos);
+
+ if (chunk == null) {
+ return false;
+ }
+
+ world.removeChunk(chunk);
+ TestWorldDiskIO.saveChunk(chunk, getServer());
+
+ return true;
+ }
+
+ public void sendChunk(Player player, Vec3i chunkPos) {
+ ChunkData chunk = getServer().getWorld().getData().getChunk(chunkPos);
+
+ if (chunk == null) {
+ throw new IllegalStateException(
+ String.format(
+ "Chunk (%d; %d; %d) is not loaded, cannot send",
+ chunkPos.x,
+ chunkPos.y,
+ chunkPos.z
+ )
+ );
+ }
+
+ PacketSendChunk packet = new PacketSendChunk();
+ packet.set(chunk);
+ player.getClient().sendPacket(packet);
+
+ getLoadManager().getVisionManager().getVision(player, true).getVisibleChunks().add(chunkPos);
+ }
+
+ public void revokeChunk(Player player, Vec3i chunkPos) {
+ PacketRevokeChunk packet = new PacketRevokeChunk();
+ packet.set(chunkPos);
+ player.getClient().sendPacket(packet);
+
+ PlayerVision vision = getLoadManager().getVisionManager().getVision(player, false);
+ if (vision != null) {
+ vision.getVisibleChunks().remove(chunkPos);
+ }
+ }
+
+ /**
+ * Checks whether or not the chunk at the specified location is loaded. A
+ * loaded chunk is accessible through the server's {@link WorldData} object.
+ *
+ * @param chunkPos the position of the chunk
+ * @return {@code true} iff the chunk is loaded
+ */
+ public boolean isChunkLoaded(Vec3i chunkPos) {
+ return getServer().getWorld().isChunkLoaded(chunkPos);
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/server/management/load/ChunkRequestDaemon.java b/src/main/java/ru/windcorp/progressia/server/management/load/ChunkRequestDaemon.java
new file mode 100644
index 0000000..ff3c7e0
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/server/management/load/ChunkRequestDaemon.java
@@ -0,0 +1,206 @@
+/*
+ * Progressia
+ * Copyright (C) 2020-2021 Wind Corporation and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package ru.windcorp.progressia.server.management.load;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+
+import glm.vec._3.i.Vec3i;
+import ru.windcorp.progressia.common.Units;
+import ru.windcorp.progressia.common.world.generic.ChunkMap;
+import ru.windcorp.progressia.common.world.generic.ChunkMaps;
+import ru.windcorp.progressia.common.world.generic.ChunkSet;
+import ru.windcorp.progressia.common.world.generic.ChunkSets;
+import ru.windcorp.progressia.server.Server;
+
+/**
+ * Chunk request daemon gathers chunk requests from players (via {@link VisionManager}) and loads or unloads chunks appropriately.
+ */
+public class ChunkRequestDaemon {
+
+ private static final float CHUNK_UNLOAD_DELAY = Units.get(5, "s");
+
+ private final ChunkManager chunkManager;
+
+ private final ChunkSet loaded;
+ private final ChunkSet requested = ChunkSets.newHashSet();
+ private final ChunkSet toLoad = ChunkSets.newHashSet();
+ private final ChunkSet toRequestUnload = ChunkSets.newHashSet();
+
+ private final Collection buffer = new ArrayList<>();
+
+ private static class ChunkUnloadRequest {
+ private final Vec3i chunkPos;
+ private final long unloadAt;
+
+ public ChunkUnloadRequest(Vec3i chunkPos, long unloadAt) {
+ this.chunkPos = chunkPos;
+ this.unloadAt = unloadAt;
+ }
+
+ /**
+ * @return the chunk position
+ */
+ public Vec3i getChunkPos() {
+ return chunkPos;
+ }
+
+ /**
+ * @return the moment when the chunks becomes eligible for unloading
+ */
+ public long getUnloadAt() {
+ return unloadAt;
+ }
+ }
+
+ private final ChunkMap unloadSchedule = ChunkMaps.newHashMap();
+
+ public ChunkRequestDaemon(ChunkManager chunkManager) {
+ this.chunkManager = chunkManager;
+ this.loaded = getServer().getWorld().getData().getLoadedChunks();
+ }
+
+ public void tick() {
+ synchronized (getServer().getWorld().getData()) {
+ synchronized (getServer().getPlayerManager().getMutex()) {
+ loadAndUnloadChunks();
+ sendAndRevokeChunks();
+ }
+ }
+ }
+
+ private void loadAndUnloadChunks() {
+ gatherLoadRequests();
+ updateLoadQueues();
+ processLoadQueues();
+ }
+
+ private void gatherLoadRequests() {
+ requested.clear();
+
+ getChunkManager().getLoadManager().getVisionManager().forEachVision(vision -> {
+ vision.getRequestedChunks().clear();
+ vision.getPlayer().requestChunksToLoad(vision.getRequestedChunks()::add);
+ requested.addAll(vision.getRequestedChunks());
+ });
+ }
+
+ private void updateLoadQueues() {
+ toLoad.clear();
+ toLoad.addAll(requested);
+ toLoad.removeAll(loaded);
+
+ toRequestUnload.clear();
+ toRequestUnload.addAll(loaded);
+ toRequestUnload.removeAll(requested);
+ }
+
+ private void processLoadQueues() {
+ toRequestUnload.forEach(this::scheduleUnload);
+ toRequestUnload.clear();
+
+ toLoad.forEach(getChunkManager()::loadOrGenerateChunk);
+ toLoad.clear();
+
+ unloadScheduledChunks();
+ }
+
+ private void scheduleUnload(Vec3i chunkPos) {
+ if (unloadSchedule.containsKey(chunkPos)) {
+ // Unload already requested, skip
+ return;
+ }
+
+ long unloadAt = System.currentTimeMillis() + (long) (getUnloadDelay() * 1000);
+ Vec3i chunkPosCopy = new Vec3i(chunkPos);
+
+ unloadSchedule.put(chunkPosCopy, new ChunkUnloadRequest(chunkPosCopy, unloadAt));
+ }
+
+ private void unloadScheduledChunks() {
+ long now = System.currentTimeMillis();
+
+ for (Iterator it = unloadSchedule.values().iterator(); it.hasNext();) {
+ ChunkUnloadRequest request = it.next();
+
+ if (request.getUnloadAt() < now) {
+ it.remove();
+ getChunkManager().unloadChunk(request.getChunkPos());
+ }
+ }
+ }
+
+ private void sendAndRevokeChunks() {
+ getChunkManager().getLoadManager().getVisionManager().forEachVision(vision -> {
+ revokeChunks(vision);
+ sendChunks(vision);
+ });
+ }
+
+ private void sendChunks(PlayerVision vision) {
+ vision.getRequestedChunks().forEachIn(getServer().getWorld(), chunk -> {
+ if (!chunk.isReady())
+ return;
+ if (vision.isChunkVisible(chunk.getPosition()))
+ return;
+ buffer.add(chunk.getPosition());
+ });
+
+ if (buffer.isEmpty()) return;
+ for (Vec3i chunkPos : buffer) {
+ getChunkManager().sendChunk(vision.getPlayer(), chunkPos);
+ }
+
+ buffer.clear();
+ }
+
+ private void revokeChunks(PlayerVision vision) {
+ vision.getVisibleChunks().forEach(chunkPos -> {
+ if (getChunkManager().isChunkLoaded(chunkPos) && vision.getRequestedChunks().contains(chunkPos))
+ return;
+ buffer.add(new Vec3i(chunkPos));
+ });
+
+ if (buffer.isEmpty()) return;
+ for (Vec3i chunkPos : buffer) {
+ getChunkManager().revokeChunk(vision.getPlayer(), chunkPos);
+ }
+
+ buffer.clear();
+ }
+
+ /**
+ * @return the minimum amount of time a chunk will spend in the unload queue
+ */
+ public float getUnloadDelay() {
+ return CHUNK_UNLOAD_DELAY;
+ }
+
+ /**
+ * @return the manager
+ */
+ public ChunkManager getChunkManager() {
+ return chunkManager;
+ }
+
+ public Server getServer() {
+ return getChunkManager().getServer();
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/server/management/load/EntityManager.java b/src/main/java/ru/windcorp/progressia/server/management/load/EntityManager.java
new file mode 100644
index 0000000..6aa3853
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/server/management/load/EntityManager.java
@@ -0,0 +1,97 @@
+/*
+ * Progressia
+ * Copyright (C) 2020-2021 Wind Corporation and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package ru.windcorp.progressia.server.management.load;
+
+import gnu.trove.set.TLongSet;
+import ru.windcorp.progressia.common.world.entity.EntityData;
+import ru.windcorp.progressia.common.world.entity.PacketRevokeEntity;
+import ru.windcorp.progressia.common.world.entity.PacketSendEntity;
+import ru.windcorp.progressia.server.Player;
+import ru.windcorp.progressia.server.Server;
+
+public class EntityManager {
+
+ private final LoadManager loadManager;
+
+ private final TLongSet loaded;
+
+ public EntityManager(LoadManager loadManager) {
+ this.loadManager = loadManager;
+ this.loaded = getServer().getWorld().getData().getLoadedEntities();
+ }
+
+ public void sendEntity(Player player, long entityId) {
+ PlayerVision vision = getLoadManager().getVisionManager().getVision(player, true);
+ if (!vision.getVisibleEntities().add(entityId)) {
+ return;
+ }
+
+ EntityData entity = getServer().getWorld().getData().getEntity(entityId);
+
+ if (entity == null) {
+ throw new IllegalStateException(
+ "Entity with entity ID " + EntityData.formatEntityId(entityId) + " is not loaded, cannot send"
+ );
+ }
+
+ PacketSendEntity packet = new PacketSendEntity();
+ packet.set(entity);
+ player.getClient().sendPacket(packet);
+ }
+
+ public void revokeEntity(Player player, long entityId) {
+ PlayerVision vision = getLoadManager().getVisionManager().getVision(player, false);
+ if (vision == null) {
+ return;
+ }
+ if (!vision.getVisibleEntities().remove(entityId)) {
+ return;
+ }
+
+ PacketRevokeEntity packet = new PacketRevokeEntity();
+ packet.set(entityId);
+ player.getClient().sendPacket(packet);
+ }
+
+ public boolean isEntityVisible(Player player, long entityId) {
+ PlayerVision vision = getLoadManager().getVisionManager().getVision(player, false);
+
+ if (vision == null) {
+ return false;
+ }
+
+ return vision.isEntityVisible(entityId);
+ }
+
+ public boolean isEntityLoaded(long entityId) {
+ return loaded.contains(entityId);
+ }
+
+ /**
+ * @return the loadManager
+ */
+ public LoadManager getLoadManager() {
+ return loadManager;
+ }
+
+ public Server getServer() {
+ return getLoadManager().getServer();
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/server/management/load/EntityRequestDaemon.java b/src/main/java/ru/windcorp/progressia/server/management/load/EntityRequestDaemon.java
new file mode 100644
index 0000000..9605ffe
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/server/management/load/EntityRequestDaemon.java
@@ -0,0 +1,121 @@
+/*
+ * Progressia
+ * Copyright (C) 2020-2021 Wind Corporation and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package ru.windcorp.progressia.server.management.load;
+
+import java.util.function.Consumer;
+
+import glm.vec._3.i.Vec3i;
+import gnu.trove.TLongCollection;
+import gnu.trove.iterator.TLongIterator;
+import gnu.trove.list.array.TLongArrayList;
+import gnu.trove.set.TLongSet;
+import ru.windcorp.progressia.common.util.Vectors;
+import ru.windcorp.progressia.common.world.generic.ChunkSet;
+import ru.windcorp.progressia.server.Server;
+
+public class EntityRequestDaemon {
+
+ private final EntityManager entityManager;
+
+ private final TLongCollection buffer = new TLongArrayList();
+
+ public EntityRequestDaemon(EntityManager entityManager) {
+ this.entityManager = entityManager;
+ }
+
+ public void tick() {
+ synchronized (getServer().getWorld().getData()) {
+ synchronized (getServer().getPlayerManager().getMutex()) {
+ gatherRequests();
+ revokeEntities();
+ sendEntities();
+ }
+ }
+ }
+
+ private void gatherRequests() {
+ Vec3i v = Vectors.grab3i();
+
+ forEachVision(vision -> {
+
+ TLongSet requestedEntities = vision.getRequestedEntities();
+ requestedEntities.clear();
+
+ ChunkSet visibleChunks = vision.getVisibleChunks();
+ getServer().getWorld().forEachEntity(entity -> {
+ if (visibleChunks.contains(entity.getChunkCoords(v))) {
+ requestedEntities.add(entity.getEntityId());
+ }
+ });
+ });
+
+ Vectors.release(v);
+ }
+
+ private void sendEntities() {
+ forEachVision(vision -> {
+ for (TLongIterator it = vision.getRequestedEntities().iterator(); it.hasNext();) {
+ long entityId = it.next();
+ if (getEntityManager().isEntityLoaded(entityId) && !vision.getVisibleEntities().contains(entityId)) {
+ buffer.add(entityId);
+ }
+ }
+
+ if (buffer.isEmpty()) return;
+ for (TLongIterator it = buffer.iterator(); it.hasNext();) {
+ getEntityManager().sendEntity(vision.getPlayer(), it.next());
+ }
+
+ buffer.clear();
+ });
+ }
+
+ private void revokeEntities() {
+ forEachVision(vision -> {
+ for (TLongIterator it = vision.getVisibleEntities().iterator(); it.hasNext();) {
+ long entityId = it.next();
+ if (!getEntityManager().isEntityLoaded(entityId) || !vision.getRequestedEntities().contains(entityId)) {
+ buffer.add(entityId);
+ }
+ }
+
+ if (buffer.isEmpty()) return;
+ for (TLongIterator it = buffer.iterator(); it.hasNext();) {
+ getEntityManager().revokeEntity(vision.getPlayer(), it.next());
+ }
+
+ buffer.clear();
+ });
+ }
+
+ /**
+ * @return the entityManager
+ */
+ public EntityManager getEntityManager() {
+ return entityManager;
+ }
+
+ public Server getServer() {
+ return getEntityManager().getServer();
+ }
+
+ private void forEachVision(Consumer super PlayerVision> action) {
+ getEntityManager().getLoadManager().getVisionManager().forEachVision(action);
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/server/management/load/LoadManager.java b/src/main/java/ru/windcorp/progressia/server/management/load/LoadManager.java
new file mode 100644
index 0000000..3670116
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/server/management/load/LoadManager.java
@@ -0,0 +1,65 @@
+/*
+ * Progressia
+ * Copyright (C) 2020-2021 Wind Corporation and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package ru.windcorp.progressia.server.management.load;
+
+import ru.windcorp.progressia.server.Server;
+
+public class LoadManager {
+
+ private final Server server;
+ private final ChunkManager chunkManager;
+ private final EntityManager entityManager;
+ private final VisionManager visionManager;
+
+ public LoadManager(Server server) {
+ this.server = server;
+
+ this.chunkManager = new ChunkManager(this);
+ this.entityManager = new EntityManager(this);
+ this.visionManager = new VisionManager(this);
+ }
+
+ /**
+ * @return the chunkManager
+ */
+ public ChunkManager getChunkManager() {
+ return chunkManager;
+ }
+
+ /**
+ * @return the entityManager
+ */
+ public EntityManager getEntityManager() {
+ return entityManager;
+ }
+
+ /**
+ * @return the visionManager
+ */
+ public VisionManager getVisionManager() {
+ return visionManager;
+ }
+
+ /**
+ * @return the server
+ */
+ public Server getServer() {
+ return server;
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/server/management/load/PlayerVision.java b/src/main/java/ru/windcorp/progressia/server/management/load/PlayerVision.java
new file mode 100644
index 0000000..bb8e474
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/server/management/load/PlayerVision.java
@@ -0,0 +1,85 @@
+/*
+ * Progressia
+ * Copyright (C) 2020-2021 Wind Corporation and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package ru.windcorp.progressia.server.management.load;
+
+import glm.vec._3.i.Vec3i;
+import gnu.trove.TCollections;
+import gnu.trove.set.TLongSet;
+import gnu.trove.set.hash.TLongHashSet;
+import ru.windcorp.progressia.common.world.generic.ChunkSet;
+import ru.windcorp.progressia.common.world.generic.ChunkSets;
+import ru.windcorp.progressia.server.Player;
+
+public class PlayerVision {
+
+ private final Player player;
+
+ private final ChunkSet visibleChunks = ChunkSets.newSyncHashSet();
+ private final ChunkSet requestedChunks = ChunkSets.newHashSet();
+
+ private final TLongSet visibleEntities = TCollections.synchronizedSet(new TLongHashSet());
+ private final TLongSet requestedEntities = new TLongHashSet();
+
+ public PlayerVision(Player player) {
+ this.player = player;
+ }
+
+ public boolean isChunkVisible(Vec3i chunkPos) {
+ return visibleChunks.contains(chunkPos);
+ }
+
+ public boolean isEntityVisible(long entityId) {
+ return visibleEntities.contains(entityId);
+ }
+
+ /**
+ * @return the requestedChunks
+ */
+ public ChunkSet getRequestedChunks() {
+ return requestedChunks;
+ }
+
+ /**
+ * @return the visibleChunks
+ */
+ public ChunkSet getVisibleChunks() {
+ return visibleChunks;
+ }
+
+ /**
+ * @return the requestedEntities
+ */
+ public TLongSet getRequestedEntities() {
+ return requestedEntities;
+ }
+
+ /**
+ * @return the visibleEntities
+ */
+ public TLongSet getVisibleEntities() {
+ return visibleEntities;
+ }
+
+ /**
+ * @return the player
+ */
+ public Player getPlayer() {
+ return player;
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/server/management/load/VisionManager.java b/src/main/java/ru/windcorp/progressia/server/management/load/VisionManager.java
new file mode 100644
index 0000000..cdf2d8e
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/server/management/load/VisionManager.java
@@ -0,0 +1,108 @@
+/*
+ * Progressia
+ * Copyright (C) 2020-2021 Wind Corporation and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package ru.windcorp.progressia.server.management.load;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+
+import com.google.common.eventbus.Subscribe;
+
+import glm.vec._3.i.Vec3i;
+import ru.windcorp.progressia.common.world.generic.ChunkSet;
+import ru.windcorp.progressia.common.world.generic.ChunkSets;
+import ru.windcorp.progressia.server.Player;
+import ru.windcorp.progressia.server.Server;
+import ru.windcorp.progressia.server.events.PlayerJoinedEvent;
+import ru.windcorp.progressia.server.events.PlayerLeftEvent;
+
+public class VisionManager {
+
+ private final LoadManager loadManager;
+
+ private final Map visions = Collections.synchronizedMap(new HashMap<>());
+
+ public VisionManager(LoadManager loadManager) {
+ this.loadManager = loadManager;
+ getServer().subscribe(this);
+ }
+
+ @Subscribe
+ private void onPlayerJoined(PlayerJoinedEvent event) {
+ System.out.println("VisionManager.onPlayerJoined()");
+ getVision(event.getPlayer(), true);
+ }
+
+ @Subscribe
+ private void onPlayerLeft(PlayerLeftEvent event) {
+ System.out.println("VisionManager.onPlayerLeft()");
+ visions.remove(event.getPlayer());
+ }
+
+ public PlayerVision getVision(Player player, boolean createIfMissing) {
+ if (createIfMissing) {
+ return visions.computeIfAbsent(player, PlayerVision::new);
+ } else {
+ return visions.get(player);
+ }
+ }
+
+ public void forEachVision(Consumer super PlayerVision> action) {
+ visions.values().forEach(action);
+ }
+
+ public boolean isChunkVisible(Vec3i chunkPos, Player player) {
+ PlayerVision vision = getVision(player, false);
+
+ if (vision == null) {
+ return false;
+ }
+
+ return vision.isChunkVisible(chunkPos);
+ }
+
+ public ChunkSet getVisibleChunks(Player player) {
+ PlayerVision vision = getVision(player, false);
+
+ if (vision == null) {
+ return ChunkSets.empty();
+ }
+
+ return vision.getVisibleChunks();
+ }
+
+ /**
+ * @return the loadManager
+ */
+ public LoadManager getLoadManager() {
+ return loadManager;
+ }
+
+ /**
+ * @return the chunkManager
+ */
+ public ChunkManager getChunkManager() {
+ return getLoadManager().getChunkManager();
+ }
+
+ public Server getServer() {
+ return getLoadManager().getServer();
+ }
+
+}