Reverted last two commits because no one wants to fix the bugs

This commit is contained in:
OLEGSHA 2021-08-28 21:31:34 +03:00
parent a222ea8f67
commit 41a2909f7c
Signed by: OLEGSHA
GPG Key ID: E57A4B08D64AFF7A
7 changed files with 104 additions and 191 deletions

View File

@ -25,9 +25,8 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import glm.vec._3.i.Vec3i; import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.block.BlockData; import ru.windcorp.progressia.common.world.block.BlockData;
@ -50,7 +49,7 @@ public class DefaultChunkData implements ChunkData {
public boolean isEmpty = false; public boolean isEmpty = false;
public boolean isOpaque = false; public boolean isOpaque = false;
public static Set<BlockData> transparent = Collections.emptySet(); public static HashSet<BlockData> transparent;
private final Vec3i position = new Vec3i(); private final Vec3i position = new Vec3i();
private final DefaultWorldData world; private final DefaultWorldData world;
@ -205,36 +204,45 @@ public class DefaultChunkData implements ChunkData {
public void setGenerationHint(Object generationHint) { public void setGenerationHint(Object generationHint) {
this.generationHint = generationHint; this.generationHint = generationHint;
} }
public void computeOpaque() { public void computeOpaque()
for (int xyz = 0; xyz < BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK; xyz++) { {
if (transparent.contains(blocks[xyz])) { for (int xyz=0;xyz<BLOCKS_PER_CHUNK*BLOCKS_PER_CHUNK*BLOCKS_PER_CHUNK;xyz++)
{
if (transparent.contains( blocks[xyz]))
{
isOpaque = false; isOpaque = false;
return; return;
} }
} }
isOpaque = true; isOpaque = true;
} }
public boolean isOpaque() { public boolean isOpaque()
{
return isOpaque; return isOpaque;
} }
public void computeEmpty() { public void computeEmpty()
{
BlockData air = new BlockData("Test:Air"); BlockData air = new BlockData("Test:Air");
for (int xyz = 0; xyz < BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK; xyz++) { for (int xyz=0;xyz<BLOCKS_PER_CHUNK*BLOCKS_PER_CHUNK*BLOCKS_PER_CHUNK;xyz++)
if (blocks[xyz] != air) { {
if (blocks[xyz] != air)
{
isEmpty = false; isEmpty = false;
return; return;
} }
} }
isEmpty = true; isEmpty = true;
} }
public boolean isEmpty() { public boolean isEmpty()
{
return isEmpty; return isEmpty;
} }
/** /**
* Implementation of {@link TileDataStack} used internally by * Implementation of {@link TileDataStack} used internally by
* {@link DefaultChunkData} to * {@link DefaultChunkData} to

View File

@ -71,9 +71,6 @@ public class Server {
private final ClientManager clientManager; private final ClientManager clientManager;
private final PlayerManager playerManager; private final PlayerManager playerManager;
private final LoadManager loadManager; private final LoadManager loadManager;
private final ChunkRequestDaemon chunkRequestDaemon;
private final EntityRequestDaemon entityRequestDaemon;
private final TaskQueue taskQueue = new TaskQueue(this::isServerThread); private final TaskQueue taskQueue = new TaskQueue(this::isServerThread);
@ -93,13 +90,10 @@ public class Server {
this.clientManager = new ClientManager(this); this.clientManager = new ClientManager(this);
this.playerManager = new PlayerManager(this); this.playerManager = new PlayerManager(this);
this.loadManager = new LoadManager(this); this.loadManager = new LoadManager(this);
this.chunkRequestDaemon = new ChunkRequestDaemon(loadManager.getChunkManager());
this.entityRequestDaemon = new EntityRequestDaemon(loadManager.getEntityManager());
schedule(getClientManager()::processPackets); schedule(getClientManager()::processPackets);
schedule(this.chunkRequestDaemon::tick); schedule(new ChunkRequestDaemon(loadManager.getChunkManager())::tick);
schedule(this.entityRequestDaemon::tick); schedule(new EntityRequestDaemon(loadManager.getEntityManager())::tick);
// Must run after request daemons so it only schedules chunks that // Must run after request daemons so it only schedules chunks that
// hadn't unloaded // hadn't unloaded
@ -335,7 +329,6 @@ public class Server {
* operation or fails to start. * operation or fails to start.
*/ */
public void start() { public void start() {
this.chunkRequestDaemon.start();
this.serverThread.start(); this.serverThread.start();
} }
@ -356,7 +349,6 @@ public class Server {
public void shutdown(String message) { public void shutdown(String message) {
LogManager.getLogger().warn("Server.shutdown() is not yet implemented"); LogManager.getLogger().warn("Server.shutdown() is not yet implemented");
serverThread.stop(); serverThread.stop();
chunkRequestDaemon.stop();
} }
private void scheduleWorldTicks(Server server) { private void scheduleWorldTicks(Server server) {

View File

@ -34,7 +34,7 @@ public class ServerState {
} }
public static void startServer() { public static void startServer() {
Server server = new Server(new DefaultWorldData(), new TestGenerationConfig().getGenerator()); Server server = new Server(new DefaultWorldData(), TestGenerationConfig.createGenerator());
setInstance(server); setInstance(server);
server.start(); server.start();
} }

View File

@ -20,9 +20,8 @@ package ru.windcorp.progressia.server.management.load;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.ExecutorService;
import java.util.function.Consumer; import java.util.concurrent.Executors;
import glm.vec._3.i.Vec3i; import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.Units; import ru.windcorp.progressia.common.Units;
import ru.windcorp.progressia.common.world.generic.ChunkMap; import ru.windcorp.progressia.common.world.generic.ChunkMap;
@ -32,39 +31,40 @@ import ru.windcorp.progressia.common.world.generic.ChunkSets;
import ru.windcorp.progressia.server.Server; import ru.windcorp.progressia.server.Server;
/** /**
* Chunk request daemon gathers chunk requests from players (via * Chunk request daemon gathers chunk requests from players (via {@link VisionManager}) and loads or unloads chunks appropriately.
* {@link VisionManager}) and loads or unloads chunks appropriately.
*/ */
public class ChunkRequestDaemon { public class ChunkRequestDaemon {
private static final float CHUNK_UNLOAD_DELAY = Units.get(5, "s"); private static final float CHUNK_UNLOAD_DELAY = Units.get(5, "s");
private final ChunkManager chunkManager; private final ChunkManager chunkManager;
private final ChunkSet loaded; private final ChunkSet loaded;
private final ChunkSet requested = ChunkSets.newHashSet(); private final ChunkSet requested = ChunkSets.newHashSet();
private final ChunkSet toLoad = ChunkSets.newHashSet(); private final ChunkSet toLoad = ChunkSets.newHashSet();
private final ChunkSet toGenerate = ChunkSets.newHashSet(); private final ChunkSet toGenerate = ChunkSets.newHashSet();
private final ChunkSet toRequestUnload = ChunkSets.newHashSet(); private final ChunkSet toRequestUnload = ChunkSets.newHashSet();
private final ExecutorService executor = Executors.newSingleThreadExecutor();
private final Collection<Vec3i> buffer = new ArrayList<>(); private final Collection<Vec3i> buffer = new ArrayList<>();
private static class ChunkUnloadRequest { private static class ChunkUnloadRequest {
private final Vec3i chunkPos; private final Vec3i chunkPos;
private final long unloadAt; private final long unloadAt;
public ChunkUnloadRequest(Vec3i chunkPos, long unloadAt) { public ChunkUnloadRequest(Vec3i chunkPos, long unloadAt) {
this.chunkPos = chunkPos; this.chunkPos = chunkPos;
this.unloadAt = unloadAt; this.unloadAt = unloadAt;
} }
/** /**
* @return the chunk position * @return the chunk position
*/ */
public Vec3i getChunkPos() { public Vec3i getChunkPos() {
return chunkPos; return chunkPos;
} }
/** /**
* @return the moment when the chunks becomes eligible for unloading * @return the moment when the chunks becomes eligible for unloading
*/ */
@ -72,42 +72,19 @@ public class ChunkRequestDaemon {
return unloadAt; return unloadAt;
} }
} }
private final ChunkMap<ChunkUnloadRequest> unloadSchedule = ChunkMaps.newHashMap(); private final ChunkMap<ChunkUnloadRequest> unloadSchedule = ChunkMaps.newHashMap();
private Thread thread = null;
private final AtomicBoolean shouldRun = new AtomicBoolean(false);
public ChunkRequestDaemon(ChunkManager chunkManager) { public ChunkRequestDaemon(ChunkManager chunkManager) {
this.chunkManager = chunkManager; this.chunkManager = chunkManager;
this.loaded = getServer().getWorld().getData().getLoadedChunks(); this.loaded = getServer().getWorld().getData().getLoadedChunks();
} }
public synchronized void start() { public void tick() {
if (this.thread != null) {
throw new IllegalStateException("Already running");
}
this.thread = new Thread(this::runOffthread, getClass().getSimpleName());
this.shouldRun.set(true);
this.thread.start();
}
public synchronized void stop() {
this.shouldRun.set(false);
synchronized (this) {
notify();
}
}
public synchronized void tick() {
synchronized (getServer().getWorld().getData()) { synchronized (getServer().getWorld().getData()) {
synchronized (getServer().getPlayerManager().getMutex()) { synchronized (getServer().getPlayerManager().getMutex()) {
loadAndUnloadChunks(); loadAndUnloadChunks();
sendAndRevokeChunks(); sendAndRevokeChunks();
notify();
} }
} }
} }
@ -139,13 +116,18 @@ public class ChunkRequestDaemon {
} }
private void processLoadQueues() { private void processLoadQueues() {
toGenerate.forEach(getChunkManager()::loadOrGenerateChunk); toRequestUnload.forEach((pos) -> executor.submit(() -> scheduleUnload(pos)));
toGenerate.clear();
toRequestUnload.forEach(this::scheduleUnload);
toRequestUnload.clear(); toRequestUnload.clear();
toLoad.forEach((pos) -> executor.submit(() -> getChunkManager().loadOrGenerateChunk(pos)));
toLoad.clear();
toGenerate.forEach((pos) -> executor.submit(() -> getChunkManager().loadOrGenerateChunk(pos)));
toGenerate.clear();
executor.submit(() -> unloadScheduledChunks());
} }
private void scheduleUnload(Vec3i chunkPos) { private void scheduleUnload(Vec3i chunkPos) {
if (unloadSchedule.containsKey(chunkPos)) { if (unloadSchedule.containsKey(chunkPos)) {
// Unload already requested, skip // Unload already requested, skip
@ -154,9 +136,28 @@ public class ChunkRequestDaemon {
long unloadAt = System.currentTimeMillis() + (long) (getUnloadDelay() * 1000); long unloadAt = System.currentTimeMillis() + (long) (getUnloadDelay() * 1000);
Vec3i chunkPosCopy = new Vec3i(chunkPos); Vec3i chunkPosCopy = new Vec3i(chunkPos);
unloadSchedule.put(chunkPosCopy, new ChunkUnloadRequest(chunkPosCopy, unloadAt)); 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();
if (requested.contains(request.getChunkPos())) {
continue; // do not unload chunks that became requested
}
getChunkManager().unloadChunk(request.getChunkPos());
}
}
}
private void sendAndRevokeChunks() { private void sendAndRevokeChunks() {
getChunkManager().getLoadManager().getVisionManager().forEachVision(vision -> { getChunkManager().getLoadManager().getVisionManager().forEachVision(vision -> {
@ -171,129 +172,51 @@ public class ChunkRequestDaemon {
toGenerate.add(chunk); toGenerate.add(chunk);
return; return;
} }
if (vision.isChunkVisible(chunk.getPosition())) { if (vision.isChunkVisible(chunk.getPosition())) {
return; return;
} }
buffer.add(chunk.getPosition()); buffer.add(chunk.getPosition());
}); });
if (buffer.isEmpty()) if (buffer.isEmpty()) return;
return;
for (Vec3i chunkPos : buffer) { for (Vec3i chunkPos : buffer) {
getChunkManager().sendChunk(vision.getPlayer(), chunkPos); getChunkManager().sendChunk(vision.getPlayer(), chunkPos);
} }
buffer.clear(); buffer.clear();
} }
private void revokeChunks(PlayerVision vision) { private void revokeChunks(PlayerVision vision) {
vision.getVisibleChunks().forEach(chunkPos -> { vision.getVisibleChunks().forEach(chunkPos -> {
if (getChunkManager().isChunkLoaded(chunkPos) && vision.getRequestedChunks().contains(chunkPos)) if (getChunkManager().isChunkLoaded(chunkPos) && vision.getRequestedChunks().contains(chunkPos))
return; return;
buffer.add(new Vec3i(chunkPos)); buffer.add(new Vec3i(chunkPos));
}); });
if (buffer.isEmpty()) if (buffer.isEmpty()) return;
return;
for (Vec3i chunkPos : buffer) { for (Vec3i chunkPos : buffer) {
getChunkManager().revokeChunk(vision.getPlayer(), chunkPos); getChunkManager().revokeChunk(vision.getPlayer(), chunkPos);
} }
buffer.clear(); buffer.clear();
} }
/*
* Off-thread activity
*/
private void runOffthread() {
while (true) {
processLoadQueue();
processUnloadQueue();
synchronized (this) {
try {
// We're not afraid of spurious wakeups
wait();
} catch (InterruptedException e) {
// Pretend nothing happened
}
if (!shouldRun.get()) {
return;
}
}
}
}
private void processQueueOffthread(ChunkSet queue, Consumer<Vec3i> action) {
while (true) {
synchronized (this) {
Iterator<Vec3i> iterator = toLoad.iterator();
if (!iterator.hasNext()) {
return;
}
Vec3i position = iterator.next();
iterator.remove();
action.accept(position);
}
}
}
private void processLoadQueue() {
processQueueOffthread(toLoad, getChunkManager()::loadOrGenerateChunk);
}
private void processUnloadQueue() {
long now = System.currentTimeMillis();
Collection<Vec3i> toUnload = null;
synchronized (this) {
for (Iterator<ChunkUnloadRequest> it = unloadSchedule.values().iterator(); it.hasNext();) {
ChunkUnloadRequest request = it.next();
if (request.getUnloadAt() < now) {
it.remove();
if (requested.contains(request.getChunkPos())) {
continue; // do not unload chunks that became requested
}
if (toUnload == null) {
toUnload = new ArrayList<>();
}
toUnload.add(request.getChunkPos());
}
}
}
if (toUnload == null) {
return;
}
toUnload.forEach(getChunkManager()::unloadChunk);
}
/** /**
* @return the minimum amount of time a chunk will spend in the unload queue * @return the minimum amount of time a chunk will spend in the unload queue
*/ */
public float getUnloadDelay() { public float getUnloadDelay() {
return CHUNK_UNLOAD_DELAY; return CHUNK_UNLOAD_DELAY;
} }
/** /**
* @return the manager * @return the manager
*/ */
public ChunkManager getChunkManager() { public ChunkManager getChunkManager() {
return chunkManager; return chunkManager;
} }
public Server getServer() { public Server getServer() {
return getChunkManager().getServer(); return getChunkManager().getServer();
} }

View File

@ -34,7 +34,7 @@ import ru.windcorp.progressia.common.world.rels.RelFace;
import ru.windcorp.progressia.common.world.TileDataStack; import ru.windcorp.progressia.common.world.TileDataStack;
import ru.windcorp.progressia.common.world.generic.GenericChunks; import ru.windcorp.progressia.common.world.generic.GenericChunks;
import ru.windcorp.progressia.common.world.TileDataReference; import ru.windcorp.progressia.common.world.TileDataReference;
import ru.windcorp.progressia.server.ServerState; import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.world.block.BlockLogic; import ru.windcorp.progressia.server.world.block.BlockLogic;
import ru.windcorp.progressia.server.world.block.BlockLogicRegistry; import ru.windcorp.progressia.server.world.block.BlockLogicRegistry;
import ru.windcorp.progressia.server.world.block.TickableBlock; import ru.windcorp.progressia.server.world.block.TickableBlock;
@ -226,7 +226,7 @@ public class DefaultChunkLogic implements ChunkLogic {
} }
private void tmp_generateTickLists() { private void tmp_generateTickLists() {
ServerWorldContextRO context = ServerState.getInstance().createContext(getUp()); ServerWorldContextRO context = Server.getCurrentServer().createContext(getUp());
GenericChunks.forEachBiC(blockInChunk -> { GenericChunks.forEachBiC(blockInChunk -> {

View File

@ -19,7 +19,6 @@
package ru.windcorp.progressia.server.world; package ru.windcorp.progressia.server.world;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -44,7 +43,7 @@ public class DefaultWorldLogic implements WorldLogic {
private final WorldGenerator generator; private final WorldGenerator generator;
private final Map<DefaultChunkData, DefaultChunkLogic> chunks = Collections.synchronizedMap(new HashMap<>()); private final Map<DefaultChunkData, DefaultChunkLogic> chunks = new HashMap<>();
private final Evaluation tickEntitiesTask = new TickEntitiesTask(); private final Evaluation tickEntitiesTask = new TickEntitiesTask();

View File

@ -49,18 +49,9 @@ public class TestGenerationConfig {
private static final float CURVATURE = Units.get("100 m"); private static final float CURVATURE = Units.get("100 m");
private static final float INNER_RADIUS = Units.get("200 m"); private static final float INNER_RADIUS = Units.get("200 m");
private final Fields fields = new Fields(SEED); private static final Fields FIELDS = new Fields(SEED);
private final Function<Server, WorldGenerator> generator;
public TestGenerationConfig() {
this.generator = createGenerator();
}
public Function<Server, WorldGenerator> getGenerator() {
return generator;
}
private Function<Server, WorldGenerator> createGenerator() { public static Function<Server, WorldGenerator> createGenerator() {
Planet planet = new Planet( Planet planet = new Planet(
((int) PLANET_RADIUS) / Coordinates.CHUNK_SIZE, ((int) PLANET_RADIUS) / Coordinates.CHUNK_SIZE,
@ -69,7 +60,7 @@ public class TestGenerationConfig {
INNER_RADIUS INNER_RADIUS
); );
TestHeightMap heightMap = new TestHeightMap(planet, planet.getRadius() / 4, fields); TestHeightMap heightMap = new TestHeightMap(planet, planet.getRadius() / 4, FIELDS);
FloatRangeMap<TerrainLayer> layers = new ArrayFloatRangeMap<>(); FloatRangeMap<TerrainLayer> layers = new ArrayFloatRangeMap<>();
registerTerrainLayers(layers); registerTerrainLayers(layers);
@ -81,11 +72,11 @@ public class TestGenerationConfig {
} }
private void registerTerrainLayers(FloatRangeMap<TerrainLayer> layers) { private static void registerTerrainLayers(FloatRangeMap<TerrainLayer> layers) {
BlockData dirt = BlockDataRegistry.getInstance().get("Test:Dirt"); BlockData dirt = BlockDataRegistry.getInstance().get("Test:Dirt");
BlockData air = BlockDataRegistry.getInstance().get("Test:Air"); BlockData air = BlockDataRegistry.getInstance().get("Test:Air");
SurfaceFloatField cliffs = fields.get("Test:Cliff"); SurfaceFloatField cliffs = FIELDS.get("Test:Cliff");
WorleyProceduralNoise.Builder<TerrainLayer> builder = WorleyProceduralNoise.builder(); WorleyProceduralNoise.Builder<TerrainLayer> builder = WorleyProceduralNoise.builder();
TestContent.ROCKS.getRocks().forEach(rock -> { TestContent.ROCKS.getRocks().forEach(rock -> {
@ -97,9 +88,9 @@ public class TestGenerationConfig {
} }
}, 1); }, 1);
}); });
SurfaceFloatField rockDepthOffsets = fields.register( SurfaceFloatField rockDepthOffsets = FIELDS.register(
"Test:RockDepthOffsets", "Test:RockDepthOffsets",
() -> tweak(fields.primitive(), 40, 5) () -> tweak(FIELDS.primitive(), 40, 5)
); );
RockLayer rockLayer = new RockLayer(builder.build(SEED), rockDepthOffsets); RockLayer rockLayer = new RockLayer(builder.build(SEED), rockDepthOffsets);
@ -114,28 +105,28 @@ public class TestGenerationConfig {
layers.put(4, Float.POSITIVE_INFINITY, rockLayer); layers.put(4, Float.POSITIVE_INFINITY, rockLayer);
} }
private void registerFeatures(List<SurfaceFeature> features) { private static void registerFeatures(List<SurfaceFeature> features) {
SurfaceFloatField forestiness = fields.register( SurfaceFloatField forestiness = FIELDS.register(
"Test:Forest", "Test:Forest",
() -> squash(scale(fields.primitive(), 200), 5) () -> squash(scale(FIELDS.primitive(), 200), 5)
); );
SurfaceFloatField grassiness = fields.register( SurfaceFloatField grassiness = FIELDS.register(
"Test:Grass", "Test:Grass",
f -> multiply( f -> multiply(
tweak(octaves(fields.primitive(), 2, 2), 40, 0.5, 1.2), tweak(octaves(FIELDS.primitive(), 2, 2), 40, 0.5, 1.2),
squash(tweak(fields.get("Test:Forest", f), 1, -1, 1), 10), squash(tweak(FIELDS.get("Test:Forest", f), 1, -1, 1), 10),
anti(squash(fields.get("Test:Cliff", f), 10)) anti(squash(FIELDS.get("Test:Cliff", f), 10))
) )
); );
Function<String, SurfaceFloatField> floweriness = flowerName -> fields.register( Function<String, SurfaceFloatField> floweriness = flowerName -> FIELDS.register(
"Test:Flower" + flowerName, "Test:Flower" + flowerName,
f -> multiply( f -> multiply(
selectPositive(squash(scale(octaves(fields.primitive(), 2, 3), 100), 2), 1, 0.5), selectPositive(squash(scale(octaves(FIELDS.primitive(), 2, 3), 100), 2), 1, 0.5),
tweak(fields.get("Test:Forest", f), 1, -1, 1.1), tweak(FIELDS.get("Test:Forest", f), 1, -1, 1.1),
anti(squash(fields.get("Test:Cliff", f), 10)) anti(squash(FIELDS.get("Test:Cliff", f), 10))
) )
); );