Refactored ChunkManager and EntityManager, added server event bus
This commit is contained in:
parent
ef572c43c7
commit
4332a78221
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
@ -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}.
|
||||
|
@ -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());
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user