Refactored ChunkManager and EntityManager, added server event bus

This commit is contained in:
OLEGSHA 2021-03-26 20:26:12 +03:00
parent ef572c43c7
commit 4332a78221
Signed by: OLEGSHA
GPG Key ID: E57A4B08D64AFF7A
19 changed files with 1429 additions and 479 deletions

View File

@ -0,0 +1,261 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.common.world.generic;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import glm.vec._3.i.Vec3i;
import gnu.trove.map.hash.TLongObjectHashMap;
public class ChunkMaps {
public static <V> ChunkMap<V> newHashMap() {
return new LongBasedChunkMap<V>(new TLongObjectHashMap<V>());
}
public static <V> ChunkMap<V> newSyncHashMap(Object mutex) {
return new SynchronizedChunkMap<V>(new LongBasedChunkMap<V>(new TLongObjectHashMap<V>()), mutex);
}
public static <V> ChunkMap<V> newSyncHashMap() {
return newSyncHashMap(null);
}
@SuppressWarnings("unchecked")
public static <V> ChunkMap<V> empty() {
return (ChunkMap<V>) EMPTY_MAP;
}
private ChunkMaps() {
}
private final static ChunkMap<Object> EMPTY_MAP = new ChunkMap<Object>() {
@Override
public int size() {
return 0;
}
@Override
public boolean containsKey(Vec3i pos) {
return false;
}
@Override
public Object get(Vec3i pos) {
return null;
}
@Override
public Object put(Vec3i pos, Object obj) {
throw new UnsupportedOperationException();
}
@Override
public Object remove(Vec3i pos) {
throw new UnsupportedOperationException();
}
@Override
public Collection<Object> values() {
return Collections.emptyList();
}
@Override
public ChunkSet keys() {
return ChunkSets.empty();
}
@Override
public boolean removeIf(BiPredicate<? super Vec3i, ? super Object> condition) {
return false;
}
@Override
public void forEach(BiConsumer<? super Vec3i, ? super Object> action) {
// Do nothing
}
};
private static class SynchronizedChunkMap<V> implements ChunkMap<V> {
private final ChunkMap<V> parent;
private final Object mutex;
public SynchronizedChunkMap(ChunkMap<V> parent, Object mutex) {
Objects.requireNonNull(parent, "parent");
this.parent = parent;
this.mutex = mutex == null ? this : mutex;
}
@Override
public int size() {
synchronized (mutex) {
return parent.size();
}
}
@Override
public boolean isEmpty() {
synchronized (mutex) {
return parent.isEmpty();
}
}
@Override
public boolean containsKey(Vec3i pos) {
synchronized (mutex) {
return parent.containsKey(pos);
}
}
@Override
public V get(Vec3i pos) {
synchronized (mutex) {
return parent.get(pos);
}
}
@Override
public V put(Vec3i pos, V obj) {
synchronized (mutex) {
return parent.put(pos, obj);
}
}
@Override
public V remove(Vec3i pos) {
synchronized (mutex) {
return parent.remove(pos);
}
}
@Override
public boolean containsValue(V value) {
synchronized (mutex) {
return parent.containsValue(value);
}
}
@Override
public V getOrDefault(Vec3i pos, V def) {
synchronized (mutex) {
return parent.getOrDefault(pos, def);
}
}
@Override
public V compute(Vec3i pos, BiFunction<? super Vec3i, ? super V, ? extends V> remappingFunction) {
synchronized (mutex) {
return parent.compute(pos, remappingFunction);
}
}
@Override
public boolean containsChunk(GenericChunk<?, ?, ?, ?> chunk) {
synchronized (mutex) {
return parent.containsChunk(chunk);
}
}
@Override
public V get(GenericChunk<?, ?, ?, ?> chunk) {
synchronized (mutex) {
return parent.get(chunk);
}
}
@Override
public V put(GenericChunk<?, ?, ?, ?> chunk, V obj) {
synchronized (mutex) {
return parent.put(chunk, obj);
}
}
@Override
public V remove(GenericChunk<?, ?, ?, ?> chunk) {
synchronized (mutex) {
return parent.remove(chunk);
}
}
@Override
public V getOrDefault(GenericChunk<?, ?, ?, ?> chunk, V def) {
synchronized (mutex) {
return parent.getOrDefault(chunk, def);
}
}
@Override
public <C extends GenericChunk<C, ?, ?, ?>> V compute(
C chunk,
BiFunction<? super C, ? super V, ? extends V> remappingFunction
) {
synchronized (mutex) {
return parent.compute(chunk, remappingFunction);
}
}
@Override
public Collection<V> values() {
synchronized (mutex) {
return parent.values();
}
}
@Override
public ChunkSet keys() {
synchronized (mutex) {
return parent.keys();
}
}
@Override
public boolean removeIf(BiPredicate<? super Vec3i, ? super V> condition) {
synchronized (mutex) {
return parent.removeIf(condition);
}
}
@Override
public void forEach(BiConsumer<? super Vec3i, ? super V> action) {
synchronized (mutex) {
parent.forEach(action);
}
}
@Override
public <C extends GenericChunk<C, ?, ?, ?>> void forEachIn(
GenericWorld<?, ?, ?, C, ?> world,
BiConsumer<? super C, ? super V> action
) {
synchronized (mutex) {
parent.forEachIn(world, action);
}
}
}
}

