diff --git a/src/main/java/ru/windcorp/progressia/client/Client.java b/src/main/java/ru/windcorp/progressia/client/Client.java index 9902de5..0d84fe3 100644 --- a/src/main/java/ru/windcorp/progressia/client/Client.java +++ b/src/main/java/ru/windcorp/progressia/client/Client.java @@ -19,7 +19,7 @@ public class Client { private final ServerCommsChannel comms; public Client(WorldData world, ServerCommsChannel comms) { - this.world = new WorldRender(world); + this.world = new WorldRender(world, this); this.comms = comms; comms.addListener(new DefaultClientCommsListener(this)); diff --git a/src/main/java/ru/windcorp/progressia/client/world/ChunkRender.java b/src/main/java/ru/windcorp/progressia/client/world/ChunkRender.java index 94af7f8..5cf3555 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/ChunkRender.java +++ b/src/main/java/ru/windcorp/progressia/client/world/ChunkRender.java @@ -59,7 +59,6 @@ implements GenericChunk< private final WorldRender world; private final ChunkData data; - private boolean needsUpdate; private Model model = null; private final Map tileRenderLists = @@ -108,16 +107,12 @@ implements GenericChunk< } public synchronized void markForUpdate() { - this.needsUpdate = true; - } - - public synchronized boolean needsUpdate() { - return needsUpdate; + getWorld().markChunkForUpdate(getPosition()); } public synchronized void render(ShapeRenderHelper renderer) { - if (model == null || needsUpdate()) { - buildModel(); + if (model == null) { + return; } renderer.pushTransform().translate( @@ -131,7 +126,7 @@ implements GenericChunk< renderer.popTransform(); } - private void buildModel() { + public synchronized void update() { Collection optimizers = ChunkRenderOptimizers.getAllSuppliers().stream() .map(ChunkRenderOptimizerSupplier::createOptimizer) @@ -159,7 +154,6 @@ implements GenericChunk< .forEach(builder::addPart); model = new StaticModel(builder); - needsUpdate = false; } private void buildBlock( diff --git a/src/main/java/ru/windcorp/progressia/client/world/WorldRender.java b/src/main/java/ru/windcorp/progressia/client/world/WorldRender.java index 14f99d4..4b01aaf 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/WorldRender.java +++ b/src/main/java/ru/windcorp/progressia/client/world/WorldRender.java @@ -20,10 +20,12 @@ package ru.windcorp.progressia.client.world; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import java.util.WeakHashMap; import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.client.Client; import ru.windcorp.progressia.client.graphics.backend.FaceCulling; import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper; import ru.windcorp.progressia.client.world.block.BlockRender; @@ -31,11 +33,14 @@ import ru.windcorp.progressia.client.world.entity.EntityRenderRegistry; import ru.windcorp.progressia.client.world.entity.EntityRenderable; import ru.windcorp.progressia.client.world.tile.TileRender; import ru.windcorp.progressia.client.world.tile.TileRenderStack; +import ru.windcorp.progressia.common.util.VectorUtil; import ru.windcorp.progressia.common.world.ChunkData; import ru.windcorp.progressia.common.world.ChunkDataListeners; import ru.windcorp.progressia.common.world.WorldData; import ru.windcorp.progressia.common.world.WorldDataListener; import ru.windcorp.progressia.common.world.entity.EntityData; +import ru.windcorp.progressia.common.world.generic.ChunkSet; +import ru.windcorp.progressia.common.world.generic.ChunkSets; import ru.windcorp.progressia.common.world.generic.GenericWorld; public class WorldRender @@ -46,35 +51,52 @@ implements GenericWorld< ChunkRender, EntityRenderable > { - + private final WorldData data; + private final Client client; private final Map chunks = Collections.synchronizedMap(new HashMap<>()); private final Map entityModels = Collections.synchronizedMap(new WeakHashMap<>()); - public WorldRender(WorldData data) { + private final ChunkSet chunksToUpdate = ChunkSets.newSyncHashSet(); + + public WorldRender(WorldData data, Client client) { this.data = data; + this.client = client; data.addListener(ChunkDataListeners.createAdder(new ChunkUpdateListener(this))); data.addListener(new WorldDataListener() { @Override public void onChunkLoaded(WorldData world, ChunkData chunk) { - chunks.put(chunk, new ChunkRender(WorldRender.this, chunk)); + addChunk(chunk); } @Override public void beforeChunkUnloaded(WorldData world, ChunkData chunk) { - chunks.remove(chunk); + removeChunk(chunk); } }); } + + protected void addChunk(ChunkData chunk) { + chunks.put(chunk, new ChunkRender(WorldRender.this, chunk)); + markChunkForUpdate(chunk.getPosition()); + } + protected void removeChunk(ChunkData chunk) { + chunks.remove(chunk); + } + public WorldData getData() { return data; } + public Client getClient() { + return client; + } + public ChunkRender getChunk(ChunkData chunkData) { return chunks.get(chunkData); } @@ -95,10 +117,67 @@ implements GenericWorld< } public void render(ShapeRenderHelper renderer) { + updateChunks(); + getChunks().forEach(chunk -> chunk.render(renderer)); renderEntities(renderer); } + private void updateChunks() { + synchronized (chunksToUpdate) { + if (chunksToUpdate.isEmpty()) return; + + int updates = updateChunksNearLocalPlayer(); + int maximumUpdates = getMaximumChunkUpdatesPerFrame(); + + updateRandomChunks(maximumUpdates - updates); + } + } + + private int updateChunksNearLocalPlayer() { + EntityData entity = getClient().getLocalPlayer().getEntity(); + if (entity == null) return 0; + + int[] updates = new int[] { 0 }; + + VectorUtil.iterateCuboidAround(entity.getChunkCoords(null), 3, chunkPos -> { + if (chunksToUpdate.contains(chunkPos)) { + getChunk(chunkPos).update(); + chunksToUpdate.remove(chunkPos); + + updates[0]++; + } + }); + + return updates[0]; + } + + private void updateRandomChunks(int allowedUpdates) { + if (allowedUpdates <= 0) return; + + for (Iterator it = chunksToUpdate.iterator(); it.hasNext();) { + Vec3i chunkPos = it.next(); + ChunkRender chunk = getChunk(chunkPos); + + if (chunk != null) { + chunk.update(); + allowedUpdates--; + } + + it.remove(); + + if (allowedUpdates <= 0) return; + } + } + + private int getMaximumChunkUpdatesPerFrame() { + return 1; + } + + public int getPendingChunkUpdates() { + return chunksToUpdate.size(); + } + private void renderEntities(ShapeRenderHelper renderer) { FaceCulling.push(false); @@ -122,5 +201,11 @@ implements GenericWorld< return EntityRenderRegistry.getInstance().get(entity.getId()) .createRenderable(entity); } + + public void markChunkForUpdate(Vec3i chunkPos) { + if (getData().getChunk(chunkPos) != null) { + chunksToUpdate.add(chunkPos); + } + } } diff --git a/src/main/java/ru/windcorp/progressia/common/util/VectorUtil.java b/src/main/java/ru/windcorp/progressia/common/util/VectorUtil.java index eaa02ed..4e7ac70 100644 --- a/src/main/java/ru/windcorp/progressia/common/util/VectorUtil.java +++ b/src/main/java/ru/windcorp/progressia/common/util/VectorUtil.java @@ -19,7 +19,7 @@ public class VectorUtil { X, Y, Z, W; } - public static void forEachVectorInCuboid( + public static void iterateCuboid( int x0, int y0, int z0, int x1, int y1, int z1, Consumer action @@ -38,6 +38,57 @@ public class VectorUtil { Vectors.release(cursor); } + public static void iterateCuboid( + Vec3i vMin, Vec3i vMax, + Consumer action + ) { + iterateCuboid(vMin.x, vMin.y, vMin.z, vMax.x, vMax.y, vMax.z, action); + } + + public static void iterateCuboidAround( + int cx, int cy, int cz, + int dx, int dy, int dz, + Consumer action + ) { + if (dx < 0) throw new IllegalArgumentException("dx " + dx + " is negative"); + if (dy < 0) throw new IllegalArgumentException("dy " + dx + " is negative"); + if (dz < 0) throw new IllegalArgumentException("dz " + dx + " is negative"); + + if (dx % 2 == 0) throw new IllegalArgumentException("dx " + dx + " is even, only odd accepted"); + if (dy % 2 == 0) throw new IllegalArgumentException("dy " + dy + " is even, only odd accepted"); + if (dz % 2 == 0) throw new IllegalArgumentException("dz " + dz + " is even, only odd accepted"); + + dx /= 2; + dy /= 2; + dz /= 2; + + iterateCuboid(cx - dx, cy - dy, cz - dz, cx + dx + 1, cy + dy + 1, cz + dz + 1, action); + } + + public static void iterateCuboidAround( + Vec3i center, + Vec3i diameters, + Consumer action + ) { + iterateCuboidAround(center.x, center.y, center.z, diameters.x, diameters.y, diameters.z, action); + } + + public static void iterateCuboidAround( + int cx, int cy, int cz, + int diameter, + Consumer action + ) { + iterateCuboidAround(cx, cy, cz, diameter, diameter, diameter, action); + } + + public static void iterateCuboidAround( + Vec3i center, + int diameter, + Consumer action + ) { + iterateCuboidAround(center.x, center.y, center.z, diameter, action); + } + public static void applyMat4(Vec3 in, Mat4 mat, Vec3 out) { Vec4 vec4 = Vectors.grab4(); vec4.set(in, 1f); diff --git a/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java b/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java index ff9fc71..2e454e0 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java +++ b/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java @@ -183,7 +183,7 @@ implements GenericChunk< } public void forEachBlock(Consumer action) { - VectorUtil.forEachVectorInCuboid( + VectorUtil.iterateCuboid( 0, 0, 0, BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK, action diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkSets.java b/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkSets.java index a8b23d9..3e50dd3 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkSets.java +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkSets.java @@ -2,9 +2,11 @@ package ru.windcorp.progressia.common.world.generic; import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Predicate; import glm.vec._3.i.Vec3i; -import gnu.trove.impl.sync.TSynchronizedLongSet; import gnu.trove.set.hash.TLongHashSet; public class ChunkSets { @@ -14,13 +16,19 @@ public class ChunkSets { } public static ChunkSet newSyncHashSet(Object mutex) { - return new LongBasedChunkSet(new TSynchronizedLongSet(new TLongHashSet(), mutex)); + return new SynchronizedChunkSet(new LongBasedChunkSet(new TLongHashSet()), mutex); } public static ChunkSet newSyncHashSet() { - return new LongBasedChunkSet(new TSynchronizedLongSet(new TLongHashSet())); + return newSyncHashSet(null); } + public static ChunkSet empty() { + return EMPTY_SET; + } + + private ChunkSets() {} + private final static ChunkSet EMPTY_SET = new ChunkSet() { @Override @@ -89,10 +97,174 @@ public class ChunkSets { }; - public static ChunkSet empty() { - return EMPTY_SET; + private static class SynchronizedChunkSet implements ChunkSet { + + private final ChunkSet parent; + private final Object mutex; + + public SynchronizedChunkSet(ChunkSet parent, Object mutex) { + Objects.requireNonNull(parent, "parent"); + this.parent = parent; + + this.mutex = mutex == null ? this : mutex; + } + + @Override + public Iterator iterator() { + return parent.iterator(); // Must be synchronized manually by user! + } + + @Override + public void forEach(Consumer action) { + synchronized (mutex) { parent.forEach(action); } + } + + @Override + public int size() { + synchronized (mutex) { return parent.size(); } + } + + @Override + public boolean isEmpty() { + synchronized (mutex) { return parent.isEmpty(); } + } + + @Override + public boolean contains(Vec3i pos) { + synchronized (mutex) { return parent.contains(pos); } + } + + @Override + public boolean add(Vec3i pos) { + synchronized (mutex) { return parent.add(pos); } + } + + @Override + public boolean remove(Vec3i pos) { + synchronized (mutex) { return parent.remove(pos); } + } + + @Override + public boolean contains(int x, int y, int z) { + synchronized (mutex) { return parent.contains(x, y, z); } + } + + @Override + public boolean add(int x, int y, int z) { + synchronized (mutex) { return parent.add(x, y, z); } + } + + @Override + public boolean remove(int x, int y, int z) { + synchronized (mutex) { return parent.remove(x, y, z); } + } + + @Override + public boolean contains(GenericChunk chunk) { + synchronized (mutex) { return parent.contains(chunk); } + } + + @Override + public boolean add(GenericChunk chunk) { + synchronized (mutex) { return parent.add(chunk); } + } + + @Override + public boolean remove(GenericChunk chunk) { + synchronized (mutex) { return parent.remove(chunk); } + } + + @Override + public > void forEachIn(GenericWorld world, + Consumer action) { + synchronized (mutex) { parent.forEachIn(world, action); } + } + + @Override + public boolean containsAll(ChunkSet other) { + synchronized (mutex) { return parent.containsAll(other); } + } + + @Override + public boolean containsAny(ChunkSet other) { + synchronized (mutex) { return parent.containsAny(other); } + } + + @Override + public void addAll(ChunkSet other) { + synchronized (mutex) { parent.addAll(other); } + } + + @Override + public void removeAll(ChunkSet other) { + synchronized (mutex) { parent.removeAll(other); } + } + + @Override + public void retainAll(ChunkSet other) { + synchronized (mutex) { parent.retainAll(other); } + } + + @Override + public void clear() { + synchronized (mutex) { parent.clear(); } + } + + @Override + public boolean containsAll(Iterable other) { + synchronized (mutex) { return parent.containsAll(other); } + } + + @Override + public boolean containsAny(Iterable other) { + synchronized (mutex) { return parent.containsAny(other); } + } + + @Override + public void addAll(Iterable other) { + synchronized (mutex) { parent.addAll(other); } + } + + @Override + public void removeAll(Iterable other) { + synchronized (mutex) { parent.removeAll(other); } + } + + @Override + public void retainAll(Iterable other) { + synchronized (mutex) { parent.retainAll(other); } + } + + @Override + public void removeIf(Predicate condition) { + synchronized (mutex) { parent.removeIf(condition); } + } + + @Override + public void retainIf(Predicate condition) { + synchronized (mutex) { parent.retainIf(condition); } + } + + @Override + public boolean containsAllChunks(Iterable> chunks) { + synchronized (mutex) { return parent.containsAllChunks(chunks); } + } + + @Override + public boolean containsAnyChunks(Iterable> chunks) { + synchronized (mutex) { return parent.containsAnyChunks(chunks); } + } + + @Override + public void addAllChunks(Iterable> chunks) { + synchronized (mutex) { parent.addAllChunks(chunks); } + } + + @Override + public void removeAllChunks(Iterable> chunks) { + synchronized (mutex) { parent.removeAllChunks(chunks); } + } + } - - private ChunkSets() {} } diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/GenericChunk.java b/src/main/java/ru/windcorp/progressia/common/world/generic/GenericChunk.java index 8dc3cf8..45a6ad0 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/generic/GenericChunk.java +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/GenericChunk.java @@ -55,7 +55,7 @@ public interface GenericChunk< } default void forEachBiC(Consumer action) { - VectorUtil.forEachVectorInCuboid( + VectorUtil.iterateCuboid( 0, 0, 0, BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK, action @@ -63,7 +63,7 @@ public interface GenericChunk< } default void forEachBiW(Consumer action) { - VectorUtil.forEachVectorInCuboid( + VectorUtil.iterateCuboid( Coordinates.getInWorld(getX(), 0), Coordinates.getInWorld(getY(), 0), Coordinates.getInWorld(getZ(), 0), diff --git a/src/main/java/ru/windcorp/progressia/server/world/TickAndUpdateUtil.java b/src/main/java/ru/windcorp/progressia/server/world/TickAndUpdateUtil.java index f260675..a38ccd7 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/TickAndUpdateUtil.java +++ b/src/main/java/ru/windcorp/progressia/server/world/TickAndUpdateUtil.java @@ -112,49 +112,6 @@ public class TickAndUpdateUtil { tickEntity(EntityLogicRegistry.getInstance().get(data.getId()), data, TickContextMutable.start().withServer(server).build()); } -// public static BlockTickContext getBlockTickContext( -// Server server, -// Vec3i blockInWorld -// ) { -// MutableBlockTickContext result = new MutableBlockTickContext(); -// result.init(server, blockInWorld); -// return result; -// } -// -// public static TileTickContext getTileTickContext( -// Server server, -// Vec3i blockInWorld, -// BlockFace face, -// int layer -// ) { -// MutableTileTickContext result = new MutableTileTickContext(); -// result.init(server, blockInWorld, face, layer); -// return result; -// } -// -// public static TileTickContext getTileTickContext( -// Server server, -// TileDataStack stack, -// int index -// ) { -// MutableTileTickContext result = new MutableTileTickContext(); -// result.init(server, stack, index); -// return result; -// } -// -// public static TileTickContext getTileTickContext( -// Server server, -// TileReference ref -// ) { -// MutableTileTickContext result = new MutableTileTickContext(); -// result.init(server, ref); -// return result; -// } -// -// public static TickContext getTickContext(Server server) { -// return getBlockTickContext(server, null); -// } - private TickAndUpdateUtil() {} } diff --git a/src/main/java/ru/windcorp/progressia/test/LayerTestGUI.java b/src/main/java/ru/windcorp/progressia/test/LayerTestGUI.java index 94d80a1..0395f35 100755 --- a/src/main/java/ru/windcorp/progressia/test/LayerTestGUI.java +++ b/src/main/java/ru/windcorp/progressia/test/LayerTestGUI.java @@ -73,6 +73,12 @@ public class LayerTestGUI extends GUILayer { 128 )); + panel.addChild(new DynamicLabel( + "ChunkUpdatesDisplay", new Font().withColor(0x37A3E6).deriveShadow(), + () -> "Pending updates: " + Integer.toString(ClientState.getInstance().getWorld().getPendingChunkUpdates()), + 128 + )); + panel.getChildren().forEach(c -> { if (c instanceof Label) { labels.add((Label) c); diff --git a/src/main/java/ru/windcorp/progressia/test/TestPlayerControls.java b/src/main/java/ru/windcorp/progressia/test/TestPlayerControls.java index d00ca09..8aea90f 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestPlayerControls.java +++ b/src/main/java/ru/windcorp/progressia/test/TestPlayerControls.java @@ -29,8 +29,8 @@ public class TestPlayerControls { private TestPlayerControls() {} - private static final double MODE_SWITCH_MAX_DELAY = 100 * Units.MILLISECONDS; - private static final double MIN_JUMP_DELAY = 200 * Units.MILLISECONDS; + private static final double MODE_SWITCH_MAX_DELAY = 300 * Units.MILLISECONDS; + private static final double MIN_JUMP_DELAY = 400 * Units.MILLISECONDS; // Horizontal and vertical max control speed when flying private static final float FLYING_SPEED = 6.0f * Units.METERS_PER_SECOND;