View File

@ -1,245 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.server;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
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.common.world.generic.ChunkSet;
import ru.windcorp.progressia.common.world.generic.ChunkSets;
import ru.windcorp.progressia.test.TestWorldDiskIO;
public class ChunkManager {
private class PlayerVision {
private final ChunkSet visible = ChunkSets.newSyncHashSet();
private final ChunkSet requested = ChunkSets.newHashSet();
private final ChunkSet toSend = ChunkSets.newHashSet();
private final ChunkSet toRevoke = ChunkSets.newHashSet();
public boolean isChunkVisible(Vec3i chunkPos) {
return visible.contains(chunkPos);
}
public void gatherRequests(Player player) {
requested.clear();
player.requestChunksToLoad(requested::add);
}
public void updateQueues(Player player) {
toSend.clear();
requested.forEachIn(server.getWorld(), chunk -> {
if (!chunk.isReady())
return;
if (visible.contains(chunk))
return;
toSend.add(chunk);
});
toRevoke.clear();
toRevoke.addAll(visible);
toRevoke.removeIf(v -> loaded.contains(v) && requested.contains(v));
}
public void processQueues(Player player) {
toRevoke.forEach(chunkPos -> revokeChunk(player, chunkPos));
toRevoke.clear();
toSend.forEach(chunkPos -> sendChunk(player, chunkPos));
toSend.clear();
}
}
private final Server server;
private final ChunkSet loaded;
private final ChunkSet requested = ChunkSets.newHashSet();
private final ChunkSet toLoad = ChunkSets.newHashSet();
private final ChunkSet toUnload = ChunkSets.newHashSet();
// TODO replace with a normal Map managed by some sort of PlayerListener,
// weak maps are weak
private final Map<Player, PlayerVision> visions = Collections.synchronizedMap(new WeakHashMap<>());
public ChunkManager(Server server) {
this.server = server;
this.loaded = server.getWorld().getData().getLoadedChunks();
}
public void tick() {
synchronized (getServer().getWorld().getData()) {
synchronized (visions) {
gatherRequests();
updateQueues();
processQueues();
}
}
}
private void gatherRequests() {
requested.clear();
server.getPlayerManager().getPlayers().forEach(p -> {
PlayerVision vision = getVision(p, true);
vision.gatherRequests(p);
requested.addAll(vision.requested);
});
}
private void updateQueues() {
toLoad.clear();
toLoad.addAll(requested);
toLoad.removeAll(loaded);
toUnload.clear();
toUnload.addAll(loaded);
toUnload.removeAll(requested);
visions.forEach((p, v) -> {
v.updateQueues(p);
});
}
private void processQueues() {
toUnload.forEach(this::unloadChunk);
toUnload.clear();
toLoad.forEach(this::loadOrGenerateChunk);
toLoad.clear();
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 loadOrGenerateChunk(Vec3i chunkPos) {
boolean chunkLoadedFromDisk = loadChunk(chunkPos);
if (!chunkLoadedFromDisk) {
getServer().getWorld().generate(chunkPos);
}
}
public boolean loadChunk(Vec3i chunkPos) {
WorldData world = getServer().getWorld().getData();
ChunkData chunk = TestWorldDiskIO.tryToLoad(chunkPos, world, getServer());
if (chunk != null) {
world.addChunk(chunk);
return true;
} else {
return false;
}
}
public void unloadChunk(Vec3i chunkPos) {
WorldData world = getServer().getWorld().getData();
ChunkData chunk = world.getChunk(chunkPos);
if (chunk == null) {
throw new IllegalStateException(
String.format(
"Chunk (%d; %d; %d) not loaded, cannot unload",
chunkPos.x,
chunkPos.y,
chunkPos.z
)
);
}
world.removeChunk(chunk);
TestWorldDiskIO.saveChunk(chunk, getServer());
}
public void sendChunk(Player player, Vec3i chunkPos) {
ChunkData chunk = server.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);
getVision(player, true).visible.add(chunkPos);
}
public void revokeChunk(Player player, Vec3i chunkPos) {
PacketRevokeChunk packet = new PacketRevokeChunk();
packet.set(chunkPos);
player.getClient().sendPacket(packet);
PlayerVision vision = getVision(player, false);
if (vision != null) {
vision.visible.remove(chunkPos);
}
}
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.visible;
}
public Server getServer() {
return server;
}
}

View File

@ -1,197 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.server;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import glm.vec._3.i.Vec3i;
import gnu.trove.TCollections;
import gnu.trove.iterator.TLongIterator;
import gnu.trove.set.TLongSet;
import gnu.trove.set.hash.TLongHashSet;
import ru.windcorp.jputil.chars.StringUtil;
import ru.windcorp.progressia.common.util.Vectors;
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.common.world.generic.ChunkSet;
public class EntityManager {
private class PlayerVision {
private final TLongSet visible = TCollections.synchronizedSet(new TLongHashSet());
private final TLongSet requested = new TLongHashSet();
private final TLongSet toSend = new TLongHashSet();
private final TLongSet toRevoke = new TLongHashSet();
public boolean isEntityVisible(long entityId) {
return visible.contains(entityId);
}
public void gatherRequests(Player player) {
requested.clear();
ChunkSet visibleChunks = player.getClient().getVisibleChunks();
Vec3i v = Vectors.grab3i();
getServer().getWorld().forEachEntity(entity -> {
if (visibleChunks.contains(entity.getChunkCoords(v))) {
requested.add(entity.getEntityId());
}
});
Vectors.release(v);
}
public void updateQueues(Player player) {
toSend.clear();
toSend.addAll(requested);
toSend.removeAll(visible);
toSend.retainAll(loaded);
toRevoke.clear();
for (TLongIterator it = visible.iterator(); it.hasNext();) {
long entityId = it.next();
if (!loaded.contains(entityId) || !requested.contains(entityId)) {
toRevoke.add(entityId);
}
}
}
public void processQueues(Player player) {
toRevoke.forEach(entityId -> {
revokeEntity(player, entityId);
return true;
});
toRevoke.clear();
toSend.forEach(entityId -> {
sendEntity(player, entityId);
return true;
});
toSend.clear();
}
}
private final Server server;
private final TLongSet loaded;
// TODO replace with a normal Map managed by some sort of PlayerListener,
// weak maps are weak
private final Map<Player, PlayerVision> 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;
}
}

View File

@ -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;

View File

@ -15,18 +15,25 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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.
* <p>
* 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.
* <p>
* 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)}.
* <p>
* 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 <E extends Exception> void waitAndInvoke(
ThrowingRunnable<E> task
)
throws InterruptedException,
E {
public <E extends Exception> void waitAndInvoke(ThrowingRunnable<E> 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}.

View File

@ -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());

View File

@ -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) {

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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;
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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();
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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;
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<Vec3i> 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<ChunkUnloadRequest> 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<ChunkUnloadRequest> 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();
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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();
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<Player, PlayerVision> 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();
}
}