From d2ccf3c4795032ffb5e3dbcfed615a76f97f9b32 Mon Sep 17 00:00:00 2001 From: austin Date: Mon, 4 Nov 2024 13:51:04 -0500 Subject: [PATCH] rewrite client side chunks + transvoxel integration --- .gitignore | 2 +- buildNumber.properties | 4 +- .../generation/chunkgenooptimizations.md | 4 + .../generation/worldgenerationindex.md | 3 +- docs/src/progress/renderertodo.md | 16 + pom.xml | 43 +- .../client/fluid/cells/FluidCellManager.java | 44 +- .../foliagemanager/ClientFoliageManager.java | 8 +- .../client/foliagemanager/FoliageCell.java | 2 +- .../client/foliagemanager/FoliageChunk.java | 60 +- .../client/scene/ClientWorldData.java | 14 +- .../client/sim/ClientSimulation.java | 6 +- .../client/terrain/cache/ChunkData.java | 33 + .../terrain/cache/ClientTerrainCache.java | 52 +- .../terrain/cells/ClientDrawCellManager.java | 690 ++++++++++++++++++ .../client/terrain/cells/DrawCell.java | 504 +++++++++---- .../client/terrain/cells/DrawCellManager.java | 8 +- .../terrain/manager/ClientTerrainManager.java | 53 +- .../client/ui/menu/WindowUtils.java | 15 + .../ui/menu/debug/ImGuiChunkMonitor.java | 87 +++ .../client/ui/menu/debug/ImGuiTestGen.java | 7 +- .../ui/menu/debug/ImGuiWindowMacros.java | 5 + .../collision/CollisionEngine.java | 4 +- .../collision/CollisionWorldData.java | 2 +- .../java/electrosphere/engine/Globals.java | 7 +- src/main/java/electrosphere/engine/Main.java | 9 +- .../engine/loadingthreads/ClientLoading.java | 57 +- .../engine/loadingthreads/LoadingUtils.java | 44 +- .../electrosphere/engine/signal/Signal.java | 8 + .../engine/threads/ThreadManager.java | 15 + .../entity/EntityCreationUtils.java | 3 - .../electrosphere/entity/scene/Scene.java | 9 + .../entity/types/terrain/TerrainChunk.java | 51 +- .../net/client/ClientNetworking.java | 56 +- .../net/client/MessageProtocol.java | 2 + .../net/client/protocol/TerrainProtocol.java | 9 +- .../net/parser/net/message/AuthMessage.java | 61 +- .../parser/net/message/CharacterMessage.java | 79 +- .../net/parser/net/message/CombatMessage.java | 103 ++- .../net/parser/net/message/EntityMessage.java | 292 +++++++- .../parser/net/message/InventoryMessage.java | 157 +++- .../net/parser/net/message/LoreMessage.java | 43 +- .../parser/net/message/NetworkMessage.java | 64 +- .../net/parser/net/message/PlayerMessage.java | 62 +- .../net/parser/net/message/ServerMessage.java | 40 +- .../net/message/SynchronizationMessage.java | 148 +++- .../parser/net/message/TerrainMessage.java | 239 +++++- .../net/parser/net/message/TypeBytes.java | 60 +- .../parser/net/raw/CircularByteBuffer.java | 120 --- .../net/parser/net/raw/NetworkParser.java | 124 +++- .../net/parser/util/ByteStreamUtils.java | 2 +- .../net/server/MessageProtocol.java | 4 +- .../java/electrosphere/net/server/Server.java | 5 +- .../net/server/ServerConnectionHandler.java | 44 +- .../net/server/protocol/TerrainProtocol.java | 188 ++--- .../meshgen/TerrainChunkModelGeneration.java | 9 +- .../meshgen/TransvoxelModelGeneration.java | 175 +++-- .../renderer/ui/imgui/ImGuiLinePlot.java | 25 +- .../server/MainServerFunctions.java | 2 + .../datacell/GriddedDataCellManager.java | 2 +- .../physics/DataCellPhysicsManager.java | 3 +- .../server/terrain/diskmap/ChunkDiskMap.java | 22 +- .../TestGenerationChunkGenerator.java | 157 +++- .../generation/heightmap/HillsGen.java | 6 +- .../manager/ChunkGenerationThread.java | 123 ++++ .../terrain/manager/ServerChunkCache.java | 166 +++++ .../terrain/manager/ServerTerrainManager.java | 99 +-- .../util/ds/octree/WorldOctTree.java | 435 +++++++++++ .../cells/ClientDrawCellManagerTests.java | 51 ++ .../StateCleanupCheckerExtension.java | 1 + .../util/ds/octree/ChunkTreeTests.java | 11 + .../util/ds/octree/WorldOctTreeTests.java | 54 ++ template.json | 1 - 73 files changed, 4366 insertions(+), 747 deletions(-) create mode 100644 docs/src/architecture/generation/chunkgenooptimizations.md create mode 100644 src/main/java/electrosphere/client/terrain/cells/ClientDrawCellManager.java create mode 100644 src/main/java/electrosphere/client/ui/menu/debug/ImGuiChunkMonitor.java delete mode 100644 src/main/java/electrosphere/net/parser/net/raw/CircularByteBuffer.java create mode 100644 src/main/java/electrosphere/server/terrain/manager/ChunkGenerationThread.java create mode 100644 src/main/java/electrosphere/server/terrain/manager/ServerChunkCache.java create mode 100644 src/main/java/electrosphere/util/ds/octree/WorldOctTree.java create mode 100644 src/test/java/electrosphere/client/terrain/cells/ClientDrawCellManagerTests.java create mode 100644 src/test/java/electrosphere/util/ds/octree/WorldOctTreeTests.java diff --git a/.gitignore b/.gitignore index 9f2c3a17..b1e0ea27 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,7 @@ /nb-configuration.xml /Telephone-*.jar -/NetArranger-*.jar +/NetArranger*.jar /lwjglx-debug-*.jar /hs_err_pid* /replay_pid* diff --git a/buildNumber.properties b/buildNumber.properties index eaad655c..d231cc53 100644 --- a/buildNumber.properties +++ b/buildNumber.properties @@ -1,3 +1,3 @@ #maven.buildNumber.plugin properties file -#Mon Oct 28 16:07:42 EDT 2024 -buildNumber=364 +#Mon Nov 04 12:41:10 EST 2024 +buildNumber=375 diff --git a/docs/src/architecture/generation/chunkgenooptimizations.md b/docs/src/architecture/generation/chunkgenooptimizations.md new file mode 100644 index 00000000..5294dee0 --- /dev/null +++ b/docs/src/architecture/generation/chunkgenooptimizations.md @@ -0,0 +1,4 @@ +@page chunkgenoptimizations Chunk Generation Optimizations + +Strategies to try: + - Cache "is surface" "is sky" "is cave" at generator level, then looking x,z against cached values to find whether the newly requested chunk is surface or not \ No newline at end of file diff --git a/docs/src/architecture/generation/worldgenerationindex.md b/docs/src/architecture/generation/worldgenerationindex.md index b816c16a..60cb4fe9 100644 --- a/docs/src/architecture/generation/worldgenerationindex.md +++ b/docs/src/architecture/generation/worldgenerationindex.md @@ -4,4 +4,5 @@ - @subpage biomeselection - @subpage biomegenerationproblems - @subpage terraingenerationprocess -- @subpage voxelgenideas \ No newline at end of file +- @subpage voxelgenideas +- @subpage chunkgenoptimizations \ No newline at end of file diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index a0a81df3..0af83474 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -914,6 +914,22 @@ Update default resolution in config Fix main menu ui test Refactor math utils to spatial math utils to make room for more fundamental utils +(10/29/2024) +Begin drawcellmanager rewrite + +(10/30/2024) +Integrate transvoxel algorithm +Document NetArranger +Break out datastructures library + +(10/31/2024) +Fix some transvoxel bugs +Optimizations +Refactoring generator code + +(11/01/2024) +Optimizations +Fix transvoxel xnzn edge generation # TODO diff --git a/pom.xml b/pom.xml index c262b5ea..e0081515 100644 --- a/pom.xml +++ b/pom.xml @@ -305,7 +305,15 @@ io.github.studiorailgun MathUtils - 1.0.1 + 1.1.0 + + + + + + io.github.studiorailgun + DataStructures + 1.1.0 @@ -440,6 +448,24 @@ + + Run Net Arranger + generate-sources + + exec + + + java.exe + + -jar + NetArranger.jar + + + 0 + 1 + + + @@ -452,6 +478,20 @@ integration + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + + -javaagent:./lwjglx-debug-1.0.0.jar=t;o=trace.log + 0 + + + + fast,unit,integration @@ -471,6 +511,7 @@ -javaagent:./lwjglx-debug-1.0.0.jar=t;o=trace.log + 0 diff --git a/src/main/java/electrosphere/client/fluid/cells/FluidCellManager.java b/src/main/java/electrosphere/client/fluid/cells/FluidCellManager.java index 2224d964..ef4556ba 100644 --- a/src/main/java/electrosphere/client/fluid/cells/FluidCellManager.java +++ b/src/main/java/electrosphere/client/fluid/cells/FluidCellManager.java @@ -14,6 +14,7 @@ import electrosphere.engine.Globals; import electrosphere.entity.EntityUtils; import electrosphere.net.parser.net.message.TerrainMessage; import electrosphere.renderer.shader.ShaderProgram; +import electrosphere.server.terrain.manager.ServerTerrainChunk; /** * @@ -78,7 +79,7 @@ public class FluidCellManager { */ public FluidCellManager(ClientTerrainManager clientTerrainManager, int discreteX, int discreteY, int discreteZ){ if(Globals.clientWorldData != null){ - worldBoundDiscreteMax = (int)(Globals.clientWorldData.getWorldBoundMin().x / Globals.clientWorldData.getDynamicInterpolationRatio() * 1.0f); + worldBoundDiscreteMax = (int)(Globals.clientWorldData.getWorldBoundMin().x / ServerTerrainChunk.CHUNK_DIMENSION * 1.0f); } cells = new HashSet(); hasNotRequested = new HashSet(); @@ -209,24 +210,55 @@ public class FluidCellManager { } } - + /** + * Checks if the manager has not requested a cell yet + * @return true if a cell has not been requested yet, false otherwise + */ public boolean containsUnrequestedCell(){ return hasNotRequested.size() > 0; } + + /** + * Gets the number of unrequested cells + * @return The number of unrequested cells + */ + public int getUnrequestedSize(){ + return hasNotRequested.size(); + } + /** + * Checks if the manager has a cell that is not drawable + * @return true if there is an undrawable cell, false otherwise + */ public boolean containsUndrawableCell(){ return undrawable.size() > 0; } + + /** + * Gets the number of undrawable cells + * @return The number of undrawable cells + */ + public int getUndrawableSize(){ + return undrawable.size(); + } + /** + * Checks if the manager has a cell that is updateable + * @return true if there is an updateable cell, false otherwise + */ public boolean containsUpdateableCell(){ return updateable.size() > 0; } - + /** + * Transforms real space into cell space + * @param input The real coordinate + * @return The cell coordinate + */ public int transformRealSpaceToCellSpace(double input){ - return (int)(input / Globals.clientWorldData.getDynamicInterpolationRatio()); + return (int)(input / ServerTerrainChunk.CHUNK_DIMENSION); } /** @@ -321,9 +353,10 @@ public class FluidCellManager { * Updates cells that need updating in this manager */ public void update(){ + Globals.profiler.beginCpuSample("FluidCellManager.update"); calculateDeltas(); if(update){ - if(containsUnrequestedCell() && !containsUndrawableCell()){ + if(containsUnrequestedCell()){ updateUnrequestedCell(); } else if(containsUndrawableCell()){ makeCellDrawable(); @@ -331,6 +364,7 @@ public class FluidCellManager { updateCellModel(); } } + Globals.profiler.endCpuSample(); } /** diff --git a/src/main/java/electrosphere/client/foliagemanager/ClientFoliageManager.java b/src/main/java/electrosphere/client/foliagemanager/ClientFoliageManager.java index e1824ed7..50b16afc 100644 --- a/src/main/java/electrosphere/client/foliagemanager/ClientFoliageManager.java +++ b/src/main/java/electrosphere/client/foliagemanager/ClientFoliageManager.java @@ -83,6 +83,7 @@ public class ClientFoliageManager { * @param worldPos The world position */ private void updatePosition(Vector3i worldPos){ + Globals.profiler.beginCpuSample("ClientFoliageManager.updatePosition"); FoliageChunk foundChunk = null; for(FoliageChunk chunk : chunkUpdateCache){ if(chunk.getWorldPos().equals(worldPos)){ @@ -98,7 +99,8 @@ public class ClientFoliageManager { } else { chunkUpdateCache.remove(foundChunk); } - foundChunk.updateCells(); + foundChunk.updateCells(false); + Globals.profiler.endCpuSample(); } /** @@ -130,7 +132,7 @@ public class ClientFoliageManager { public boolean dependenciesAreReady(){ return Globals.clientWorldData != null && - Globals.drawCellManager != null && + Globals.clientDrawCellManager != null && Globals.playerEntity != null ; } @@ -142,7 +144,7 @@ public class ClientFoliageManager { public void evaluateChunk(Vector3i worldPos){ for(FoliageChunk chunk : chunkUpdateCache){ if(chunk.getWorldPos().equals(worldPos)){ - chunk.updateCells(); + chunk.updateCells(true); break; } } diff --git a/src/main/java/electrosphere/client/foliagemanager/FoliageCell.java b/src/main/java/electrosphere/client/foliagemanager/FoliageCell.java index ff065454..2205ad8d 100644 --- a/src/main/java/electrosphere/client/foliagemanager/FoliageCell.java +++ b/src/main/java/electrosphere/client/foliagemanager/FoliageCell.java @@ -212,7 +212,7 @@ public class FoliageCell { if(voxelPosition.y + 1 >= ServerTerrainChunk.CHUNK_DIMENSION){ return; } - if(!Globals.drawCellManager.generatedPhysics(worldPosition)){ + if(!Globals.clientDrawCellManager.isFullLOD(worldPosition)){ return; } List foliageTypesSupported = Globals.gameConfigCurrent.getVoxelData().getTypeFromId(data.getType(voxelPosition)).getAmbientFoliage(); diff --git a/src/main/java/electrosphere/client/foliagemanager/FoliageChunk.java b/src/main/java/electrosphere/client/foliagemanager/FoliageChunk.java index 16c829b0..67979564 100644 --- a/src/main/java/electrosphere/client/foliagemanager/FoliageChunk.java +++ b/src/main/java/electrosphere/client/foliagemanager/FoliageChunk.java @@ -34,6 +34,11 @@ public class FoliageChunk { */ static final double HALF_RES_DIST = 30; + /** + * Tracks whether this chunk contains a foliage voxel or not + */ + boolean containsFoliageVoxel = false; + /** * The octree holding all the chunks to evaluate */ @@ -75,27 +80,56 @@ public class FoliageChunk { this.currentChunkData = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos); // //evaluate top cells if chunk above this one exists this.aboveChunkData = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(worldPos).add(0,1,0)); - this.updateCells(); + this.updateCells(true); Globals.profiler.endCpuSample(); } /** * Updates all cells in the chunk + * @param force true if should ignore cache, false otherwise */ - public void updateCells(){ + public void updateCells(boolean force){ Globals.profiler.beginCpuSample("FoliageChunk.updateCells"); - Vector3d playerPos = EntityUtils.getPosition(Globals.playerEntity); - //the sets to iterate through - boolean updated = true; - int attempts = 0; - while(updated && attempts < 3){ - ChunkTreeNode rootNode = this.chunkTree.getRoot(); - updated = this.recursivelyUpdateCells(rootNode, playerPos); - attempts++; + //re-evaluate whether contains foliage voxel or not + if(force){ + this.containsFoliageVoxel = checkContainsFoliageVoxel(); + } + if(force || containsFoliageVoxel){ + Vector3d playerPos = EntityUtils.getPosition(Globals.playerEntity); + //the sets to iterate through + boolean updated = true; + int attempts = 0; + while(updated && attempts < 3){ + ChunkTreeNode rootNode = this.chunkTree.getRoot(); + updated = this.recursivelyUpdateCells(rootNode, playerPos); + attempts++; + } } Globals.profiler.endCpuSample(); } + /** + * Checks if the chunk contains a foliage voxel or not + * @return true if contains foliage voxel, false otherwise + */ + private boolean checkContainsFoliageVoxel(){ + ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(this.getWorldPos()); + if(data == null){ + return false; + } + for(int x = 0; x < ChunkData.CHUNK_SIZE; x++){ + for(int y = 0; y < ChunkData.CHUNK_SIZE; y++){ + for(int z = 0; z < ChunkData.CHUNK_SIZE; z++){ + List foliageTypesSupported = Globals.gameConfigCurrent.getVoxelData().getTypeFromId(data.getType(new Vector3i(x,y,z))).getAmbientFoliage(); + if(foliageTypesSupported != null && foliageTypesSupported.size() > 0){ + return true; + } + } + } + } + return false; + } + /** * Recursively update child nodes * @param node The root node @@ -104,6 +138,7 @@ public class FoliageChunk { private boolean recursivelyUpdateCells(ChunkTreeNode node, Vector3d playerPos){ boolean updated = false; if(this.shouldSplit(playerPos, node)){ + Globals.profiler.beginCpuSample("FoliageChunk.split"); //perform op ChunkTreeNode container = chunkTree.split(node); @@ -119,8 +154,10 @@ public class FoliageChunk { ); child.convertToLeaf(new FoliageCell(worldPos, child.getMinBound(), realPos, 5 - child.getLevel())); }); + Globals.profiler.endCpuSample(); updated = true; } else if(this.shouldJoin(playerPos, node)) { + // Globals.profiler.beginCpuSample("FoliageChunk.join"); //perform op ChunkTreeNode newLeaf = chunkTree.join(node); @@ -129,9 +166,12 @@ public class FoliageChunk { //do creations newLeaf.convertToLeaf(new FoliageCell(worldPos, newLeaf.getMinBound(), realPos, 5 - newLeaf.getLevel())); + // Globals.profiler.endCpuSample(); updated = true; } else if(shouldGenerate(playerPos, node)){ + // Globals.profiler.beginCpuSample("FoliageChunk.generate"); node.getData().generate(); + // Globals.profiler.endCpuSample(); updated = true; } else if(!node.isLeaf()){ List> children = new LinkedList>(node.getChildren()); diff --git a/src/main/java/electrosphere/client/scene/ClientWorldData.java b/src/main/java/electrosphere/client/scene/ClientWorldData.java index 3b579b78..3312ea83 100644 --- a/src/main/java/electrosphere/client/scene/ClientWorldData.java +++ b/src/main/java/electrosphere/client/scene/ClientWorldData.java @@ -4,6 +4,8 @@ import org.joml.Vector3d; import org.joml.Vector3f; import org.joml.Vector3i; +import electrosphere.server.terrain.manager.ServerTerrainChunk; + /** * Client's data on the world */ @@ -31,8 +33,6 @@ public class ClientWorldData { Vector3f worldMinPoint; Vector3f worldMaxPoint; - int dynamicInterpolationRatio; - float randomDampener; @@ -42,13 +42,11 @@ public class ClientWorldData { public ClientWorldData( Vector3f worldMinPoint, Vector3f worldMaxPoint, - int dynamicInterpolationRatio, float randomDampener, int worldDiscreteSize ) { this.worldMinPoint = worldMinPoint; this.worldMaxPoint = worldMaxPoint; - this.dynamicInterpolationRatio = dynamicInterpolationRatio; this.randomDampener = randomDampener; this.worldDiscreteSize = worldDiscreteSize; } @@ -64,10 +62,6 @@ public class ClientWorldData { return worldMaxPoint; } - public int getDynamicInterpolationRatio() { - return dynamicInterpolationRatio; - } - public float getRandomDampener() { return randomDampener; } @@ -78,11 +72,11 @@ public class ClientWorldData { public int convertRealToChunkSpace(double real){ - return (int)Math.floor(real / dynamicInterpolationRatio); + return (int)Math.floor(real / ServerTerrainChunk.CHUNK_DIMENSION); } public float convertChunkToRealSpace(int chunk){ - return chunk * dynamicInterpolationRatio; + return chunk * ServerTerrainChunk.CHUNK_DIMENSION; } public int convertRealToWorld(double real){ diff --git a/src/main/java/electrosphere/client/sim/ClientSimulation.java b/src/main/java/electrosphere/client/sim/ClientSimulation.java index b7d18bd7..88d1ee49 100644 --- a/src/main/java/electrosphere/client/sim/ClientSimulation.java +++ b/src/main/java/electrosphere/client/sim/ClientSimulation.java @@ -166,7 +166,7 @@ public class ClientSimulation { * Loads terrain that is in queue */ public void loadTerrain(){ - Globals.profiler.beginCpuSample("load terrain"); + Globals.profiler.beginCpuSample("ClientSimulation.loadTerrain"); if(Globals.clientTerrainManager != null){ Globals.clientTerrainManager.handleMessages(); updateTerrainCellManager(); @@ -185,9 +185,9 @@ public class ClientSimulation { /// /// C L I E N T C E L L M A N A G E R /// - if(Globals.drawCellManager != null && Globals.clientWorldData != null){ + if(Globals.clientDrawCellManager != null && Globals.clientWorldData != null){ //Cell manager do your things - Globals.drawCellManager.update(); + Globals.clientDrawCellManager.update(); } } diff --git a/src/main/java/electrosphere/client/terrain/cache/ChunkData.java b/src/main/java/electrosphere/client/terrain/cache/ChunkData.java index 59bd0447..4036d6a0 100644 --- a/src/main/java/electrosphere/client/terrain/cache/ChunkData.java +++ b/src/main/java/electrosphere/client/terrain/cache/ChunkData.java @@ -26,6 +26,31 @@ public class ChunkData { //Used in DrawCell to keep track of which positions to invalidate Set modifiedSinceLastGeneration = new HashSet(); + /** + * The word x coordinate + */ + int worldX; + /** + * The word y coordinate + */ + int worldY; + /** + * The word z coordinate + */ + int worldZ; + + /** + * Creates a chunk data + * @param worldX The word x coordinate + * @param worldY The word y coordinate + * @param worldZ The word z coordinate + */ + public ChunkData(int worldX, int worldY, int worldZ){ + this.worldX = worldX; + this.worldY = worldY; + this.worldZ = worldZ; + } + /** * Gets the voxel type array in this container @@ -178,5 +203,13 @@ public class ChunkData { return position.x + "_" + position.y + "_" + position.z; } + /** + * Gets the world position of the chunk data + * @return The world position + */ + public Vector3i getWorldPos(){ + return new Vector3i(worldX,worldY,worldZ); + } + } diff --git a/src/main/java/electrosphere/client/terrain/cache/ClientTerrainCache.java b/src/main/java/electrosphere/client/terrain/cache/ClientTerrainCache.java index 4bb90b52..90f1dcf4 100644 --- a/src/main/java/electrosphere/client/terrain/cache/ClientTerrainCache.java +++ b/src/main/java/electrosphere/client/terrain/cache/ClientTerrainCache.java @@ -8,6 +8,8 @@ import java.util.concurrent.CopyOnWriteArrayList; import org.joml.Vector3i; +import io.github.studiorailgun.HashUtils; + /** * Acts as a cache in front of terrain model to streamline receiving chunks @@ -17,16 +19,21 @@ public class ClientTerrainCache { //cache capacity int cacheSize; //the map of chunk key -> chunk data - Map cacheMap = new ConcurrentHashMap(); + Map cacheMap = new ConcurrentHashMap(); //the list of keys in the cache - List cacheList = new CopyOnWriteArrayList(); + List cacheList = new CopyOnWriteArrayList(); //A map of chunk to its world position Map chunkPositionMap = new ConcurrentHashMap(); + + /** + * The map tracking chunks that have been requested + */ + Map requestedChunks = new ConcurrentHashMap(); /** * Constructor * @param cacheSize The capacity of the cache - */ + */ public ClientTerrainCache(int cacheSize){ this.cacheSize = cacheSize; } @@ -42,8 +49,10 @@ public class ClientTerrainCache { cacheMap.put(getKey(worldX,worldY,worldZ),chunkData); chunkPositionMap.put(chunkData,new Vector3i(worldX,worldY,worldZ)); while(cacheList.size() > cacheSize){ - String currentChunk = cacheList.remove(0); - cacheMap.remove(currentChunk); + Long currentChunk = cacheList.remove(0); + ChunkData data = cacheMap.remove(currentChunk); + Vector3i worldPos = data.getWorldPos(); + requestedChunks.remove(getKey(worldPos.x,worldPos.y,worldPos.z)); } } @@ -64,8 +73,8 @@ public class ClientTerrainCache { * @param worldZ The z world position * @return The cache key */ - public String getKey(int worldX, int worldY, int worldZ){ - return worldX + "_" + worldY + "_" + worldZ; + public long getKey(int worldX, int worldY, int worldZ){ + return HashUtils.cantorHash(worldX, worldY, worldZ); } /** @@ -110,5 +119,34 @@ public class ClientTerrainCache { public Vector3i getChunkPosition(ChunkData chunk){ return chunkPositionMap.get(chunk); } + + /** + * Gets the number of cells that have been requested + * @return The number of cells that have been requested + */ + public int getRequestedCellCount(){ + return this.requestedChunks.size(); + } + + /** + * Checks if a chunk has been requested or not + * @param worldX The x coordinate in the world + * @param worldY The y coordinate in the world + * @param worldZ The z coordinate in the world + * @return true if it has been requested, false otherwise + */ + public boolean hasRequested(int worldX, int worldY, int worldZ){ + return this.requestedChunks.containsKey(getKey(worldX, worldY, worldZ)); + } + + /** + * Marks a chunk as requested + * @param worldX The x coordinate in the world + * @param worldY The y coordinate in the world + * @param worldZ The z coordinate in the world + */ + public void markAsRequested(int worldX, int worldY, int worldZ){ + this.requestedChunks.put(getKey(worldX, worldY, worldZ),true); + } } diff --git a/src/main/java/electrosphere/client/terrain/cells/ClientDrawCellManager.java b/src/main/java/electrosphere/client/terrain/cells/ClientDrawCellManager.java new file mode 100644 index 00000000..f41d2212 --- /dev/null +++ b/src/main/java/electrosphere/client/terrain/cells/ClientDrawCellManager.java @@ -0,0 +1,690 @@ +package electrosphere.client.terrain.cells; + +import java.util.LinkedList; +import java.util.List; + +import org.joml.Vector3d; +import org.joml.Vector3i; + +import electrosphere.client.terrain.cells.DrawCell.DrawCellFace; +import electrosphere.engine.Globals; +import electrosphere.entity.EntityUtils; +import electrosphere.logger.LoggerInterface; +import electrosphere.server.terrain.manager.ServerTerrainChunk; +import electrosphere.util.ds.octree.WorldOctTree; +import electrosphere.util.ds.octree.WorldOctTree.FloatingChunkTreeNode; +import electrosphere.util.math.GeomUtils; + +/** + * Manages draw cells on the client + */ +public class ClientDrawCellManager { + + /** + * Number of times to try updating per frame. Lower this to reduce lag but slow down terrain mesh generation. + */ + static final int UPDATE_ATTEMPTS_PER_FRAME = 3; + + /** + * The distance to draw at full resolution + */ + public static final double FULL_RES_DIST = 5 * ServerTerrainChunk.CHUNK_DIMENSION; + + /** + * The distance for half resolution + */ + public static final double HALF_RES_DIST = 15 * ServerTerrainChunk.CHUNK_DIMENSION; + + /** + * The octree holding all the chunks to evaluate + */ + WorldOctTree chunkTree; + + /** + * Tracks whether the cell manager updated last frame or not + */ + boolean updatedLastFrame = true; + + /** + * Controls whether the client draw cell manager should update or not + */ + boolean shouldUpdate = true; + + /** + * The voxel texture atlas + */ + VoxelTextureAtlas textureAtlas; + + /** + * The dimensions of the world + */ + int worldDim = 0; + + /** + * Tracks the number of currently valid cells (ie didn't require an update this frame) + */ + int validCellCount = 0; + + /** + * The number of maximum resolution chunks + */ + int maxResCount = 0; + + /** + * The number of half resolution chunks + */ + int halfResCount = 0; + + /** + * The number of generated chunks + */ + int generated = 0; + + /** + * Constructor + * @param voxelTextureAtlas The voxel texture atlas + * @param worldDim The size of the world in chunks + */ + public ClientDrawCellManager(VoxelTextureAtlas voxelTextureAtlas, int worldDim){ + this.chunkTree = new WorldOctTree(new Vector3i(0,0,0), new Vector3i(worldDim, worldDim, worldDim)); + this.worldDim = worldDim; + this.textureAtlas = voxelTextureAtlas; + } + + /** + * Updates all cells in the chunk + */ + public void update(){ + Globals.profiler.beginCpuSample("ClientDrawCellManager.updateCells"); + if(shouldUpdate && Globals.playerEntity != null){ + Vector3d playerPos = EntityUtils.getPosition(Globals.playerEntity); + //the sets to iterate through + updatedLastFrame = true; + int attempts = 0; + validCellCount = 0; + while(updatedLastFrame && attempts < UPDATE_ATTEMPTS_PER_FRAME){ + FloatingChunkTreeNode rootNode = this.chunkTree.getRoot(); + updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerPos); + attempts++; + } + } + Globals.profiler.endCpuSample(); + } + + /** + * Recursively update child nodes + * @param node The root node + * @param playerPos The player's position + * @return true if there is work remaining to be done, false otherwise + */ + private boolean recursivelyUpdateCells(FloatingChunkTreeNode node, Vector3d playerPos){ + Vector3d playerRealPos = EntityUtils.getPosition(Globals.playerEntity); + boolean updated = false; + if(this.shouldSplit(playerPos, node)){ + Globals.profiler.beginCpuSample("ClientDrawCellManager.split"); + //perform op + FloatingChunkTreeNode container = chunkTree.split(node); + + //do deletions + this.recursivelyDestroy(node); + + //do creations + container.getChildren().forEach(child -> { + Vector3i cellWorldPos = new Vector3i( + child.getMinBound().x, + child.getMinBound().y, + child.getMinBound().z + ); + DrawCell drawCell = DrawCell.generateTerrainCell(cellWorldPos); + child.convertToLeaf(drawCell); + }); + + //update neighbors + this.conditionalUpdateAdjacentNodes(container, container.getChildren().get(0).getLevel()); + + Globals.profiler.endCpuSample(); + updated = true; + } else if(this.shouldJoin(playerPos, node)) { + Globals.profiler.beginCpuSample("ClientDrawCellManager.join"); + //perform op + FloatingChunkTreeNode newLeaf = chunkTree.join(node); + + //do deletions + this.recursivelyDestroy(node); + + //do creations + DrawCell drawCell = DrawCell.generateTerrainCell(node.getMinBound()); + newLeaf.convertToLeaf(drawCell); + + //update neighbors + this.conditionalUpdateAdjacentNodes(newLeaf, newLeaf.getLevel()); + + Globals.profiler.endCpuSample(); + updated = true; + } else if(shouldRequest(playerPos, node)){ + Globals.profiler.beginCpuSample("ClientDrawCellManager.request"); + DrawCell cell = node.getData(); + this.requestChunks(node); + cell.setHasRequested(true); + Globals.profiler.endCpuSample(); + updated = true; + } else if(shouldGenerate(playerPos, node)){ + Globals.profiler.beginCpuSample("ClientDrawCellManager.generate"); + int lodLevel = this.getLODLevel(playerRealPos, node); + List highResFaces = this.solveHighResFace(node); + if(containsDataToGenerate(node,highResFaces)){ + node.getData().generateDrawableEntity(textureAtlas, lodLevel, highResFaces); + } + Globals.profiler.endCpuSample(); + updated = true; + } else if(!node.isLeaf()){ + this.validCellCount++; + List> children = new LinkedList>(node.getChildren()); + for(FloatingChunkTreeNode child : children){ + boolean childUpdate = recursivelyUpdateCells(child, playerPos); + if(childUpdate == true){ + updated = true; + } + } + } + return updated; + } + + /** + * Gets the minimum distance from a node to a point + * @param pos the position to check against + * @param node the node + * @return the distance + */ + public double getMinDistance(Vector3d pos, FloatingChunkTreeNode node){ + Vector3i min = node.getMinBound(); + Vector3i max = node.getMaxBound(); + return GeomUtils.getMinDistanceAABB(pos, Globals.clientWorldData.convertWorldToRealSpace(min), Globals.clientWorldData.convertWorldToRealSpace(max)); + } + + /** + * Gets whether this should be split or not + * @param pos the player position + * @param node The node + * @return true if should split, false otherwise + */ + public boolean shouldSplit(Vector3d pos, FloatingChunkTreeNode node){ + //breaking out into dedicated function so can add case handling ie if we want + //to combine fullres nodes into larger nodes to conserve on draw calls + return + node.isLeaf() && + node.canSplit() && + ( + ( + node.getLevel() < this.chunkTree.getMaxLevel() - 1 && + this.getMinDistance(pos, node) <= HALF_RES_DIST + ) + || + ( + node.getLevel() < this.chunkTree.getMaxLevel() && + this.getMinDistance(pos, node) <= FULL_RES_DIST + ) + ) + ; + } + + /** + * Gets the LOD level of the draw cell + * @param pos The position of the cell + * @param node The node to consider + * @return -1 if outside of render range, -1 if the node is not a valid draw cell leaf, otherwise returns the LOD level + */ + private int getLODLevel(Vector3d pos, FloatingChunkTreeNode node){ + return this.chunkTree.getMaxLevel() - node.getLevel(); + } + + /** + * Solves which face (if any) is the high res face for a LOD chunk + * @param node The node for the chunk + * @return The face if there is a higher resolution face, null otherwise + */ + private List solveHighResFace(FloatingChunkTreeNode node){ + //don't bother to check if it's a full res chunk + if(node.getLevel() == this.chunkTree.getMaxLevel()){ + return null; + } + int lodMultiplitier = this.chunkTree.getMaxLevel() - node.getLevel() + 1; + int spacing = (int)Math.pow(2,lodMultiplitier); + List faces = new LinkedList(); + if(node.getMinBound().x - 1 >= 0){ + FloatingChunkTreeNode xNegNode = this.chunkTree.search(new Vector3i(node.getMinBound()).add(-1,1,1), false); + if(xNegNode != null && xNegNode.getLevel() > node.getLevel()){ + faces.add(DrawCellFace.X_NEGATIVE); + } + } + if(node.getMinBound().y - 1 >= 0){ + FloatingChunkTreeNode yNegNode = this.chunkTree.search(new Vector3i(node.getMinBound()).add(1,-1,1), false); + if(yNegNode != null && yNegNode.getLevel() > node.getLevel()){ + faces.add(DrawCellFace.Y_NEGATIVE); + } + } + if(node.getMinBound().z - 1 >= 0){ + FloatingChunkTreeNode zNegNode = this.chunkTree.search(new Vector3i(node.getMinBound()).add(1,1,-1), false); + if(zNegNode != null && zNegNode.getLevel() > node.getLevel()){ + faces.add(DrawCellFace.Z_NEGATIVE); + } + } + if(node.getMaxBound().x + spacing + 1 < this.worldDim){ + FloatingChunkTreeNode xPosNode = this.chunkTree.search(new Vector3i(node.getMinBound()).add(spacing + 1,1,1), false); + if(xPosNode != null && xPosNode.getLevel() > node.getLevel()){ + faces.add(DrawCellFace.X_POSITIVE); + } + } + if(node.getMaxBound().y + spacing + 1 < this.worldDim){ + FloatingChunkTreeNode yPosNode = this.chunkTree.search(new Vector3i(node.getMinBound()).add(1,spacing + 1,1), false); + if(yPosNode != null && yPosNode.getLevel() > node.getLevel()){ + faces.add(DrawCellFace.Y_POSITIVE); + } + } + if(node.getMaxBound().z + spacing + 1 < this.worldDim){ + FloatingChunkTreeNode zPosNode = this.chunkTree.search(new Vector3i(node.getMinBound()).add(1,1,spacing + 1), false); + if(zPosNode != null && zPosNode.getLevel() > node.getLevel()){ + faces.add(DrawCellFace.Z_POSITIVE); + } + } + if(faces.size() > 0){ + return faces; + } + return null; + } + + /** + * Conditionally updates all adjacent nodes if their level would require transition cells in the voxel rasterization + * @param node The node to search from adjacencies from + * @param level The level to check against + */ + private void conditionalUpdateAdjacentNodes(FloatingChunkTreeNode node, int level){ + //don't bother to check if it's a lowest-res chunk + if(this.chunkTree.getMaxLevel() - level > DrawCell.LOWEST_LOD){ + return; + } + if(node.getMinBound().x - 1 >= 0){ + FloatingChunkTreeNode xNegNode = this.chunkTree.search(new Vector3i(node.getMinBound()).add(-1,0,0), false); + if(xNegNode != null && xNegNode.getLevel() < level){ + xNegNode.getData().setHasGenerated(false); + } + } + if(node.getMinBound().y - 1 >= 0){ + FloatingChunkTreeNode yNegNode = this.chunkTree.search(new Vector3i(node.getMinBound()).add(0,-1,0), false); + if(yNegNode != null && yNegNode.getLevel() < level){ + yNegNode.getData().setHasGenerated(false); + } + } + if(node.getMinBound().z - 1 >= 0){ + FloatingChunkTreeNode zNegNode = this.chunkTree.search(new Vector3i(node.getMinBound()).add(0,0,-1), false); + if(zNegNode != null && zNegNode.getLevel() < level){ + zNegNode.getData().setHasGenerated(false); + } + } + if(node.getMaxBound().x + 1 < this.worldDim){ + FloatingChunkTreeNode xPosNode = this.chunkTree.search(new Vector3i(node.getMaxBound()).add(1,-1,-1), false); + if(xPosNode != null && xPosNode.getLevel() < level){ + xPosNode.getData().setHasGenerated(false); + } + } + if(node.getMaxBound().y + 1 < this.worldDim){ + FloatingChunkTreeNode yPosNode = this.chunkTree.search(new Vector3i(node.getMaxBound()).add(-1,1,-1), false); + if(yPosNode != null && yPosNode.getLevel() < level){ + yPosNode.getData().setHasGenerated(false); + } + } + if(node.getMaxBound().z + 1 < this.worldDim){ + FloatingChunkTreeNode zPosNode = this.chunkTree.search(new Vector3i(node.getMaxBound()).add(-1,-1,1), false); + if(zPosNode != null && zPosNode.getLevel() < level){ + zPosNode.getData().setHasGenerated(false); + } + } + } + + /** + * Gets whether this should be joined or not + * @param pos the player position + * @param node The node + * @return true if should be joined, false otherwise + */ + public boolean shouldJoin(Vector3d pos, FloatingChunkTreeNode node){ + //breaking out into dedicated function so can add case handling ie if we want + //to combine fullres nodes into larger nodes to conserve on draw calls + return + node.getLevel() > 0 && + !node.isLeaf() && + ( + ( + node.getLevel() == this.chunkTree.getMaxLevel() && + this.getMinDistance(pos, node) > FULL_RES_DIST + ) + || + ( + this.getMinDistance(pos, node) > HALF_RES_DIST + ) + ) + ; + } + + /** + * Checks if this cell should request chunk data + * @param pos the player's position + * @param node the node + * @return true if should request chunk data, false otherwise + */ + public boolean shouldRequest(Vector3d pos, FloatingChunkTreeNode node){ + return + node.isLeaf() && + node.getData() != null && + !node.getData().hasRequested() && + ( + ( + node.getLevel() == this.chunkTree.getMaxLevel() + // && + // this.getMinDistance(pos, node) <= FULL_RES_DIST + ) + || + ( + node.getLevel() == this.chunkTree.getMaxLevel() - 1 + && + this.getMinDistance(pos, node) <= HALF_RES_DIST + ) + ) + ; + } + + /** + * Checks if this cell should generate + * @param pos the player's position + * @param node the node + * @return true if should generate, false otherwise + */ + public boolean shouldGenerate(Vector3d pos, FloatingChunkTreeNode node){ + return + node.isLeaf() && + node.getData() != null && + !node.getData().hasGenerated() && + ( + ( + node.getLevel() == this.chunkTree.getMaxLevel() + // && + // this.getMinDistance(pos, node) <= FULL_RES_DIST + ) + || + ( + node.getLevel() == this.chunkTree.getMaxLevel() - 1 + && + this.getMinDistance(pos, node) <= HALF_RES_DIST + ) + ) + ; + } + + /** + * Checks if the node should have destroy called on it + * @param node The node + * @return true if should destroy, false otherwise + */ + public boolean shouldDestroy(FloatingChunkTreeNode node){ + return + node.getData() != null && + node.getData().getEntity() != null + ; + } + + /** + * Destroys the foliage chunk + */ + protected void destroy(){ + this.recursivelyDestroy(this.chunkTree.getRoot()); + } + + /** + * Recursively destroy a tree + * @param node The root of the tree + */ + private void recursivelyDestroy(FloatingChunkTreeNode node){ + if(node.getChildren().size() > 0){ + node.getChildren().forEach(child -> recursivelyDestroy(child)); + } + if(node.getData() != null){ + node.getData().destroy(); + } + } + + /** + * Checks if the cell manager made an update last frame or not + * @return true if an update occurred, false otherwise + */ + public boolean updatedLastFrame(){ + return this.updatedLastFrame; + } + + /** + * Checks if the position is within the full LOD range + * @param worldPos The world position + * @return true if within full LOD range, false otherwise + */ + public boolean isFullLOD(Vector3i worldPos){ + Vector3d playerRealPos = EntityUtils.getPosition(Globals.playerEntity); + Vector3d chunkMin = Globals.clientWorldData.convertWorldToRealSpace(worldPos); + Vector3d chunkMax = Globals.clientWorldData.convertWorldToRealSpace(new Vector3i(worldPos).add(1,1,1)); + return GeomUtils.getMinDistanceAABB(playerRealPos, chunkMin, chunkMax) <= FULL_RES_DIST; + } + + /** + * Evicts all cells + */ + public void evictAll(){ + this.chunkTree.clear(); + } + + + /** + * Marks a draw cell as updateable + * @param worldX The world x position + * @param worldY The world y position + * @param worldZ The world z position + */ + public void markUpdateable(float worldX, float worldY, float worldZ){ + throw new Error("Unimplemented"); + } + + /** + * Checks if the terrain cache has a chunk at a given world point + * @param worldX the x coordinate + * @param worldY the y coordinate + * @param worldZ the z coordinate + * @return true if the chunk data exists, false otherwise + */ + boolean containsChunkDataAtWorldPoint(int worldX, int worldY, int worldZ){ + if(Globals.clientTerrainManager != null){ + return Globals.clientTerrainManager.containsChunkDataAtWorldPoint(worldX,worldY,worldZ); + } + return true; + } + + /** + * Requests all chunks for a given draw cell + * @param cell The cell + */ + private void requestChunks(WorldOctTree.FloatingChunkTreeNode node){ + DrawCell cell = node.getData(); + int lodMultiplitier = this.chunkTree.getMaxLevel() - node.getLevel() + 1; + for(int i = 0; i < 2 * lodMultiplitier; i++){ + for(int j = 0; j < 2 * lodMultiplitier; j++){ + for(int k = 0; k < 2 * lodMultiplitier; k++){ + Vector3i posToCheck = new Vector3i(cell.getWorldPos()).add(i,j,k); + if( + posToCheck.x >= 0 && + posToCheck.x < Globals.clientWorldData.getWorldDiscreteSize() && + posToCheck.y >= 0 && + posToCheck.y < Globals.clientWorldData.getWorldDiscreteSize() && + posToCheck.z >= 0 && + posToCheck.z < Globals.clientWorldData.getWorldDiscreteSize() && + !Globals.clientTerrainManager.containsChunkDataAtWorldPoint(posToCheck.x, posToCheck.y, posToCheck.z) + ){ + //client should request chunk data from server for each chunk necessary to create the model + LoggerInterface.loggerNetworking.DEBUG("(Client) Send Request for terrain at " + posToCheck); + Globals.clientTerrainManager.requestChunk(posToCheck.x, posToCheck.y, posToCheck.z); + } + } + } + } + } + + /** + * Checks if all chunk data required to generate this draw cell is present + * @param node The node + * @param highResFace The higher resolution face of a not-full-resolution chunk. Null if the chunk is max resolution or there is no higher resolution face for the current chunk + * @return true if all data is available, false otherwise + */ + private boolean containsDataToGenerate(WorldOctTree.FloatingChunkTreeNode node, List highResFaces){ + DrawCell cell = node.getData(); + int lodMultiplitier = this.chunkTree.getMaxLevel() - node.getLevel() + 1; + for(int i = 0; i < 2 * lodMultiplitier; i++){ + for(int j = 0; j < 2 * lodMultiplitier; j++){ + for(int k = 0; k < 2 * lodMultiplitier; k++){ + Vector3i posToCheck = new Vector3i(cell.getWorldPos()).add(i,j,k); + if( + posToCheck.x >= 0 && + posToCheck.x < Globals.clientWorldData.getWorldDiscreteSize() && + posToCheck.y >= 0 && + posToCheck.y < Globals.clientWorldData.getWorldDiscreteSize() && + posToCheck.z >= 0 && + posToCheck.z < Globals.clientWorldData.getWorldDiscreteSize() && + !Globals.clientTerrainManager.containsChunkDataAtWorldPoint(posToCheck.x, posToCheck.y, posToCheck.z) + ){ + return false; + } + } + } + } + if(highResFaces != null){ + for(DrawCellFace highResFace : highResFaces){ + //x & y are in face-space + for(int x = 0; x < 2 * lodMultiplitier; x++){ + for(int y = 0; y < 2 * lodMultiplitier; y++){ + Vector3i posToCheck = null; + //implicitly performing transforms to adapt from face-space to world space + switch(highResFace){ + case X_POSITIVE: { + posToCheck = new Vector3i(cell.getWorldPos()).add(lodMultiplitier,x,y); + } break; + case X_NEGATIVE: { + posToCheck = new Vector3i(cell.getWorldPos()).add(-1,x,y); + } break; + case Y_POSITIVE: { + posToCheck = new Vector3i(cell.getWorldPos()).add(x,lodMultiplitier,y); + } break; + case Y_NEGATIVE: { + posToCheck = new Vector3i(cell.getWorldPos()).add(x,-1,y); + } break; + case Z_POSITIVE: { + posToCheck = new Vector3i(cell.getWorldPos()).add(x,y,lodMultiplitier); + } break; + case Z_NEGATIVE: { + posToCheck = new Vector3i(cell.getWorldPos()).add(x,y,-1); + } break; + } + if( + posToCheck.x >= 0 && + posToCheck.x < Globals.clientWorldData.getWorldDiscreteSize() && + posToCheck.y >= 0 && + posToCheck.y < Globals.clientWorldData.getWorldDiscreteSize() && + posToCheck.z >= 0 && + posToCheck.z < Globals.clientWorldData.getWorldDiscreteSize() && + !Globals.clientTerrainManager.containsChunkDataAtWorldPoint(posToCheck.x, posToCheck.y, posToCheck.z) + ){ + return false; + } + } + } + } + } + return true; + } + + /** + * Sets whether the draw cell manager should update or not + * @param shouldUpdate true if should update, false otherwise + */ + public void setShouldUpdate(boolean shouldUpdate){ + this.shouldUpdate = shouldUpdate; + } + + /** + * Gets whether the client draw cell manager should update or not + * @return true if should update, false otherwise + */ + public boolean getShouldUpdate(){ + return this.shouldUpdate; + } + + /** + * Gets the number of currently valid cells + * @return The number of currently valid cells + */ + public int getValidCellCount(){ + return validCellCount; + } + + /** + * Calculates the status of the draw cell manager + */ + public void updateStatus(){ + maxResCount = 0; + halfResCount = 0; + generated = 0; + this.recursivelyCalculateStatus(this.chunkTree.getRoot()); + } + + /** + * Recursively calculates the status of the manager + * @param node The root node + */ + private void recursivelyCalculateStatus(FloatingChunkTreeNode node){ + if(node.getLevel() == this.chunkTree.getMaxLevel() - 1){ + halfResCount++; + } + if(node.getLevel() == this.chunkTree.getMaxLevel()){ + maxResCount++; + } + if(node.getData() != null && node.getData().hasGenerated()){ + generated++; + } + if(node.getChildren() != null && node.getChildren().size() > 0){ + List> children = new LinkedList>(node.getChildren()); + for(FloatingChunkTreeNode child : children){ + recursivelyCalculateStatus(child); + } + } + } + + /** + * Gets The number of maximum resolution chunks + * @return The number of maximum resolution chunks + */ + public int getMaxResCount() { + return maxResCount; + } + + /** + * Gets The number of half resolution chunks + * @return The number of half resolution chunks + */ + public int getHalfResCount() { + return halfResCount; + } + + /** + * Gets The number of generated chunks + * @return + */ + public int getGenerated() { + return generated; + } + + + +} diff --git a/src/main/java/electrosphere/client/terrain/cells/DrawCell.java b/src/main/java/electrosphere/client/terrain/cells/DrawCell.java index d9221982..96eed5b9 100644 --- a/src/main/java/electrosphere/client/terrain/cells/DrawCell.java +++ b/src/main/java/electrosphere/client/terrain/cells/DrawCell.java @@ -1,5 +1,7 @@ package electrosphere.client.terrain.cells; +import java.util.List; + import org.joml.Quaterniond; import org.joml.Vector3d; import org.joml.Vector3i; @@ -10,6 +12,7 @@ import electrosphere.engine.Globals; import electrosphere.entity.ClientEntityUtils; import electrosphere.entity.Entity; import electrosphere.entity.types.terrain.TerrainChunk; +import electrosphere.renderer.meshgen.TransvoxelModelGeneration; import electrosphere.renderer.meshgen.TransvoxelModelGeneration.TransvoxelChunkData; /** @@ -39,15 +42,57 @@ public class DrawCell { float[][][] weights = new float[ChunkData.CHUNK_DATA_GENERATOR_SIZE][ChunkData.CHUNK_DATA_GENERATOR_SIZE][ChunkData.CHUNK_DATA_GENERATOR_SIZE]; int[][][] types = new int[ChunkData.CHUNK_DATA_GENERATOR_SIZE][ChunkData.CHUNK_DATA_GENERATOR_SIZE][ChunkData.CHUNK_DATA_GENERATOR_SIZE]; + /** + * An invalid LOD level + */ + public static final int INVALID_LOD_LEVEL = -1; - //the maximum detail LOD level + /** + * The maximum detail LOD level + */ public static final int FULL_DETAIL_LOD = 0; + + /** + * The half detail lod level + */ + public static final int HALF_DETAIL_LOD = 1; + + /** + * The lowest available LOD + */ + public static final int LOWEST_LOD = HALF_DETAIL_LOD; + + /** + * The lod level of this draw cell + */ + int lodLevel = FULL_DETAIL_LOD; + + /** + * Tracks whether the draw cell has requested its chunk data or not + */ + boolean hasRequested = false; + + /** + * Tracks whether the draw cell has generated its entity or not + */ + boolean hasGenerated = false; + + + /** + * The initial voxel type encountered + */ + int initialVoxelType = -1; + + /** + * True if there are multiple types of voxels in this chunk + */ + boolean multiVoxelType = false; /** * Private constructor */ - DrawCell(){ + private DrawCell(){ } @@ -66,17 +111,35 @@ public class DrawCell { /** * Generates a drawable entity based on this chunk */ - public void generateDrawableEntity(VoxelTextureAtlas atlas, int lod, DrawCellFace higherLODFace){ + public void generateDrawableEntity(VoxelTextureAtlas atlas, int lod, List higherLODFaces){ + Globals.profiler.beginCpuSample("DrawCell.fillInData"); + boolean success = this.fillInData(lod); + Globals.profiler.endCpuSample(); + if(!success){ + this.setHasRequested(false); + return; + } if(modelEntity != null){ Globals.clientScene.deregisterEntity(modelEntity); } - this.fillInData(); TransvoxelChunkData chunkData = new TransvoxelChunkData(weights, types, lod); - if(lod > FULL_DETAIL_LOD){ - + if(higherLODFaces != null){ + for(DrawCellFace face : higherLODFaces){ + Globals.profiler.beginCpuSample("DrawCell.fillInFaceData"); + success = this.fillInFaceData(chunkData,face,lod); + Globals.profiler.endCpuSample(); + if(!success){ + this.setHasRequested(false); + return; + } + } } - modelEntity = TerrainChunk.clientCreateTerrainChunkEntity(chunkData, lod, atlas); + if(lod == INVALID_LOD_LEVEL){ + throw new Error("Trying to generate invalid LOD"); + } + modelEntity = TerrainChunk.clientCreateTerrainChunkEntity(chunkData, lod, atlas, this.hasPolygons()); ClientEntityUtils.initiallyPositionEntity(modelEntity, getRealPos(), new Quaterniond()); + this.setHasGenerated(true); } /** @@ -90,147 +153,322 @@ public class DrawCell { worldPos.z * ChunkData.CHUNK_SIZE ); } + + /** + * Gets the world-space position of the draw cell + * @return the world-space position + */ + protected Vector3i getWorldPos(){ + return new Vector3i(worldPos); + } /** * Destroys a drawcell including its physics */ public void destroy(){ CollisionEngine collisionEngine = Globals.clientSceneWrapper.getCollisionEngine(); - collisionEngine.destroyPhysics(modelEntity); - ClientEntityUtils.destroyEntity(modelEntity); + if(modelEntity != null){ + collisionEngine.destroyPhysics(modelEntity); + ClientEntityUtils.destroyEntity(modelEntity); + } + } + + /** + * Gets the entity for the cell + * @return The entity if it exists, null otherwise + */ + public Entity getEntity(){ + return modelEntity; } /** * Fills in the internal arrays of data for generate terrain models + * @return true if the data was successfully filled, false otherwise */ - private void fillInData(){ - // - //fill in data - // - //main chunk - ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos); - for(int x = 0; x < ChunkData.CHUNK_SIZE; x++){ - for(int y = 0; y < ChunkData.CHUNK_SIZE; y++){ - for(int z = 0; z < ChunkData.CHUNK_SIZE; z++){ - weights[x][y][z] = currentChunk.getWeight(x,y,z); - types[x][y][z] = currentChunk.getType(x,y,z); + private boolean fillInData(int lod){ + int spacingFactor = (int)Math.pow(2,lod); + for(int x = 0; x < ChunkData.CHUNK_DATA_GENERATOR_SIZE; x++){ + for(int y = 0; y < ChunkData.CHUNK_DATA_GENERATOR_SIZE; y++){ + for(int z = 0; z < ChunkData.CHUNK_DATA_GENERATOR_SIZE; z++){ + ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint( + worldPos.x + (x * spacingFactor) / ChunkData.CHUNK_SIZE, + worldPos.y + (y * spacingFactor) / ChunkData.CHUNK_SIZE, + worldPos.z + (z * spacingFactor) / ChunkData.CHUNK_SIZE + ); + if(currentChunk == null){ + return false; + } + weights[x][y][z] = currentChunk.getWeight( + (x * spacingFactor) % ChunkData.CHUNK_SIZE, + (y * spacingFactor) % ChunkData.CHUNK_SIZE, + (z * spacingFactor) % ChunkData.CHUNK_SIZE + ); + types[x][y][z] = currentChunk.getType( + (x * spacingFactor) % ChunkData.CHUNK_SIZE, + (y * spacingFactor) % ChunkData.CHUNK_SIZE, + (z * spacingFactor) % ChunkData.CHUNK_SIZE + ); + + //checks to see if there is only one type of voxel in this chunk + if(!this.multiVoxelType){ + if(this.initialVoxelType == -1){ + this.initialVoxelType = types[x][y][z]; + } else if(this.initialVoxelType != types[x][y][z]){ + this.multiVoxelType = true; + } + } } } } - //face X - if(worldPos.x + 1 < Globals.clientWorldData.getWorldDiscreteSize()){ - currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos.x + 1, worldPos.y, worldPos.z); - for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){ - for(int j = 0; j < ChunkData.CHUNK_SIZE; j++){ - weights[ChunkData.CHUNK_SIZE][i][j] = currentChunk.getWeight(0, i, j); - types[ChunkData.CHUNK_SIZE][i][j] = currentChunk.getType(0, i, j); - } - } - } else { - for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){ - for(int j = 0; j < ChunkData.CHUNK_SIZE; j++){ - weights[ChunkData.CHUNK_SIZE][i][j] = -1; - types[ChunkData.CHUNK_SIZE][i][j] = 0; - } - } - } - //face Y - if(worldPos.y + 1 < Globals.clientWorldData.getWorldDiscreteSize()){ - currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos.x, worldPos.y + 1, worldPos.z); - for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){ - for(int j = 0; j < ChunkData.CHUNK_SIZE; j++){ - weights[i][ChunkData.CHUNK_SIZE][j] = currentChunk.getWeight(i, 0, j); - types[i][ChunkData.CHUNK_SIZE][j] = currentChunk.getType(i, 0, j); - } - } - } else { - for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){ - for(int j = 0; j < ChunkData.CHUNK_SIZE; j++){ - weights[i][ChunkData.CHUNK_SIZE][j] = -1; - types[i][ChunkData.CHUNK_SIZE][j] = 0; - } - } - } - //face Z - if(worldPos.z + 1 < Globals.clientWorldData.getWorldDiscreteSize()){ - currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z + 1); - for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){ - for(int j = 0; j < ChunkData.CHUNK_SIZE; j++){ - weights[i][j][ChunkData.CHUNK_SIZE] = currentChunk.getWeight(i, j, 0); - types[i][j][ChunkData.CHUNK_SIZE] = currentChunk.getType(i, j, 0); - } - } - } else { - for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){ - for(int j = 0; j < ChunkData.CHUNK_SIZE; j++){ - weights[i][j][ChunkData.CHUNK_SIZE] = -1; - types[i][j][ChunkData.CHUNK_SIZE] = 0; - } - } - } - //edge X-Y - if( - worldPos.x + 1 < Globals.clientWorldData.getWorldDiscreteSize() && - worldPos.y + 1 < Globals.clientWorldData.getWorldDiscreteSize() - ){ - currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos.x + 1, worldPos.y + 1, worldPos.z); - for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){ - weights[ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE][i] = currentChunk.getWeight(0, 0, i); - types [ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE][i] = currentChunk.getType(0, 0, i); - } - } else { - for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){ - weights[ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE][i] = -1; - types [ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE][i] = 0; - } - } - //edge X-Z - if( - worldPos.x + 1 < Globals.clientWorldData.getWorldDiscreteSize() && - worldPos.z + 1 < Globals.clientWorldData.getWorldDiscreteSize() - ){ - currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos.x + 1, worldPos.y, worldPos.z + 1); - for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){ - weights[ChunkData.CHUNK_SIZE][i][ChunkData.CHUNK_SIZE] = currentChunk.getWeight(0, i, 0); - types [ChunkData.CHUNK_SIZE][i][ChunkData.CHUNK_SIZE] = currentChunk.getType(0, i, 0); - } - } else { - for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){ - weights[ChunkData.CHUNK_SIZE][i][ChunkData.CHUNK_SIZE] = -1; - types [ChunkData.CHUNK_SIZE][i][ChunkData.CHUNK_SIZE] = 0; - } - } - //edge Y-Z - if( - worldPos.y + 1 < Globals.clientWorldData.getWorldDiscreteSize() && - worldPos.z + 1 < Globals.clientWorldData.getWorldDiscreteSize() - ){ - currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos.x, worldPos.y + 1, worldPos.z + 1); - for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){ - weights[i][ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE] = currentChunk.getWeight(i, 0, 0); - types [i][ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE] = currentChunk.getType(i, 0, 0); - } - } else { - for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){ - weights[i][ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE] = -1; - types [i][ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE] = 0; - } - } - if( - worldPos.z + 1 < Globals.clientWorldData.getWorldDiscreteSize() && - worldPos.y + 1 < Globals.clientWorldData.getWorldDiscreteSize() && - worldPos.z + 1 < Globals.clientWorldData.getWorldDiscreteSize() - ){ - currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos.x + 1, worldPos.y + 1, worldPos.z + 1); - if(currentChunk != null){ - weights[ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE] = currentChunk.getWeight(0, 0, 0); - types[ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE] = currentChunk.getType(0, 0, 0); - } - } else { - weights[ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE] = -1; - types[ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE] = 0; - } + return true; } + + /** + * Fills in the data for the higher resolution face + * @param chunkData The data for the chunk to generate + * @param higherLODFace The face that is higher LOD + * @param lod The Level of Detail for this chunk + * @return true if successfully filled in data, false otherwise + */ + private boolean fillInFaceData(TransvoxelChunkData chunkData, DrawCellFace higherLODFace, int lod){ + int mainSpacing = (int)Math.pow(2,lod); + int higherResSpacing = (int)Math.pow(2,lod - 1); + float[][] faceWeights = new float[TransvoxelModelGeneration.FACE_DATA_DIMENSIONS][TransvoxelModelGeneration.FACE_DATA_DIMENSIONS]; + int[][] faceTypes = new int[TransvoxelModelGeneration.FACE_DATA_DIMENSIONS][TransvoxelModelGeneration.FACE_DATA_DIMENSIONS]; + //allocate face array + for(int x = 0; x < TransvoxelModelGeneration.FACE_DATA_DIMENSIONS; x++){ + for(int y = 0; y < TransvoxelModelGeneration.FACE_DATA_DIMENSIONS; y++){ + int worldCoordOffset1 = (x * higherResSpacing) / ChunkData.CHUNK_SIZE; + int worldCoordOffset2 = (y * higherResSpacing) / ChunkData.CHUNK_SIZE; + //solve coordinates relative to the face + int localCoord1 = (x * higherResSpacing) % ChunkData.CHUNK_SIZE; + int localCoord2 = (y * higherResSpacing) % ChunkData.CHUNK_SIZE; + + //implicitly performing transforms to adapt from face-space to world & local space + switch(higherLODFace){ + case X_POSITIVE: { + ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i( + worldPos.x + (17 * mainSpacing) / ChunkData.CHUNK_SIZE, + worldPos.y + worldCoordOffset1, + worldPos.z + worldCoordOffset2 + )); + if(currentChunk == null){ + return false; + } + faceWeights[x][y] = currentChunk.getWeight( + 0, + localCoord1, + localCoord2 + ); + faceTypes[x][y] = currentChunk.getType( + 0, + localCoord1, + localCoord2 + ); + } break; + case X_NEGATIVE: { + ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i( + worldPos.x, + worldPos.y + worldCoordOffset1, + worldPos.z + worldCoordOffset2 + )); + if(currentChunk == null){ + return false; + } + faceWeights[x][y] = currentChunk.getWeight( + 0, + localCoord1, + localCoord2 + ); + faceTypes[x][y] = currentChunk.getType( + 0, + localCoord1, + localCoord2 + ); + } break; + case Y_POSITIVE: { + ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i( + worldPos.x + worldCoordOffset1, + worldPos.y + (17 * mainSpacing) / ChunkData.CHUNK_SIZE, + worldPos.z + worldCoordOffset2 + )); + if(currentChunk == null){ + return false; + } + faceWeights[x][y] = currentChunk.getWeight( + localCoord1, + 0, + localCoord2 + ); + faceTypes[x][y] = currentChunk.getType( + localCoord1, + 0, + localCoord2 + ); + } break; + case Y_NEGATIVE: { + ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i( + worldPos.x + worldCoordOffset1, + worldPos.y, + worldPos.z + worldCoordOffset2 + )); + if(currentChunk == null){ + return false; + } + faceWeights[x][y] = currentChunk.getWeight( + localCoord1, + 0, + localCoord2 + ); + faceTypes[x][y] = currentChunk.getType( + localCoord1, + 0, + localCoord2 + ); + } break; + case Z_POSITIVE: { + ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i( + worldPos.x + worldCoordOffset1, + worldPos.y + worldCoordOffset2, + worldPos.z + (17 * mainSpacing) / ChunkData.CHUNK_SIZE + )); + if(currentChunk == null){ + return false; + } + faceWeights[x][y] = currentChunk.getWeight( + localCoord1, + localCoord2, + 0 + ); + faceTypes[x][y] = currentChunk.getType( + localCoord1, + localCoord2, + 0 + ); + } break; + case Z_NEGATIVE: { + ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i( + worldPos.x + worldCoordOffset1, + worldPos.y + worldCoordOffset2, + worldPos.z + )); + if(currentChunk == null){ + return false; + } + faceWeights[x][y] = currentChunk.getWeight( + localCoord1, + localCoord2, + 0 + ); + faceTypes[x][y] = currentChunk.getType( + localCoord1, + localCoord2, + 0 + ); + } break; + } + // Vector3i sampleChunkWorldPos = new Vector3i( + // worldPos.x + (x * higherResSpacing) / ChunkData.CHUNK_SIZE, + // worldPos.y + (y * higherResSpacing) / ChunkData.CHUNK_SIZE, + // worldPos.z + (z * spacingFactor) / ChunkData.CHUNK_SIZE + // ); + // ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(sampleChunkWorldPos); + // if(currentChunk == null){ + // throw new Error("Chunk is null! " + worldPos); + // } + // weights[x][y][z] = currentChunk.getWeight( + // (x * higherResSpacing) % ChunkData.CHUNK_SIZE, + // (y * higherResSpacing) % ChunkData.CHUNK_SIZE, + // (z * spacingFactor) % ChunkData.CHUNK_SIZE + // ); + // types[x][y][z] = currentChunk.getType( + // (x * higherResSpacing) % ChunkData.CHUNK_SIZE, + // (y * higherResSpacing) % ChunkData.CHUNK_SIZE, + // (z * spacingFactor) % ChunkData.CHUNK_SIZE + // ); + } + } + switch(higherLODFace){ + case X_POSITIVE: { + chunkData.addXPositiveEdge(faceWeights, faceTypes); + } break; + case X_NEGATIVE: { + chunkData.addXNegativeEdge(faceWeights, faceTypes); + } break; + case Y_POSITIVE: { + chunkData.addYPositiveEdge(faceWeights, faceTypes); + } break; + case Y_NEGATIVE: { + chunkData.addYNegativeEdge(faceWeights, faceTypes); + } break; + case Z_POSITIVE: { + chunkData.addZPositiveEdge(faceWeights, faceTypes); + } break; + case Z_NEGATIVE: { + chunkData.addZNegativeEdge(faceWeights, faceTypes); + } break; + } + return true; + } + + + /** + * Gets the LOD level + * @return The LOD level + */ + public int getLodLevel() { + return lodLevel; + } + + /** + * Sets the LOD level + * @param lodLevel The LOD level + */ + public void setLodLevel(int lodLevel) { + this.lodLevel = lodLevel; + } + + /** + * Gets whether this draw cell has requested its chunk data or not + * @return true if has requested, false otherwise + */ + public boolean hasRequested() { + return hasRequested; + } + + /** + * Sets whether this draw cell has requested its chunk data or not + * @param hasRequested true if has requested, false otherwise + */ + public void setHasRequested(boolean hasRequested) { + this.hasRequested = hasRequested; + } + + /** + * Gets whether this draw cell has generated its entity or not + * @return true if has generated, false otherwise + */ + public boolean hasGenerated() { + return hasGenerated; + } + + /** + * Sets whether this draw cell has generated its entity or not + * @param hasGenerated true if has generated, false otherwise + */ + public void setHasGenerated(boolean hasGenerated) { + this.hasGenerated = hasGenerated; + } + + /** + * Gets whether this draw cell will generate polygons or not + * @return true if it has polygons, false otherwise + */ + private boolean hasPolygons(){ + return this.multiVoxelType; + } + + } diff --git a/src/main/java/electrosphere/client/terrain/cells/DrawCellManager.java b/src/main/java/electrosphere/client/terrain/cells/DrawCellManager.java index 8b76ea40..de4c287e 100644 --- a/src/main/java/electrosphere/client/terrain/cells/DrawCellManager.java +++ b/src/main/java/electrosphere/client/terrain/cells/DrawCellManager.java @@ -2,6 +2,7 @@ package electrosphere.client.terrain.cells; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -18,6 +19,7 @@ import electrosphere.net.parser.net.message.TerrainMessage; import electrosphere.renderer.shader.ShaderProgram; import electrosphere.server.terrain.manager.ServerTerrainChunk; +@Deprecated /** * Manages the graphical entities for the terrain chunks * @@ -235,7 +237,7 @@ public class DrawCellManager { ); cells.add(cell); keyCellMap.put(targetKey,cell); - DrawCellFace higherLODFace = null; + List higherLODFace = null; keyCellMap.get(targetKey).generateDrawableEntity(atlas,0,higherLODFace); //evaluate for foliage @@ -261,7 +263,7 @@ public class DrawCellManager { worldPos.z < Globals.clientWorldData.getWorldDiscreteSize() ){ keyCellMap.get(targetKey).destroy(); - DrawCellFace higherLODFace = null; + List higherLODFace = null; keyCellMap.get(targetKey).generateDrawableEntity(atlas,0,higherLODFace); } drawable.add(targetKey); @@ -304,7 +306,7 @@ public class DrawCellManager { * @return the cell coordinate */ public int transformRealSpaceToCellSpace(double input){ - return (int)(input / Globals.clientWorldData.getDynamicInterpolationRatio()); + return (int)(input / ServerTerrainChunk.CHUNK_DIMENSION); } /** diff --git a/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java b/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java index baf4d774..5fdba3c1 100644 --- a/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java +++ b/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java @@ -8,12 +8,14 @@ import java.util.LinkedList; import java.util.List; import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Semaphore; import org.joml.Vector3i; import electrosphere.client.scene.ClientWorldData; import electrosphere.client.terrain.cache.ChunkData; import electrosphere.client.terrain.cache.ClientTerrainCache; +import electrosphere.client.terrain.cells.ClientDrawCellManager; import electrosphere.client.terrain.cells.VoxelTextureAtlas; import electrosphere.engine.Globals; import electrosphere.entity.types.terrain.TerrainChunkData; @@ -21,6 +23,7 @@ import electrosphere.logger.LoggerInterface; import electrosphere.net.parser.net.message.TerrainMessage; import electrosphere.renderer.meshgen.TerrainChunkModelGeneration; import electrosphere.renderer.model.Model; +import electrosphere.server.terrain.manager.ServerTerrainChunk; import electrosphere.server.terrain.manager.ServerTerrainManager; /** @@ -30,12 +33,22 @@ public class ClientTerrainManager { //queues messages from server List messageQueue = new CopyOnWriteArrayList(); + + /** + * Locks the terrain manager (eg if adding message from network) + */ + static Semaphore lock = new Semaphore(1); //The interpolation ratio of terrain public static final int INTERPOLATION_RATIO = ServerTerrainManager.SERVER_TERRAIN_MANAGER_INTERPOLATION_RATIO; //caches chunks from server - static final int CACHE_SIZE = 500; + static final int CACHE_SIZE = 1500 + (int)(ClientDrawCellManager.FULL_RES_DIST * 10) + (int)(ClientDrawCellManager.HALF_RES_DIST * 10); + + /** + * Size of the cache in bytes + */ + static final int CACHE_SIZE_IN_MB = (CACHE_SIZE * ServerTerrainChunk.CHUNK_DIMENSION * ServerTerrainChunk.CHUNK_DIMENSION * ServerTerrainChunk.CHUNK_DIMENSION * 2 * 4) / 1024 / 1024; //used for caching the macro values ClientTerrainCache terrainCache; @@ -58,6 +71,8 @@ public class ClientTerrainManager { * Handles messages that have been received from the server */ public void handleMessages(){ + Globals.profiler.beginCpuSample("ClientTerrainManager.handleMessages"); + lock.acquireUninterruptibly(); List bouncedMessages = new LinkedList(); for(TerrainMessage message : messageQueue){ messageQueue.remove(message); @@ -83,7 +98,7 @@ public class ClientTerrainManager { } } } - ChunkData data = new ChunkData(); + ChunkData data = new ChunkData(message.getworldX(), message.getworldY(), message.getworldZ()); data.setVoxelType(values); data.setVoxelWeight(weights); terrainCache.addChunkDataToCache( @@ -99,6 +114,8 @@ public class ClientTerrainManager { for(TerrainMessage message : bouncedMessages){ messageQueue.add(message); } + lock.release(); + Globals.profiler.endCpuSample(); } /** @@ -113,7 +130,9 @@ public class ClientTerrainManager { * @param message The message */ public void attachTerrainMessage(TerrainMessage message){ + lock.acquireUninterruptibly(); messageQueue.add(message); + lock.release(); } /** @@ -135,6 +154,22 @@ public class ClientTerrainManager { public boolean containsChunkDataAtWorldPoint(Vector3i worldPos){ return terrainCache.containsChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z); } + + /** + * Requests a chunk from the server + * @param worldX the world x coordinate of the chunk + * @param worldY the world y coordinate of the chunk + * @param worldZ the world z coordinate of the chunk + */ + public void requestChunk(int worldX, int worldY, int worldZ){ + if(!this.terrainCache.hasRequested(worldX, worldY, worldZ)){ + Globals.clientConnection.queueOutgoingMessage(TerrainMessage.constructRequestChunkDataMessage( + worldX, + worldY, + worldZ + )); + } + } /** * Checks that the cache contains chunk data at a real-space coordinate @@ -184,7 +219,9 @@ public class ClientTerrainManager { UUID newUUID = UUID.randomUUID(); promisedHash = newUUID.toString(); TerrainChunkGenQueueItem queueItem = new TerrainChunkGenQueueItem(data, promisedHash, atlas); + lock.acquireUninterruptibly(); terrainChunkGenerationQueue.add(queueItem); + lock.release(); return promisedHash; } @@ -192,12 +229,14 @@ public class ClientTerrainManager { * Pushes all terrain data in queue to the gpu and registers the resulting models */ public static void generateTerrainChunkGeometry(){ - Globals.profiler.beginCpuSample("generateTerrainChunkGeometry"); + Globals.profiler.beginCpuSample("ClientTerrainManager.generateTerrainChunkGeometry"); + lock.acquireUninterruptibly(); for(TerrainChunkGenQueueItem queueItem : terrainChunkGenerationQueue){ Model terrainModel = TerrainChunkModelGeneration.generateTerrainModel(queueItem.getData(), queueItem.getAtlas()); Globals.assetManager.registerModelToSpecificString(terrainModel, queueItem.getPromisedHash()); } terrainChunkGenerationQueue.clear(); + lock.release(); Globals.profiler.endCpuSample(); } @@ -217,5 +256,13 @@ public class ClientTerrainManager { public Vector3i getPositionOfChunk(ChunkData chunk){ return terrainCache.getChunkPosition(chunk); } + + /** + * Gets the number of chunks that have been requested + * @return The number of chunks + */ + public int getRequestedCellCount(){ + return this.terrainCache.getRequestedCellCount(); + } } diff --git a/src/main/java/electrosphere/client/ui/menu/WindowUtils.java b/src/main/java/electrosphere/client/ui/menu/WindowUtils.java index f4235921..59aff70c 100644 --- a/src/main/java/electrosphere/client/ui/menu/WindowUtils.java +++ b/src/main/java/electrosphere/client/ui/menu/WindowUtils.java @@ -184,6 +184,9 @@ public class WindowUtils { initTooltipWindow(); } + /** + * Inits the loading window + */ static void initLoadingWindow(){ Window loadingWindow = Window.create(Globals.renderingEngine.getOpenGLState(), 0, 0, Globals.WINDOW_WIDTH, Globals.WINDOW_HEIGHT, false); Label loadingLabel = Label.createLabel("LOADING"); @@ -208,6 +211,18 @@ public class WindowUtils { Globals.elementService.registerWindow(WindowStrings.WINDOW_ITEM_DRAG_CONTAINER, itemDragContainerWindow); } + /** + * Updates the loading window + */ + public static void updateLoadingWindow(String message){ + Globals.signalSystem.post(SignalType.UI_MODIFICATION,() -> { + Window loadingWindow = (Window)Globals.elementService.getWindow(WindowStrings.WINDOW_LOADING); + loadingWindow.clear(); + loadingWindow.addChild(Label.createLabel(message)); + Globals.signalSystem.post(SignalType.YOGA_APPLY,loadingWindow); + }); + } + /** * Creates the tooltip window */ diff --git a/src/main/java/electrosphere/client/ui/menu/debug/ImGuiChunkMonitor.java b/src/main/java/electrosphere/client/ui/menu/debug/ImGuiChunkMonitor.java new file mode 100644 index 00000000..3067161f --- /dev/null +++ b/src/main/java/electrosphere/client/ui/menu/debug/ImGuiChunkMonitor.java @@ -0,0 +1,87 @@ +package electrosphere.client.ui.menu.debug; + +import electrosphere.engine.Globals; +import electrosphere.renderer.ui.imgui.ImGuiLinePlot; +import electrosphere.renderer.ui.imgui.ImGuiLinePlot.ImGuiLinePlotDataset; +import electrosphere.renderer.ui.imgui.ImGuiWindow; +import electrosphere.renderer.ui.imgui.ImGuiWindow.ImGuiWindowCallback; +import imgui.ImGui; + +public class ImGuiChunkMonitor { + + /** + * Num datapoints + */ + public static final int PRESSURE_GRAPH_POINT_COUNT = 100; + + /** + * Window for viewing chunk status on server and client + */ + protected static ImGuiWindow chunkMonitorWindow; + + /** + * Creates the windows in this file + */ + protected static void createChunkMonitorWindows(){ + createChunkMonitorWindow(); + } + + /** + * Client scene entity view + */ + protected static void createChunkMonitorWindow(){ + chunkMonitorWindow = new ImGuiWindow("Chunk Monitor"); + + //client network pressure graph + ImGuiLinePlot clientNetworkBandwith = new ImGuiLinePlot("Client Network Pressure",400,400); + ImGuiLinePlotDataset clientPressureDataset = new ImGuiLinePlotDataset("Client bytes per frame", PRESSURE_GRAPH_POINT_COUNT); + clientPressureDataset.zeroOut(); + clientNetworkBandwith.addDataset(clientPressureDataset); + + //server network pressure graph + ImGuiLinePlot serverNetworkPressureGraph = new ImGuiLinePlot("Server Network Pressure",400,400); + ImGuiLinePlotDataset serverPressureDataset = new ImGuiLinePlotDataset("Server bytes per frame", PRESSURE_GRAPH_POINT_COUNT); + serverPressureDataset.zeroOut(); + serverNetworkPressureGraph.addDataset(serverPressureDataset); + + chunkMonitorWindow.setCallback(new ImGuiWindowCallback() { + long clientPressureLastValue = 0; + long serverPressureLastValue = 0; + + @Override + public void exec() { + + + //ui framework text + ImGui.text("Chunk Monitor"); + + Globals.clientDrawCellManager.updateStatus(); + ImGui.text("Renderable chunks: " + Globals.clientDrawCellManager.getGenerated()); + ImGui.text("Full res chunks: " + Globals.clientDrawCellManager.getMaxResCount()); + ImGui.text("Half res chunks: " + Globals.clientDrawCellManager.getHalfResCount()); + + + //client network pressure + if(Globals.clientConnection != null){ + long clientPressureNewTotal = Globals.clientConnection.getNumBytesRead(); + long clientPressureDelta = clientPressureNewTotal - clientPressureLastValue; + clientPressureDataset.addPoint(clientPressureDelta); + clientPressureLastValue = clientPressureNewTotal; + } + clientNetworkBandwith.draw(); + + //server network pressure + if(Globals.server != null && Globals.server.getFirstConnection() != null){ + long serverPressureNewTotal = Globals.server.getFirstConnection().getNumBytesRead(); + long serverPressureDelta = serverPressureNewTotal - serverPressureLastValue; + serverPressureDataset.addPoint(serverPressureDelta); + serverPressureLastValue = serverPressureNewTotal; + } + serverNetworkPressureGraph.draw(); + + } + }); + chunkMonitorWindow.setOpen(false); + Globals.renderingEngine.getImGuiPipeline().addImGuiWindow(chunkMonitorWindow); + } +} diff --git a/src/main/java/electrosphere/client/ui/menu/debug/ImGuiTestGen.java b/src/main/java/electrosphere/client/ui/menu/debug/ImGuiTestGen.java index a15e476e..52400574 100644 --- a/src/main/java/electrosphere/client/ui/menu/debug/ImGuiTestGen.java +++ b/src/main/java/electrosphere/client/ui/menu/debug/ImGuiTestGen.java @@ -47,7 +47,7 @@ public class ImGuiTestGen { if(ImGui.button("Regenerate")){ GriddedDataCellManager gridManager = (GriddedDataCellManager)Globals.realmManager.first().getDataCellManager(); gridManager.evictAll(); - Globals.drawCellManager.evictAll(); + Globals.clientDrawCellManager.evictAll(); Globals.clientTerrainManager.evictAll(); } @@ -80,6 +80,11 @@ public class ImGuiTestGen { if(ImGui.inputInt("biome[1][1]", biome11)){ terrainModel.getBiome()[1][1] = biome11.shortValue(); } + + //Toggles whether the client draws cell manager should update or not + if(ImGui.button("Toggle ClientDrawCellManager updates")){ + Globals.clientDrawCellManager.setShouldUpdate(!Globals.clientDrawCellManager.getShouldUpdate()); + } } }); diff --git a/src/main/java/electrosphere/client/ui/menu/debug/ImGuiWindowMacros.java b/src/main/java/electrosphere/client/ui/menu/debug/ImGuiWindowMacros.java index f0962f94..1a019e8d 100644 --- a/src/main/java/electrosphere/client/ui/menu/debug/ImGuiWindowMacros.java +++ b/src/main/java/electrosphere/client/ui/menu/debug/ImGuiWindowMacros.java @@ -51,6 +51,7 @@ public class ImGuiWindowMacros { ImGuiLogger.createLoggersWindows(); ImGuiRenderer.createRendererWindows(); ImGuiTestGen.createTestGenWindows(); + ImGuiChunkMonitor.createChunkMonitorWindows(); } /** @@ -183,6 +184,10 @@ public class ImGuiWindowMacros { ){ ImGuiTestGen.testGenWindow.setOpen(true); } + //chunk monitor + if(ImGui.button("Chunk Monitor")){ + ImGuiChunkMonitor.chunkMonitorWindow.setOpen(true); + } //close button if(ImGui.button("Close")){ mainDebugWindow.setOpen(false); diff --git a/src/main/java/electrosphere/collision/CollisionEngine.java b/src/main/java/electrosphere/collision/CollisionEngine.java index 6fd71c84..8c31840b 100644 --- a/src/main/java/electrosphere/collision/CollisionEngine.java +++ b/src/main/java/electrosphere/collision/CollisionEngine.java @@ -724,7 +724,9 @@ public class CollisionEngine { DBody rigidBody = PhysicsEntityUtils.getDBody(e); deregisterCollisionObject(rigidBody,PhysicsEntityUtils.getCollidable(e)); e.removeData(EntityDataStrings.PHYSICS_COLLISION_BODY); - deregisterPhysicsObject(rigidBody); + if(rigidBody != null){ + deregisterPhysicsObject(rigidBody); + } } if(ServerPhysicsSyncTree.hasTree(e)){ ServerPhysicsSyncTree.detachTree(e, ServerPhysicsSyncTree.getTree(e)); diff --git a/src/main/java/electrosphere/collision/CollisionWorldData.java b/src/main/java/electrosphere/collision/CollisionWorldData.java index 33c3d00d..ade9da9d 100644 --- a/src/main/java/electrosphere/collision/CollisionWorldData.java +++ b/src/main/java/electrosphere/collision/CollisionWorldData.java @@ -62,7 +62,7 @@ public class CollisionWorldData { public int getDynamicInterpolationRatio(){ if(clientWorldData != null){ - return clientWorldData.getDynamicInterpolationRatio(); + return clientWorldData.getWorldDiscreteSize(); } else { return serverWorldData.getDynamicInterpolationRatio(); } diff --git a/src/main/java/electrosphere/engine/Globals.java b/src/main/java/electrosphere/engine/Globals.java index 7e7a8d05..8f6a99d0 100644 --- a/src/main/java/electrosphere/engine/Globals.java +++ b/src/main/java/electrosphere/engine/Globals.java @@ -21,7 +21,7 @@ import electrosphere.client.player.ClientPlayerData; import electrosphere.client.scene.ClientSceneWrapper; import electrosphere.client.scene.ClientWorldData; import electrosphere.client.sim.ClientSimulation; -import electrosphere.client.terrain.cells.DrawCellManager; +import electrosphere.client.terrain.cells.ClientDrawCellManager; import electrosphere.client.terrain.cells.VoxelTextureAtlas; import electrosphere.client.terrain.manager.ClientTerrainManager; import electrosphere.client.ui.menu.WindowUtils; @@ -235,7 +235,7 @@ public class Globals { // //Generic OpenGL Statements // - public static long window; + public static long window = -1; @@ -351,7 +351,7 @@ public class Globals { //chunk stuff //draw cell manager - public static DrawCellManager drawCellManager; + public static ClientDrawCellManager clientDrawCellManager; public static VoxelTextureAtlas voxelTextureAtlas = new VoxelTextureAtlas(); //fluid cell manager @@ -696,6 +696,7 @@ public class Globals { Globals.realmManager = null; Globals.clientSceneWrapper = null; Globals.clientScene = null; + Globals.clientWorldData = null; Globals.audioEngine = null; Globals.renderingEngine = null; Globals.threadManager = null; diff --git a/src/main/java/electrosphere/engine/Main.java b/src/main/java/electrosphere/engine/Main.java index eb0c9f12..8cca6c93 100644 --- a/src/main/java/electrosphere/engine/Main.java +++ b/src/main/java/electrosphere/engine/Main.java @@ -420,14 +420,7 @@ public class Main { Globals.profiler.endCpuSample(); } catch (NullPointerException ex){ - LoggerInterface.loggerEngine.ERROR("Main frame uncaught NPE", ex); - //after a while, jvm will stop reporting stack traces with errors - //need to explicitly kill the vm if you want to see the stack trace - if(Globals.ENGINE_DEBUG){ - System.exit(1); - } else { - throw new Error("NPE!"); - } + LoggerInterface.loggerEngine.ERROR(ex); } } diff --git a/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java b/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java index 9da36f63..9d6523d7 100644 --- a/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java +++ b/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java @@ -10,7 +10,7 @@ import electrosphere.client.entity.crosshair.Crosshair; import electrosphere.client.fluid.cells.FluidCellManager; import electrosphere.client.foliagemanager.ClientFoliageManager; import electrosphere.client.sim.ClientSimulation; -import electrosphere.client.terrain.cells.DrawCellManager; +import electrosphere.client.terrain.cells.ClientDrawCellManager; import electrosphere.client.ui.menu.MenuGenerators; import electrosphere.client.ui.menu.WindowStrings; import electrosphere.client.ui.menu.WindowUtils; @@ -29,16 +29,27 @@ import electrosphere.net.NetUtils; import electrosphere.net.client.ClientNetworking; import electrosphere.renderer.actor.Actor; import electrosphere.renderer.actor.ActorTextureMask; -import electrosphere.renderer.ui.elements.Window; public class ClientLoading { + + + /** + * Number of frames to wait before updating status of draw cell manager loading + */ + static final int DRAW_CELL_UPDATE_RATE = 60; + + + /** + * The number of frames the draw cell is expected to take (minimum) to init + */ + static final int DRAW_CELL_EXPECTED_MINIMUM_FRAMES_TO_INIT = 100; protected static void loadCharacterServer(Object[] params){ - Window loadingWindow = (Window)Globals.elementService.getWindow(WindowStrings.WINDOW_LOADING); WindowUtils.recursiveSetVisible(Globals.elementService.getWindow(WindowStrings.WINDOW_MENU_MAIN), false); WindowUtils.replaceMainMenuContents(MenuGenerators.createEmptyMainMenu()); - loadingWindow.setVisible(true); + WindowUtils.recursiveSetVisible(Globals.elementService.getWindow(WindowStrings.WINDOW_LOADING), true); + WindowUtils.updateLoadingWindow("Waiting on server"); //disable menu input Globals.controlHandler.hintUpdateControlState(ControlHandler.ControlsState.NO_INPUT); //initialize the client thread (client) @@ -54,7 +65,7 @@ public class ClientLoading { //eventually should replace with at ui to select an already created character or create a new one WindowUtils.replaceMainMenuContents(MenuGeneratorsMultiplayer.createMultiplayerCharacterCreationWindow()); //make loading dialog disappear - loadingWindow.setVisible(false); + WindowUtils.recursiveSetVisible(Globals.elementService.getWindow(WindowStrings.WINDOW_LOADING), false); //make character creation window visible WindowUtils.recursiveSetVisible(Globals.elementService.getWindow(WindowStrings.WINDOW_MENU_MAIN), true); //recapture window @@ -70,6 +81,7 @@ public class ClientLoading { Globals.signalSystem.post(SignalType.UI_MODIFICATION, () -> { WindowUtils.closeWindow(WindowStrings.WINDOW_MENU_MAIN); WindowUtils.recursiveSetVisible(WindowStrings.WINDOW_LOADING, true); + WindowUtils.updateLoadingWindow("LOADING"); }); //disable menu input Globals.controlHandler.hintUpdateControlState(ControlHandler.ControlsState.NO_INPUT); @@ -263,7 +275,8 @@ public class ClientLoading { */ static void initDrawCellManager(boolean blockForInit){ int iterations = 0; - while(blockForInit && (Globals.clientWorldData == null || InitialAssetLoading.atlasQueuedTexture == null || !InitialAssetLoading.atlasQueuedTexture.hasLoaded())){ + WindowUtils.updateLoadingWindow("WAITING ON WORLD DATA"); + while(blockForInit && (Globals.clientWorldData == null || InitialAssetLoading.atlasQueuedTexture == null || !InitialAssetLoading.atlasQueuedTexture.hasLoaded()) && Globals.threadManager.shouldKeepRunning()){ try { TimeUnit.MILLISECONDS.sleep(10); iterations++; @@ -278,28 +291,25 @@ public class ClientLoading { } } //initialize draw cell manager - Globals.drawCellManager = new DrawCellManager(Globals.clientTerrainManager, 0, 0, 0); - //construct texture atlas - Globals.drawCellManager.attachTextureAtlas(Globals.voxelTextureAtlas); - //set our draw cell manager to actually generate drawable chunks - Globals.drawCellManager.setGenerateDrawables(true); + // Globals.drawCellManager = new DrawCellManager(Globals.clientTerrainManager, 0, 0, 0); + Globals.clientDrawCellManager = new ClientDrawCellManager(Globals.voxelTextureAtlas, Globals.clientWorldData.getWorldDiscreteSize()); //Alerts the client simulation that it should start loading terrain Globals.clientSimulation.setLoadingTerrain(true); //wait for all the terrain data to arrive - while(blockForInit && Globals.drawCellManager.containsUnrequestedCell()){ + int i = 0; + while(blockForInit && Globals.clientDrawCellManager.updatedLastFrame() && Globals.threadManager.shouldKeepRunning()){ + i++; + if(i % DRAW_CELL_UPDATE_RATE == 0){ + WindowUtils.updateLoadingWindow("WAITING ON SERVER TO SEND TERRAIN (" + Globals.clientTerrainManager.getAllChunks().size() + "/" + Globals.clientTerrainManager.getRequestedCellCount() + ")"); + } try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException ex) { ex.printStackTrace(); } } - - while(blockForInit && Globals.drawCellManager.containsUndrawableCell()){ - try { - TimeUnit.MILLISECONDS.sleep(10); - } catch (InterruptedException ex) { - ex.printStackTrace(); - } + if(i < DRAW_CELL_EXPECTED_MINIMUM_FRAMES_TO_INIT){ + LoggerInterface.loggerEngine.WARNING("Probably didn't block for draw cell manager initialization!"); } } @@ -310,7 +320,8 @@ public class ClientLoading { static void initFluidCellManager(boolean blockForInit){ //wait for world data - while(blockForInit && Globals.clientWorldData == null){ + WindowUtils.updateLoadingWindow("WAITING ON WORLD DATA"); + while(blockForInit && Globals.clientWorldData == null && Globals.threadManager.shouldKeepRunning()){ try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException ex) { @@ -323,7 +334,8 @@ public class ClientLoading { Globals.clientSimulation.setLoadingTerrain(true); //wait for all the terrain data to arrive - while(blockForInit && Globals.fluidCellManager.containsUnrequestedCell()){ + WindowUtils.updateLoadingWindow("REQUESTING FLUID CHUNKS FROM SERVER (" + Globals.fluidCellManager.getUnrequestedSize() + ")"); + while(blockForInit && Globals.fluidCellManager.containsUnrequestedCell() && Globals.threadManager.shouldKeepRunning()){ try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException ex) { @@ -332,7 +344,8 @@ public class ClientLoading { } //wait for undrawable cells - while(blockForInit && Globals.fluidCellManager.containsUndrawableCell()){ + WindowUtils.updateLoadingWindow("WAITING ON SERVER TO SEND FLUID CHUNKS (" + Globals.fluidCellManager.getUndrawableSize() + ")"); + while(blockForInit && Globals.fluidCellManager.containsUndrawableCell() && Globals.threadManager.shouldKeepRunning()){ try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException ex) { diff --git a/src/main/java/electrosphere/engine/loadingthreads/LoadingUtils.java b/src/main/java/electrosphere/engine/loadingthreads/LoadingUtils.java index 526f344c..aee1994d 100644 --- a/src/main/java/electrosphere/engine/loadingthreads/LoadingUtils.java +++ b/src/main/java/electrosphere/engine/loadingthreads/LoadingUtils.java @@ -17,7 +17,6 @@ import electrosphere.entity.types.creature.CreatureTemplate; import electrosphere.entity.types.creature.CreatureToolbarData.ToolbarItem; import electrosphere.game.data.creature.type.CreatureData; import electrosphere.game.data.creature.type.visualattribute.VisualAttribute; -import electrosphere.game.server.world.MacroData; import electrosphere.logger.LoggerInterface; import electrosphere.net.NetUtils; import electrosphere.net.client.ClientNetworking; @@ -26,41 +25,17 @@ import electrosphere.net.server.Server; import electrosphere.net.server.ServerConnectionHandler; import electrosphere.net.server.player.Player; import electrosphere.server.datacell.Realm; -import electrosphere.server.simulation.MacroSimulation; import electrosphere.server.simulation.MicroSimulation; /** * Utilities for all loading thread types */ public class LoadingUtils { - - - // static void initCommonWorldData(boolean FLAG_INIT_SERVER){ - // if(Globals.commonWorldData == null){ - // if(FLAG_INIT_SERVER){ - // Globals.commonWorldData = new CommonWorldData(Globals.serverWorldData, Globals.serverTerrainManager); - // if(Globals.macroSimulation != null){ - // Town startTown = Globals.macroData.getTowns().get(0); - // Vector2i firstPos = startTown.getPositions().get(0); - // // double startX = firstPos.x * Globals.serverTerrainManager.getChunkWidth(); - // // double startZ = firstPos.y * Globals.serverTerrainManager.getChunkWidth(); - // double startX = Globals.commonWorldData.convertWorldToReal(firstPos.x); - // double startZ = Globals.commonWorldData.convertWorldToReal(firstPos.y); - // Globals.spawnPoint.set((float)startX,(float)Globals.commonWorldData.getElevationAtPoint(new Vector3d(startX,0,startZ)),(float)startZ); - // } - // } else { - // //basically wait for the client to receive the world metadata - // while(!Globals.clientConnection.getClientProtocol().hasReceivedWorld()){ - // try { - // TimeUnit.MILLISECONDS.sleep(5); - // } catch (InterruptedException ex) { - // } - // } - // //then create common world data - // Globals.commonWorldData = new CommonWorldData(Globals.clientWorldData, Globals.clientTerrainManager); - // } - // } - // } + + /** + * The size of the buffer + */ + static final int STREAM_BUFFER_SIZE = 16 * 1024 * 1024; @@ -93,7 +68,12 @@ public class LoadingUtils { } } - static final int STREAM_BUFFER_SIZE = 32 * 1024 * 1024; + + /** + * Initializes a local connection + * @param runServerThread true if should run the server in a separate thread, false otherwise + * @return The server connection + */ static ServerConnectionHandler initLocalConnection(boolean runServerThread){ ServerConnectionHandler rVal = null; try { @@ -106,7 +86,7 @@ public class LoadingUtils { PipedInputStream clientInput = new PipedInputStream(STREAM_BUFFER_SIZE); PipedOutputStream serverOutput = new PipedOutputStream(clientInput); //server -> client pipe - PipedInputStream serverInput = new PipedInputStream(); + PipedInputStream serverInput = new PipedInputStream(STREAM_BUFFER_SIZE); PipedOutputStream clientOutput; clientOutput = new PipedOutputStream(serverInput); //start server communication thread diff --git a/src/main/java/electrosphere/engine/signal/Signal.java b/src/main/java/electrosphere/engine/signal/Signal.java index 49f4935b..fcabe215 100644 --- a/src/main/java/electrosphere/engine/signal/Signal.java +++ b/src/main/java/electrosphere/engine/signal/Signal.java @@ -29,6 +29,14 @@ public class Signal { YOGA_DESTROY, UI_MODIFICATION, + // + //Terrain + // + REQUEST_CHUNK, + CHUNK_CREATED, + REQUEST_CHUNK_EDIT, + CHUNK_EDITED, + } /** diff --git a/src/main/java/electrosphere/engine/threads/ThreadManager.java b/src/main/java/electrosphere/engine/threads/ThreadManager.java index 23a189b3..2353c289 100644 --- a/src/main/java/electrosphere/engine/threads/ThreadManager.java +++ b/src/main/java/electrosphere/engine/threads/ThreadManager.java @@ -9,6 +9,8 @@ import java.util.concurrent.TimeUnit; import electrosphere.engine.Globals; import electrosphere.engine.loadingthreads.LoadingThread; import electrosphere.engine.threads.LabeledThread.ThreadLabel; +import electrosphere.entity.types.terrain.TerrainChunk; +import electrosphere.server.datacell.Realm; import electrosphere.util.CodeUtils; /** @@ -110,6 +112,19 @@ public class ThreadManager { Globals.server.close(); } + if(Globals.realmManager.getRealms() != null){ + for(Realm realm : Globals.realmManager.getRealms()){ + if(realm.getServerWorldData() != null && realm.getServerWorldData().getServerTerrainManager() != null){ + realm.getServerWorldData().getServerTerrainManager().closeThreads(); + } + } + } + + /** + * Halds all terrain chunk threads + */ + TerrainChunk.haltThreads(); + // //interrupt all threads for(int i = 0; i < 3; i++){ diff --git a/src/main/java/electrosphere/entity/EntityCreationUtils.java b/src/main/java/electrosphere/entity/EntityCreationUtils.java index a4701de6..06d19d65 100644 --- a/src/main/java/electrosphere/entity/EntityCreationUtils.java +++ b/src/main/java/electrosphere/entity/EntityCreationUtils.java @@ -129,9 +129,6 @@ public class EntityCreationUtils { */ public static void makeEntityDrawablePreexistingModel(Entity entity, String modelPath){ entity.putData(EntityDataStrings.DATA_STRING_ACTOR, ActorUtils.createActorOfLoadingModel(modelPath)); - entity.putData(EntityDataStrings.DATA_STRING_POSITION, new Vector3d(0,0,0)); - entity.putData(EntityDataStrings.DATA_STRING_ROTATION, new Quaterniond().identity()); - entity.putData(EntityDataStrings.DATA_STRING_SCALE, new Vector3f(1,1,1)); entity.putData(EntityDataStrings.DATA_STRING_DRAW, true); entity.putData(EntityDataStrings.DRAW_SOLID_PASS, true); Globals.clientScene.registerEntityToTag(entity, EntityTags.DRAWABLE); diff --git a/src/main/java/electrosphere/entity/scene/Scene.java b/src/main/java/electrosphere/entity/scene/Scene.java index 21af95e5..77f40a00 100644 --- a/src/main/java/electrosphere/entity/scene/Scene.java +++ b/src/main/java/electrosphere/entity/scene/Scene.java @@ -127,6 +127,15 @@ public class Scene { return (Entity)entityIdMap.get(id); } + /** + * Checks if a scene contains a given entity + * @param e The entity + * @return true if the scene contains the entity, false otherwise + */ + public boolean containsEntity(Entity e){ + return entityIdMap.containsKey(e.getId()); + } + /** * Registers a behavior tree to simulate each scene simulation frame * @param tree The behavior tree to register diff --git a/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java b/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java index b265825d..2e237e61 100644 --- a/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java +++ b/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java @@ -1,11 +1,15 @@ package electrosphere.entity.types.terrain; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + import org.joml.Vector3d; import electrosphere.client.terrain.cells.DrawCell; import electrosphere.client.terrain.cells.VoxelTextureAtlas; import electrosphere.client.terrain.manager.ClientTerrainManager; import electrosphere.collision.PhysicsEntityUtils; +import electrosphere.engine.Globals; import electrosphere.entity.Entity; import electrosphere.entity.EntityCreationUtils; import electrosphere.entity.EntityDataStrings; @@ -20,32 +24,44 @@ import electrosphere.server.datacell.Realm; */ public class TerrainChunk { + /** + * Used for generating terrain chunks + */ + static ExecutorService generationService = Executors.newFixedThreadPool(2); /** * Creates a client terrain chunk based on weights and values provided * @param chunkData the chunk data to generate with * @param levelOfDetail Increasing value that increments level of detail. 0 would be full resolution, 1 would be half resolution and so on. Only generates physics if levelOfDetail is 0 + * @param hasPolygons true if the chunk has polygons to generate a model with, false otherwise * @return The terrain chunk entity */ - public static Entity clientCreateTerrainChunkEntity(TransvoxelChunkData chunkData, int levelOfDetail, VoxelTextureAtlas atlas){ - - TerrainChunkData data; - if(levelOfDetail == DrawCell.FULL_DETAIL_LOD){ - data = TerrainChunkModelGeneration.generateTerrainChunkData(chunkData.terrainGrid, chunkData.textureGrid, levelOfDetail); - } else { - data = TransvoxelModelGeneration.generateTerrainChunkData(chunkData); - } - String modelPath = ClientTerrainManager.queueTerrainGridGeneration(data, atlas); + public static Entity clientCreateTerrainChunkEntity(TransvoxelChunkData chunkData, int levelOfDetail, VoxelTextureAtlas atlas, boolean hasPolygons){ + Globals.profiler.beginCpuSample("TerrainChunk.clientCreateTerrainChunkEntity"); Entity rVal = EntityCreationUtils.createClientSpatialEntity(); - EntityCreationUtils.makeEntityDrawablePreexistingModel(rVal, modelPath); - if(data.vertices.size() > 0 && levelOfDetail == DrawCell.FULL_DETAIL_LOD){ - PhysicsEntityUtils.clientAttachTerrainChunkRigidBody(rVal, data); + + if(hasPolygons){ + generationService.submit(() -> { + TerrainChunkData data; + if(levelOfDetail == DrawCell.FULL_DETAIL_LOD){ + data = TerrainChunkModelGeneration.generateTerrainChunkData(chunkData.terrainGrid, chunkData.textureGrid); + } else { + data = TransvoxelModelGeneration.generateTerrainChunkData(chunkData); + } + if(Globals.clientScene.containsEntity(rVal)){ + String modelPath = ClientTerrainManager.queueTerrainGridGeneration(data, atlas); + EntityCreationUtils.makeEntityDrawablePreexistingModel(rVal, modelPath); + if(levelOfDetail == DrawCell.FULL_DETAIL_LOD){ + PhysicsEntityUtils.clientAttachTerrainChunkRigidBody(rVal, data); + } + } + }); } rVal.putData(EntityDataStrings.TERRAIN_IS_TERRAIN, true); rVal.putData(EntityDataStrings.DRAW_CAST_SHADOW, true); - + Globals.profiler.endCpuSample(); return rVal; } @@ -59,7 +75,7 @@ public class TerrainChunk { */ public static Entity serverCreateTerrainChunkEntity(Realm realm, Vector3d position, float[][][] weights, int[][][] values){ - TerrainChunkData data = TerrainChunkModelGeneration.generateTerrainChunkData(weights, values, DrawCell.FULL_DETAIL_LOD); + TerrainChunkData data = TerrainChunkModelGeneration.generateTerrainChunkData(weights, values); Entity rVal = EntityCreationUtils.createServerEntity(realm, position); if(data.vertices.size() > 0){ @@ -84,4 +100,11 @@ public class TerrainChunk { return rVal; } + /** + * Halts all running generation threads + */ + public static void haltThreads(){ + generationService.shutdownNow(); + } + } diff --git a/src/main/java/electrosphere/net/client/ClientNetworking.java b/src/main/java/electrosphere/net/client/ClientNetworking.java index 71f92f32..2cd68929 100644 --- a/src/main/java/electrosphere/net/client/ClientNetworking.java +++ b/src/main/java/electrosphere/net/client/ClientNetworking.java @@ -18,6 +18,11 @@ import java.util.concurrent.TimeUnit; * Client networking thread */ public class ClientNetworking implements Runnable { + + /** + * Milliseconds after which reading is considered slow enough to be warning-worthy + */ + static final int SOCKET_READ_WARNING_THRESHOLD = 1000; /** * The server's address @@ -62,7 +67,7 @@ public class ClientNetworking implements Runnable { //thresholds for when to send pings and to determine when we've disconnected static final long SEND_PING_THRESHOLD = 3000; - static final long PING_DISCONNECT_THRESHOLD = 20000; + static final long PING_DISCONNECT_THRESHOLD = 60 * 1000; //times for calculating ping-pong long lastPingTime = 0; long lastPongTime = 0; @@ -174,18 +179,43 @@ public class ClientNetworking implements Runnable { //start parsing messages initialized = true; while(Globals.threadManager.shouldKeepRunning() && !this.shouldDisconnect){ + + // //attempt poll incoming messages - parser.readMessagesIn(); + long readStart = System.currentTimeMillis(); + try { + parser.readMessagesIn(); + } catch (IOException e) { + LoggerInterface.loggerNetworking.ERROR(e); + } + if(System.currentTimeMillis() - readStart > SOCKET_READ_WARNING_THRESHOLD){ + LoggerInterface.loggerNetworking.WARNING("Client is slow to read from network! Delay: " + (System.currentTimeMillis() - readStart) + " Number of total bytes read(mb): " + (parser.getNumberOfBytesRead() / 1024 / 1024)); + } + + + + // //outgoing messages try { parser.pushMessagesOut(); } catch(IOException e){ LoggerInterface.loggerNetworking.ERROR(e); } - //parses messages asynchronously - this.parseMessagesAsynchronously(); - //ping logic + + + + // + //parses messages asynchronously + boolean foundMessages = this.parseMessagesAsynchronously(); + + + + //timeout logic + //if received message from server, can't have timed out + if(foundMessages){ + this.markReceivedPongMessage(); + } long currentTime = System.currentTimeMillis(); //basically if we haven't sent a ping in a while, send one if(currentTime - lastPingTime > SEND_PING_THRESHOLD){ @@ -228,7 +258,8 @@ public class ClientNetworking implements Runnable { /** * Parses messages asynchronously */ - public void parseMessagesAsynchronously(){ + public boolean parseMessagesAsynchronously(){ + boolean foundMessages = false; if(initialized){ while(parser.hasIncomingMessaage()){ NetworkMessage message = parser.popIncomingMessage(); @@ -241,9 +272,11 @@ public class ClientNetworking implements Runnable { //do something Globals.profiler.beginCpuSample("ClientProtocol.handleMessage"); this.messageProtocol.handleAsyncMessage(message); + foundMessages = true; Globals.profiler.endCpuSample(); } } + return foundMessages; } @@ -314,6 +347,17 @@ public class ClientNetworking implements Runnable { public boolean isInitialized(){ return initialized; } + + /** + * Gets the total number of bytes read by this connection + * @return The total number of bytes + */ + public long getNumBytesRead(){ + if(this.parser == null){ + return 0; + } + return this.parser.getNumberOfBytesRead(); + } } diff --git a/src/main/java/electrosphere/net/client/MessageProtocol.java b/src/main/java/electrosphere/net/client/MessageProtocol.java index 9612bcf4..83b06c3f 100644 --- a/src/main/java/electrosphere/net/client/MessageProtocol.java +++ b/src/main/java/electrosphere/net/client/MessageProtocol.java @@ -97,9 +97,11 @@ public class MessageProtocol { } //queue bounced messages for synchronous resolution if(result != null){ + Globals.profiler.beginAggregateCpuSample("MessageProtocol(client) Await lock to synchronize message"); this.synchronousMessageLock.acquireUninterruptibly(); this.synchronousMessageQueue.add(result); this.synchronousMessageLock.release(); + Globals.profiler.endCpuSample(); } Globals.profiler.endCpuSample(); } diff --git a/src/main/java/electrosphere/net/client/protocol/TerrainProtocol.java b/src/main/java/electrosphere/net/client/protocol/TerrainProtocol.java index bab65358..cdd606f2 100644 --- a/src/main/java/electrosphere/net/client/protocol/TerrainProtocol.java +++ b/src/main/java/electrosphere/net/client/protocol/TerrainProtocol.java @@ -34,7 +34,6 @@ public class TerrainProtocol implements ClientProtocolTemplate { //Vector3f worldMinPoint, Vector3f worldMaxPoint, int dynamicInterpolationRatio, float randomDampener, int worldDiscreteSize new Vector3f(message.getworldMinX(),0,message.getworldMinY()), new Vector3f(message.getworldMaxX(),32,message.getworldMaxY()), - ChunkData.CHUNK_SIZE, message.getrandomDampener(), message.getworldSizeDiscrete() ); @@ -44,9 +43,10 @@ public class TerrainProtocol implements ClientProtocolTemplate { case SPAWNPOSITION: LoggerInterface.loggerNetworking.WARNING("Received spawnPosition packet on client. This is deprecated!"); break; - case SENDCHUNKDATA: + case SENDCHUNKDATA: { + LoggerInterface.loggerNetworking.DEBUG("(Client) Received terrain at " + message.getworldX() + " " + message.getworldY() + " " + message.getworldZ()); Globals.clientTerrainManager.attachTerrainMessage(message); - break; + } break; case UPDATEVOXEL: { // //find what all drawcells might be updated by this voxel update @@ -97,9 +97,10 @@ public class TerrainProtocol implements ClientProtocolTemplate { if(Globals.clientTerrainManager.containsChunkDataAtWorldPoint(worldPosToUpdate.x, worldPosToUpdate.y, worldPosToUpdate.z)){ ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPosToUpdate.x, worldPosToUpdate.y, worldPosToUpdate.z); if(data != null){ - Globals.drawCellManager.markUpdateable(worldPosToUpdate.x, worldPosToUpdate.y, worldPosToUpdate.z); + Globals.clientDrawCellManager.markUpdateable(worldPosToUpdate.x, worldPosToUpdate.y, worldPosToUpdate.z); } } + Globals.clientFoliageManager.evaluateChunk(worldPos); } } break; case SENDFLUIDDATA: { diff --git a/src/main/java/electrosphere/net/parser/net/message/AuthMessage.java b/src/main/java/electrosphere/net/parser/net/message/AuthMessage.java index 411bdc32..9c41ad60 100644 --- a/src/main/java/electrosphere/net/parser/net/message/AuthMessage.java +++ b/src/main/java/electrosphere/net/parser/net/message/AuthMessage.java @@ -1,12 +1,15 @@ package electrosphere.net.parser.net.message; +import io.github.studiorailgun.CircularByteBuffer; import electrosphere.net.parser.util.ByteStreamUtils; -import electrosphere.net.parser.net.raw.CircularByteBuffer; import java.util.LinkedList; import java.util.List; public class AuthMessage extends NetworkMessage { + /** + * The types of messages available in this category. + */ public enum AuthMessageType { AUTHREQUEST, AUTHDETAILS, @@ -14,10 +17,17 @@ public class AuthMessage extends NetworkMessage { AUTHFAILURE, } + /** + * The type of this message in particular. + */ AuthMessageType messageType; String user; String pass; + /** + * Constructor + * @param messageType The type of this message + */ AuthMessage(AuthMessageType messageType){ this.type = MessageType.AUTH_MESSAGE; this.messageType = messageType; @@ -27,26 +37,48 @@ public class AuthMessage extends NetworkMessage { return this.messageType; } + /** + * Gets user + */ public String getuser() { return user; } + /** + * Sets user + */ public void setuser(String user) { this.user = user; } + /** + * Gets pass + */ public String getpass() { return pass; } + /** + * Sets pass + */ public void setpass(String pass) { this.pass = pass; } + /** + * Removes the packet header from the buffer + * @param byteBuffer The buffer + */ static void stripPacketHeader(CircularByteBuffer byteBuffer){ byteBuffer.read(2); } + /** + * Checks if this message can be parsed (ie are all bytes present) + * @param byteBuffer The buffer + * @param secondByte The second byte, signifying the subtype of the message + * @return true if the message can be parsed, false otherwise + */ public static boolean canParseMessage(CircularByteBuffer byteBuffer, byte secondByte){ switch(secondByte){ case TypeBytes.AUTH_MESSAGE_TYPE_AUTHREQUEST: @@ -73,18 +105,27 @@ public class AuthMessage extends NetworkMessage { return false; } + /** + * Parses a message of type AuthRequest + */ public static AuthMessage parseAuthRequestMessage(CircularByteBuffer byteBuffer){ AuthMessage rVal = new AuthMessage(AuthMessageType.AUTHREQUEST); stripPacketHeader(byteBuffer); return rVal; } + /** + * Constructs a message of type AuthRequest + */ public static AuthMessage constructAuthRequestMessage(){ AuthMessage rVal = new AuthMessage(AuthMessageType.AUTHREQUEST); rVal.serialize(); return rVal; } + /** + * Checks if a message of type AuthDetails can be parsed from the byte stream + */ public static boolean canParseAuthDetailsMessage(CircularByteBuffer byteBuffer){ int currentStreamLength = byteBuffer.getRemaining(); List temporaryByteQueue = new LinkedList(); @@ -117,6 +158,9 @@ public class AuthMessage extends NetworkMessage { return true; } + /** + * Parses a message of type AuthDetails + */ public static AuthMessage parseAuthDetailsMessage(CircularByteBuffer byteBuffer){ AuthMessage rVal = new AuthMessage(AuthMessageType.AUTHDETAILS); stripPacketHeader(byteBuffer); @@ -125,6 +169,9 @@ public class AuthMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type AuthDetails + */ public static AuthMessage constructAuthDetailsMessage(String user,String pass){ AuthMessage rVal = new AuthMessage(AuthMessageType.AUTHDETAILS); rVal.setuser(user); @@ -133,24 +180,36 @@ public class AuthMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type AuthSuccess + */ public static AuthMessage parseAuthSuccessMessage(CircularByteBuffer byteBuffer){ AuthMessage rVal = new AuthMessage(AuthMessageType.AUTHSUCCESS); stripPacketHeader(byteBuffer); return rVal; } + /** + * Constructs a message of type AuthSuccess + */ public static AuthMessage constructAuthSuccessMessage(){ AuthMessage rVal = new AuthMessage(AuthMessageType.AUTHSUCCESS); rVal.serialize(); return rVal; } + /** + * Parses a message of type AuthFailure + */ public static AuthMessage parseAuthFailureMessage(CircularByteBuffer byteBuffer){ AuthMessage rVal = new AuthMessage(AuthMessageType.AUTHFAILURE); stripPacketHeader(byteBuffer); return rVal; } + /** + * Constructs a message of type AuthFailure + */ public static AuthMessage constructAuthFailureMessage(){ AuthMessage rVal = new AuthMessage(AuthMessageType.AUTHFAILURE); rVal.serialize(); diff --git a/src/main/java/electrosphere/net/parser/net/message/CharacterMessage.java b/src/main/java/electrosphere/net/parser/net/message/CharacterMessage.java index 0dc24127..2c889f91 100644 --- a/src/main/java/electrosphere/net/parser/net/message/CharacterMessage.java +++ b/src/main/java/electrosphere/net/parser/net/message/CharacterMessage.java @@ -1,12 +1,15 @@ package electrosphere.net.parser.net.message; +import io.github.studiorailgun.CircularByteBuffer; import electrosphere.net.parser.util.ByteStreamUtils; -import electrosphere.net.parser.net.raw.CircularByteBuffer; import java.util.LinkedList; import java.util.List; public class CharacterMessage extends NetworkMessage { + /** + * The types of messages available in this category. + */ public enum CharacterMessageType { REQUESTCHARACTERLIST, RESPONSECHARACTERLIST, @@ -17,9 +20,16 @@ public class CharacterMessage extends NetworkMessage { RESPONSESPAWNCHARACTER, } + /** + * The type of this message in particular. + */ CharacterMessageType messageType; String data; + /** + * Constructor + * @param messageType The type of this message + */ CharacterMessage(CharacterMessageType messageType){ this.type = MessageType.CHARACTER_MESSAGE; this.messageType = messageType; @@ -29,18 +39,34 @@ public class CharacterMessage extends NetworkMessage { return this.messageType; } + /** + * Gets data + */ public String getdata() { return data; } + /** + * Sets data + */ public void setdata(String data) { this.data = data; } + /** + * Removes the packet header from the buffer + * @param byteBuffer The buffer + */ static void stripPacketHeader(CircularByteBuffer byteBuffer){ byteBuffer.read(2); } + /** + * Checks if this message can be parsed (ie are all bytes present) + * @param byteBuffer The buffer + * @param secondByte The second byte, signifying the subtype of the message + * @return true if the message can be parsed, false otherwise + */ public static boolean canParseMessage(CircularByteBuffer byteBuffer, byte secondByte){ switch(secondByte){ case TypeBytes.CHARACTER_MESSAGE_TYPE_REQUESTCHARACTERLIST: @@ -77,18 +103,27 @@ public class CharacterMessage extends NetworkMessage { return false; } + /** + * Parses a message of type RequestCharacterList + */ public static CharacterMessage parseRequestCharacterListMessage(CircularByteBuffer byteBuffer){ CharacterMessage rVal = new CharacterMessage(CharacterMessageType.REQUESTCHARACTERLIST); stripPacketHeader(byteBuffer); return rVal; } + /** + * Constructs a message of type RequestCharacterList + */ public static CharacterMessage constructRequestCharacterListMessage(){ CharacterMessage rVal = new CharacterMessage(CharacterMessageType.REQUESTCHARACTERLIST); rVal.serialize(); return rVal; } + /** + * Checks if a message of type ResponseCharacterList can be parsed from the byte stream + */ public static boolean canParseResponseCharacterListMessage(CircularByteBuffer byteBuffer){ int currentStreamLength = byteBuffer.getRemaining(); List temporaryByteQueue = new LinkedList(); @@ -108,6 +143,9 @@ public class CharacterMessage extends NetworkMessage { return true; } + /** + * Parses a message of type ResponseCharacterList + */ public static CharacterMessage parseResponseCharacterListMessage(CircularByteBuffer byteBuffer){ CharacterMessage rVal = new CharacterMessage(CharacterMessageType.RESPONSECHARACTERLIST); stripPacketHeader(byteBuffer); @@ -115,6 +153,9 @@ public class CharacterMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type ResponseCharacterList + */ public static CharacterMessage constructResponseCharacterListMessage(String data){ CharacterMessage rVal = new CharacterMessage(CharacterMessageType.RESPONSECHARACTERLIST); rVal.setdata(data); @@ -122,6 +163,9 @@ public class CharacterMessage extends NetworkMessage { return rVal; } + /** + * Checks if a message of type RequestCreateCharacter can be parsed from the byte stream + */ public static boolean canParseRequestCreateCharacterMessage(CircularByteBuffer byteBuffer){ int currentStreamLength = byteBuffer.getRemaining(); List temporaryByteQueue = new LinkedList(); @@ -141,6 +185,9 @@ public class CharacterMessage extends NetworkMessage { return true; } + /** + * Parses a message of type RequestCreateCharacter + */ public static CharacterMessage parseRequestCreateCharacterMessage(CircularByteBuffer byteBuffer){ CharacterMessage rVal = new CharacterMessage(CharacterMessageType.REQUESTCREATECHARACTER); stripPacketHeader(byteBuffer); @@ -148,6 +195,9 @@ public class CharacterMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type RequestCreateCharacter + */ public static CharacterMessage constructRequestCreateCharacterMessage(String data){ CharacterMessage rVal = new CharacterMessage(CharacterMessageType.REQUESTCREATECHARACTER); rVal.setdata(data); @@ -155,42 +205,63 @@ public class CharacterMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type ResponseCreateCharacterSuccess + */ public static CharacterMessage parseResponseCreateCharacterSuccessMessage(CircularByteBuffer byteBuffer){ CharacterMessage rVal = new CharacterMessage(CharacterMessageType.RESPONSECREATECHARACTERSUCCESS); stripPacketHeader(byteBuffer); return rVal; } + /** + * Constructs a message of type ResponseCreateCharacterSuccess + */ public static CharacterMessage constructResponseCreateCharacterSuccessMessage(){ CharacterMessage rVal = new CharacterMessage(CharacterMessageType.RESPONSECREATECHARACTERSUCCESS); rVal.serialize(); return rVal; } + /** + * Parses a message of type ResponseCreateCharacterFailure + */ public static CharacterMessage parseResponseCreateCharacterFailureMessage(CircularByteBuffer byteBuffer){ CharacterMessage rVal = new CharacterMessage(CharacterMessageType.RESPONSECREATECHARACTERFAILURE); stripPacketHeader(byteBuffer); return rVal; } + /** + * Constructs a message of type ResponseCreateCharacterFailure + */ public static CharacterMessage constructResponseCreateCharacterFailureMessage(){ CharacterMessage rVal = new CharacterMessage(CharacterMessageType.RESPONSECREATECHARACTERFAILURE); rVal.serialize(); return rVal; } + /** + * Parses a message of type RequestSpawnCharacter + */ public static CharacterMessage parseRequestSpawnCharacterMessage(CircularByteBuffer byteBuffer){ CharacterMessage rVal = new CharacterMessage(CharacterMessageType.REQUESTSPAWNCHARACTER); stripPacketHeader(byteBuffer); return rVal; } + /** + * Constructs a message of type RequestSpawnCharacter + */ public static CharacterMessage constructRequestSpawnCharacterMessage(){ CharacterMessage rVal = new CharacterMessage(CharacterMessageType.REQUESTSPAWNCHARACTER); rVal.serialize(); return rVal; } + /** + * Checks if a message of type ResponseSpawnCharacter can be parsed from the byte stream + */ public static boolean canParseResponseSpawnCharacterMessage(CircularByteBuffer byteBuffer){ int currentStreamLength = byteBuffer.getRemaining(); List temporaryByteQueue = new LinkedList(); @@ -210,6 +281,9 @@ public class CharacterMessage extends NetworkMessage { return true; } + /** + * Parses a message of type ResponseSpawnCharacter + */ public static CharacterMessage parseResponseSpawnCharacterMessage(CircularByteBuffer byteBuffer){ CharacterMessage rVal = new CharacterMessage(CharacterMessageType.RESPONSESPAWNCHARACTER); stripPacketHeader(byteBuffer); @@ -217,6 +291,9 @@ public class CharacterMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type ResponseSpawnCharacter + */ public static CharacterMessage constructResponseSpawnCharacterMessage(String data){ CharacterMessage rVal = new CharacterMessage(CharacterMessageType.RESPONSESPAWNCHARACTER); rVal.setdata(data); diff --git a/src/main/java/electrosphere/net/parser/net/message/CombatMessage.java b/src/main/java/electrosphere/net/parser/net/message/CombatMessage.java index 106fb361..8c546b02 100644 --- a/src/main/java/electrosphere/net/parser/net/message/CombatMessage.java +++ b/src/main/java/electrosphere/net/parser/net/message/CombatMessage.java @@ -1,16 +1,22 @@ package electrosphere.net.parser.net.message; +import io.github.studiorailgun.CircularByteBuffer; import electrosphere.net.parser.util.ByteStreamUtils; -import electrosphere.net.parser.net.raw.CircularByteBuffer; import java.util.LinkedList; import java.util.List; public class CombatMessage extends NetworkMessage { + /** + * The types of messages available in this category. + */ public enum CombatMessageType { SERVERREPORTHITBOXCOLLISION, } + /** + * The type of this message in particular. + */ CombatMessageType messageType; int entityID; int receiverEntityID; @@ -25,6 +31,10 @@ public class CombatMessage extends NetworkMessage { String hitboxType; String hurtboxType; + /** + * Constructor + * @param messageType The type of this message + */ CombatMessage(CombatMessageType messageType){ this.type = MessageType.COMBAT_MESSAGE; this.messageType = messageType; @@ -34,106 +44,188 @@ public class CombatMessage extends NetworkMessage { return this.messageType; } + /** + * Gets entityID + */ public int getentityID() { return entityID; } + /** + * Sets entityID + */ public void setentityID(int entityID) { this.entityID = entityID; } + /** + * Gets receiverEntityID + */ public int getreceiverEntityID() { return receiverEntityID; } + /** + * Sets receiverEntityID + */ public void setreceiverEntityID(int receiverEntityID) { this.receiverEntityID = receiverEntityID; } + /** + * Gets positionX + */ public double getpositionX() { return positionX; } + /** + * Sets positionX + */ public void setpositionX(double positionX) { this.positionX = positionX; } + /** + * Gets positionY + */ public double getpositionY() { return positionY; } + /** + * Sets positionY + */ public void setpositionY(double positionY) { this.positionY = positionY; } + /** + * Gets positionZ + */ public double getpositionZ() { return positionZ; } + /** + * Sets positionZ + */ public void setpositionZ(double positionZ) { this.positionZ = positionZ; } + /** + * Gets rotationX + */ public double getrotationX() { return rotationX; } + /** + * Sets rotationX + */ public void setrotationX(double rotationX) { this.rotationX = rotationX; } + /** + * Gets rotationY + */ public double getrotationY() { return rotationY; } + /** + * Sets rotationY + */ public void setrotationY(double rotationY) { this.rotationY = rotationY; } + /** + * Gets rotationZ + */ public double getrotationZ() { return rotationZ; } + /** + * Sets rotationZ + */ public void setrotationZ(double rotationZ) { this.rotationZ = rotationZ; } + /** + * Gets rotationW + */ public double getrotationW() { return rotationW; } + /** + * Sets rotationW + */ public void setrotationW(double rotationW) { this.rotationW = rotationW; } + /** + * Gets time + */ public long gettime() { return time; } + /** + * Sets time + */ public void settime(long time) { this.time = time; } + /** + * Gets hitboxType + */ public String gethitboxType() { return hitboxType; } + /** + * Sets hitboxType + */ public void sethitboxType(String hitboxType) { this.hitboxType = hitboxType; } + /** + * Gets hurtboxType + */ public String gethurtboxType() { return hurtboxType; } + /** + * Sets hurtboxType + */ public void sethurtboxType(String hurtboxType) { this.hurtboxType = hurtboxType; } + /** + * Removes the packet header from the buffer + * @param byteBuffer The buffer + */ static void stripPacketHeader(CircularByteBuffer byteBuffer){ byteBuffer.read(2); } + /** + * Checks if this message can be parsed (ie are all bytes present) + * @param byteBuffer The buffer + * @param secondByte The second byte, signifying the subtype of the message + * @return true if the message can be parsed, false otherwise + */ public static boolean canParseMessage(CircularByteBuffer byteBuffer, byte secondByte){ switch(secondByte){ case TypeBytes.COMBAT_MESSAGE_TYPE_SERVERREPORTHITBOXCOLLISION: @@ -142,6 +234,9 @@ public class CombatMessage extends NetworkMessage { return false; } + /** + * Checks if a message of type serverReportHitboxCollision can be parsed from the byte stream + */ public static boolean canParseserverReportHitboxCollisionMessage(CircularByteBuffer byteBuffer){ int currentStreamLength = byteBuffer.getRemaining(); List temporaryByteQueue = new LinkedList(); @@ -192,6 +287,9 @@ public class CombatMessage extends NetworkMessage { return true; } + /** + * Parses a message of type serverReportHitboxCollision + */ public static CombatMessage parseserverReportHitboxCollisionMessage(CircularByteBuffer byteBuffer){ CombatMessage rVal = new CombatMessage(CombatMessageType.SERVERREPORTHITBOXCOLLISION); stripPacketHeader(byteBuffer); @@ -206,6 +304,9 @@ public class CombatMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type serverReportHitboxCollision + */ public static CombatMessage constructserverReportHitboxCollisionMessage(int entityID,int receiverEntityID,long time,String hitboxType,String hurtboxType,double positionX,double positionY,double positionZ){ CombatMessage rVal = new CombatMessage(CombatMessageType.SERVERREPORTHITBOXCOLLISION); rVal.setentityID(entityID); diff --git a/src/main/java/electrosphere/net/parser/net/message/EntityMessage.java b/src/main/java/electrosphere/net/parser/net/message/EntityMessage.java index 0fda9679..11c01cfe 100644 --- a/src/main/java/electrosphere/net/parser/net/message/EntityMessage.java +++ b/src/main/java/electrosphere/net/parser/net/message/EntityMessage.java @@ -1,12 +1,15 @@ package electrosphere.net.parser.net.message; +import io.github.studiorailgun.CircularByteBuffer; import electrosphere.net.parser.util.ByteStreamUtils; -import electrosphere.net.parser.net.raw.CircularByteBuffer; import java.util.LinkedList; import java.util.List; public class EntityMessage extends NetworkMessage { + /** + * The types of messages available in this category. + */ public enum EntityMessageType { CREATE, MOVEUPDATE, @@ -20,6 +23,9 @@ public class EntityMessage extends NetworkMessage { SYNCPHYSICS, } + /** + * The type of this message in particular. + */ EntityMessageType messageType; int entityCategory; String entitySubtype; @@ -56,6 +62,10 @@ public class EntityMessage extends NetworkMessage { int bTreeID; int propertyValueInt; + /** + * Constructor + * @param messageType The type of this message + */ EntityMessage(EntityMessageType messageType){ this.type = MessageType.ENTITY_MESSAGE; this.messageType = messageType; @@ -65,282 +75,496 @@ public class EntityMessage extends NetworkMessage { return this.messageType; } + /** + * Gets entityCategory + */ public int getentityCategory() { return entityCategory; } + /** + * Sets entityCategory + */ public void setentityCategory(int entityCategory) { this.entityCategory = entityCategory; } + /** + * Gets entitySubtype + */ public String getentitySubtype() { return entitySubtype; } + /** + * Sets entitySubtype + */ public void setentitySubtype(String entitySubtype) { this.entitySubtype = entitySubtype; } + /** + * Gets entityID + */ public int getentityID() { return entityID; } + /** + * Sets entityID + */ public void setentityID(int entityID) { this.entityID = entityID; } + /** + * Gets creatureTemplate + */ public String getcreatureTemplate() { return creatureTemplate; } + /** + * Sets creatureTemplate + */ public void setcreatureTemplate(String creatureTemplate) { this.creatureTemplate = creatureTemplate; } + /** + * Gets positionX + */ public double getpositionX() { return positionX; } + /** + * Sets positionX + */ public void setpositionX(double positionX) { this.positionX = positionX; } + /** + * Gets positionY + */ public double getpositionY() { return positionY; } + /** + * Sets positionY + */ public void setpositionY(double positionY) { this.positionY = positionY; } + /** + * Gets positionZ + */ public double getpositionZ() { return positionZ; } + /** + * Sets positionZ + */ public void setpositionZ(double positionZ) { this.positionZ = positionZ; } + /** + * Gets rotationX + */ public double getrotationX() { return rotationX; } + /** + * Sets rotationX + */ public void setrotationX(double rotationX) { this.rotationX = rotationX; } + /** + * Gets rotationY + */ public double getrotationY() { return rotationY; } + /** + * Sets rotationY + */ public void setrotationY(double rotationY) { this.rotationY = rotationY; } + /** + * Gets rotationZ + */ public double getrotationZ() { return rotationZ; } + /** + * Sets rotationZ + */ public void setrotationZ(double rotationZ) { this.rotationZ = rotationZ; } + /** + * Gets rotationW + */ public double getrotationW() { return rotationW; } + /** + * Sets rotationW + */ public void setrotationW(double rotationW) { this.rotationW = rotationW; } + /** + * Gets linVelX + */ public double getlinVelX() { return linVelX; } + /** + * Sets linVelX + */ public void setlinVelX(double linVelX) { this.linVelX = linVelX; } + /** + * Gets linVelY + */ public double getlinVelY() { return linVelY; } + /** + * Sets linVelY + */ public void setlinVelY(double linVelY) { this.linVelY = linVelY; } + /** + * Gets linVelZ + */ public double getlinVelZ() { return linVelZ; } + /** + * Sets linVelZ + */ public void setlinVelZ(double linVelZ) { this.linVelZ = linVelZ; } + /** + * Gets angVelX + */ public double getangVelX() { return angVelX; } + /** + * Sets angVelX + */ public void setangVelX(double angVelX) { this.angVelX = angVelX; } + /** + * Gets angVelY + */ public double getangVelY() { return angVelY; } + /** + * Sets angVelY + */ public void setangVelY(double angVelY) { this.angVelY = angVelY; } + /** + * Gets angVelZ + */ public double getangVelZ() { return angVelZ; } + /** + * Sets angVelZ + */ public void setangVelZ(double angVelZ) { this.angVelZ = angVelZ; } + /** + * Gets linForceX + */ public double getlinForceX() { return linForceX; } + /** + * Sets linForceX + */ public void setlinForceX(double linForceX) { this.linForceX = linForceX; } + /** + * Gets linForceY + */ public double getlinForceY() { return linForceY; } + /** + * Sets linForceY + */ public void setlinForceY(double linForceY) { this.linForceY = linForceY; } + /** + * Gets linForceZ + */ public double getlinForceZ() { return linForceZ; } + /** + * Sets linForceZ + */ public void setlinForceZ(double linForceZ) { this.linForceZ = linForceZ; } + /** + * Gets angForceX + */ public double getangForceX() { return angForceX; } + /** + * Sets angForceX + */ public void setangForceX(double angForceX) { this.angForceX = angForceX; } + /** + * Gets angForceY + */ public double getangForceY() { return angForceY; } + /** + * Sets angForceY + */ public void setangForceY(double angForceY) { this.angForceY = angForceY; } + /** + * Gets angForceZ + */ public double getangForceZ() { return angForceZ; } + /** + * Sets angForceZ + */ public void setangForceZ(double angForceZ) { this.angForceZ = angForceZ; } + /** + * Gets yaw + */ public double getyaw() { return yaw; } + /** + * Sets yaw + */ public void setyaw(double yaw) { this.yaw = yaw; } + /** + * Gets pitch + */ public double getpitch() { return pitch; } + /** + * Sets pitch + */ public void setpitch(double pitch) { this.pitch = pitch; } + /** + * Gets velocity + */ public double getvelocity() { return velocity; } + /** + * Sets velocity + */ public void setvelocity(double velocity) { this.velocity = velocity; } + /** + * Gets treeState + */ public int gettreeState() { return treeState; } + /** + * Sets treeState + */ public void settreeState(int treeState) { this.treeState = treeState; } + /** + * Gets propertyType + */ public int getpropertyType() { return propertyType; } + /** + * Sets propertyType + */ public void setpropertyType(int propertyType) { this.propertyType = propertyType; } + /** + * Gets propertyValue + */ public int getpropertyValue() { return propertyValue; } + /** + * Sets propertyValue + */ public void setpropertyValue(int propertyValue) { this.propertyValue = propertyValue; } + /** + * Gets time + */ public long gettime() { return time; } + /** + * Sets time + */ public void settime(long time) { this.time = time; } + /** + * Gets bone + */ public String getbone() { return bone; } + /** + * Sets bone + */ public void setbone(String bone) { this.bone = bone; } + /** + * Gets targetID + */ public int gettargetID() { return targetID; } + /** + * Sets targetID + */ public void settargetID(int targetID) { this.targetID = targetID; } + /** + * Gets bTreeID + */ public int getbTreeID() { return bTreeID; } + /** + * Sets bTreeID + */ public void setbTreeID(int bTreeID) { this.bTreeID = bTreeID; } + /** + * Gets propertyValueInt + */ public int getpropertyValueInt() { return propertyValueInt; } + /** + * Sets propertyValueInt + */ public void setpropertyValueInt(int propertyValueInt) { this.propertyValueInt = propertyValueInt; } + /** + * Removes the packet header from the buffer + * @param byteBuffer The buffer + */ static void stripPacketHeader(CircularByteBuffer byteBuffer){ byteBuffer.read(2); } + /** + * Checks if this message can be parsed (ie are all bytes present) + * @param byteBuffer The buffer + * @param secondByte The second byte, signifying the subtype of the message + * @return true if the message can be parsed, false otherwise + */ public static boolean canParseMessage(CircularByteBuffer byteBuffer, byte secondByte){ switch(secondByte){ case TypeBytes.ENTITY_MESSAGE_TYPE_CREATE: @@ -399,6 +623,9 @@ public class EntityMessage extends NetworkMessage { return false; } + /** + * Checks if a message of type Create can be parsed from the byte stream + */ public static boolean canParseCreateMessage(CircularByteBuffer byteBuffer){ int currentStreamLength = byteBuffer.getRemaining(); List temporaryByteQueue = new LinkedList(); @@ -458,6 +685,9 @@ public class EntityMessage extends NetworkMessage { return true; } + /** + * Parses a message of type Create + */ public static EntityMessage parseCreateMessage(CircularByteBuffer byteBuffer){ EntityMessage rVal = new EntityMessage(EntityMessageType.CREATE); stripPacketHeader(byteBuffer); @@ -475,6 +705,9 @@ public class EntityMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type Create + */ public static EntityMessage constructCreateMessage(int entityID,int entityCategory,String entitySubtype,String creatureTemplate,double positionX,double positionY,double positionZ,double rotationX,double rotationY,double rotationZ,double rotationW){ EntityMessage rVal = new EntityMessage(EntityMessageType.CREATE); rVal.setentityID(entityID); @@ -492,6 +725,9 @@ public class EntityMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type moveUpdate + */ public static EntityMessage parsemoveUpdateMessage(CircularByteBuffer byteBuffer){ EntityMessage rVal = new EntityMessage(EntityMessageType.MOVEUPDATE); stripPacketHeader(byteBuffer); @@ -510,6 +746,9 @@ public class EntityMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type moveUpdate + */ public static EntityMessage constructmoveUpdateMessage(int entityID,long time,double positionX,double positionY,double positionZ,double rotationX,double rotationY,double rotationZ,double rotationW,double velocity,int propertyValueInt,int treeState){ EntityMessage rVal = new EntityMessage(EntityMessageType.MOVEUPDATE); rVal.setentityID(entityID); @@ -528,6 +767,9 @@ public class EntityMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type attackUpdate + */ public static EntityMessage parseattackUpdateMessage(CircularByteBuffer byteBuffer){ EntityMessage rVal = new EntityMessage(EntityMessageType.ATTACKUPDATE); stripPacketHeader(byteBuffer); @@ -544,6 +786,9 @@ public class EntityMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type attackUpdate + */ public static EntityMessage constructattackUpdateMessage(int entityID,long time,double positionX,double positionY,double positionZ,double rotationX,double rotationY,double rotationZ,double velocity,int treeState){ EntityMessage rVal = new EntityMessage(EntityMessageType.ATTACKUPDATE); rVal.setentityID(entityID); @@ -560,18 +805,27 @@ public class EntityMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type startAttack + */ public static EntityMessage parsestartAttackMessage(CircularByteBuffer byteBuffer){ EntityMessage rVal = new EntityMessage(EntityMessageType.STARTATTACK); stripPacketHeader(byteBuffer); return rVal; } + /** + * Constructs a message of type startAttack + */ public static EntityMessage constructstartAttackMessage(){ EntityMessage rVal = new EntityMessage(EntityMessageType.STARTATTACK); rVal.serialize(); return rVal; } + /** + * Parses a message of type Kill + */ public static EntityMessage parseKillMessage(CircularByteBuffer byteBuffer){ EntityMessage rVal = new EntityMessage(EntityMessageType.KILL); stripPacketHeader(byteBuffer); @@ -580,6 +834,9 @@ public class EntityMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type Kill + */ public static EntityMessage constructKillMessage(long time,int entityID){ EntityMessage rVal = new EntityMessage(EntityMessageType.KILL); rVal.settime(time); @@ -588,6 +845,9 @@ public class EntityMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type Destroy + */ public static EntityMessage parseDestroyMessage(CircularByteBuffer byteBuffer){ EntityMessage rVal = new EntityMessage(EntityMessageType.DESTROY); stripPacketHeader(byteBuffer); @@ -595,6 +855,9 @@ public class EntityMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type Destroy + */ public static EntityMessage constructDestroyMessage(int entityID){ EntityMessage rVal = new EntityMessage(EntityMessageType.DESTROY); rVal.setentityID(entityID); @@ -602,6 +865,9 @@ public class EntityMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type setProperty + */ public static EntityMessage parsesetPropertyMessage(CircularByteBuffer byteBuffer){ EntityMessage rVal = new EntityMessage(EntityMessageType.SETPROPERTY); stripPacketHeader(byteBuffer); @@ -612,6 +878,9 @@ public class EntityMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type setProperty + */ public static EntityMessage constructsetPropertyMessage(int entityID,long time,int propertyType,int propertyValue){ EntityMessage rVal = new EntityMessage(EntityMessageType.SETPROPERTY); rVal.setentityID(entityID); @@ -622,6 +891,9 @@ public class EntityMessage extends NetworkMessage { return rVal; } + /** + * Checks if a message of type attachEntityToEntity can be parsed from the byte stream + */ public static boolean canParseattachEntityToEntityMessage(CircularByteBuffer byteBuffer){ int currentStreamLength = byteBuffer.getRemaining(); List temporaryByteQueue = new LinkedList(); @@ -647,6 +919,9 @@ public class EntityMessage extends NetworkMessage { return true; } + /** + * Parses a message of type attachEntityToEntity + */ public static EntityMessage parseattachEntityToEntityMessage(CircularByteBuffer byteBuffer){ EntityMessage rVal = new EntityMessage(EntityMessageType.ATTACHENTITYTOENTITY); stripPacketHeader(byteBuffer); @@ -656,6 +931,9 @@ public class EntityMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type attachEntityToEntity + */ public static EntityMessage constructattachEntityToEntityMessage(int entityID,String bone,int targetID){ EntityMessage rVal = new EntityMessage(EntityMessageType.ATTACHENTITYTOENTITY); rVal.setentityID(entityID); @@ -665,6 +943,9 @@ public class EntityMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type updateEntityViewDir + */ public static EntityMessage parseupdateEntityViewDirMessage(CircularByteBuffer byteBuffer){ EntityMessage rVal = new EntityMessage(EntityMessageType.UPDATEENTITYVIEWDIR); stripPacketHeader(byteBuffer); @@ -676,6 +957,9 @@ public class EntityMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type updateEntityViewDir + */ public static EntityMessage constructupdateEntityViewDirMessage(int entityID,long time,int propertyType,double yaw,double pitch){ EntityMessage rVal = new EntityMessage(EntityMessageType.UPDATEENTITYVIEWDIR); rVal.setentityID(entityID); @@ -687,6 +971,9 @@ public class EntityMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type syncPhysics + */ public static EntityMessage parsesyncPhysicsMessage(CircularByteBuffer byteBuffer){ EntityMessage rVal = new EntityMessage(EntityMessageType.SYNCPHYSICS); stripPacketHeader(byteBuffer); @@ -714,6 +1001,9 @@ public class EntityMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type syncPhysics + */ public static EntityMessage constructsyncPhysicsMessage(int entityID,long time,double positionX,double positionY,double positionZ,double rotationX,double rotationY,double rotationZ,double rotationW,double linVelX,double linVelY,double linVelZ,double angVelX,double angVelY,double angVelZ,double linForceX,double linForceY,double linForceZ,double angForceX,double angForceY,double angForceZ){ EntityMessage rVal = new EntityMessage(EntityMessageType.SYNCPHYSICS); rVal.setentityID(entityID); diff --git a/src/main/java/electrosphere/net/parser/net/message/InventoryMessage.java b/src/main/java/electrosphere/net/parser/net/message/InventoryMessage.java index fdd62d78..18ab2cdb 100644 --- a/src/main/java/electrosphere/net/parser/net/message/InventoryMessage.java +++ b/src/main/java/electrosphere/net/parser/net/message/InventoryMessage.java @@ -1,12 +1,15 @@ package electrosphere.net.parser.net.message; +import io.github.studiorailgun.CircularByteBuffer; import electrosphere.net.parser.util.ByteStreamUtils; -import electrosphere.net.parser.net.raw.CircularByteBuffer; import java.util.LinkedList; import java.util.List; public class InventoryMessage extends NetworkMessage { + /** + * The types of messages available in this category. + */ public enum InventoryMessageType { ADDITEMTOINVENTORY, REMOVEITEMFROMINVENTORY, @@ -21,6 +24,9 @@ public class InventoryMessage extends NetworkMessage { CLIENTREQUESTPERFORMITEMACTION, } + /** + * The type of this message in particular. + */ InventoryMessageType messageType; String itemTemplate; String equipPointId; @@ -31,6 +37,10 @@ public class InventoryMessage extends NetworkMessage { int itemActionCode; int itemActionCodeState; + /** + * Constructor + * @param messageType The type of this message + */ InventoryMessage(InventoryMessageType messageType){ this.type = MessageType.INVENTORY_MESSAGE; this.messageType = messageType; @@ -40,74 +50,132 @@ public class InventoryMessage extends NetworkMessage { return this.messageType; } + /** + * Gets itemTemplate + */ public String getitemTemplate() { return itemTemplate; } + /** + * Sets itemTemplate + */ public void setitemTemplate(String itemTemplate) { this.itemTemplate = itemTemplate; } + /** + * Gets equipPointId + */ public String getequipPointId() { return equipPointId; } + /** + * Sets equipPointId + */ public void setequipPointId(String equipPointId) { this.equipPointId = equipPointId; } + /** + * Gets entityId + */ public int getentityId() { return entityId; } + /** + * Sets entityId + */ public void setentityId(int entityId) { this.entityId = entityId; } + /** + * Gets equipperId + */ public int getequipperId() { return equipperId; } + /** + * Sets equipperId + */ public void setequipperId(int equipperId) { this.equipperId = equipperId; } + /** + * Gets containerType + */ public int getcontainerType() { return containerType; } + /** + * Sets containerType + */ public void setcontainerType(int containerType) { this.containerType = containerType; } + /** + * Gets toolbarId + */ public int gettoolbarId() { return toolbarId; } + /** + * Sets toolbarId + */ public void settoolbarId(int toolbarId) { this.toolbarId = toolbarId; } + /** + * Gets itemActionCode + */ public int getitemActionCode() { return itemActionCode; } + /** + * Sets itemActionCode + */ public void setitemActionCode(int itemActionCode) { this.itemActionCode = itemActionCode; } + /** + * Gets itemActionCodeState + */ public int getitemActionCodeState() { return itemActionCodeState; } + /** + * Sets itemActionCodeState + */ public void setitemActionCodeState(int itemActionCodeState) { this.itemActionCodeState = itemActionCodeState; } + /** + * Removes the packet header from the buffer + * @param byteBuffer The buffer + */ static void stripPacketHeader(CircularByteBuffer byteBuffer){ byteBuffer.read(2); } + /** + * Checks if this message can be parsed (ie are all bytes present) + * @param byteBuffer The buffer + * @param secondByte The second byte, signifying the subtype of the message + * @return true if the message can be parsed, false otherwise + */ public static boolean canParseMessage(CircularByteBuffer byteBuffer, byte secondByte){ switch(secondByte){ case TypeBytes.INVENTORY_MESSAGE_TYPE_ADDITEMTOINVENTORY: @@ -152,6 +220,9 @@ public class InventoryMessage extends NetworkMessage { return false; } + /** + * Checks if a message of type addItemToInventory can be parsed from the byte stream + */ public static boolean canParseaddItemToInventoryMessage(CircularByteBuffer byteBuffer){ int currentStreamLength = byteBuffer.getRemaining(); List temporaryByteQueue = new LinkedList(); @@ -174,6 +245,9 @@ public class InventoryMessage extends NetworkMessage { return true; } + /** + * Parses a message of type addItemToInventory + */ public static InventoryMessage parseaddItemToInventoryMessage(CircularByteBuffer byteBuffer){ InventoryMessage rVal = new InventoryMessage(InventoryMessageType.ADDITEMTOINVENTORY); stripPacketHeader(byteBuffer); @@ -182,6 +256,9 @@ public class InventoryMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type addItemToInventory + */ public static InventoryMessage constructaddItemToInventoryMessage(int entityId,String itemTemplate){ InventoryMessage rVal = new InventoryMessage(InventoryMessageType.ADDITEMTOINVENTORY); rVal.setentityId(entityId); @@ -190,6 +267,9 @@ public class InventoryMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type removeItemFromInventory + */ public static InventoryMessage parseremoveItemFromInventoryMessage(CircularByteBuffer byteBuffer){ InventoryMessage rVal = new InventoryMessage(InventoryMessageType.REMOVEITEMFROMINVENTORY); stripPacketHeader(byteBuffer); @@ -197,6 +277,9 @@ public class InventoryMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type removeItemFromInventory + */ public static InventoryMessage constructremoveItemFromInventoryMessage(int entityId){ InventoryMessage rVal = new InventoryMessage(InventoryMessageType.REMOVEITEMFROMINVENTORY); rVal.setentityId(entityId); @@ -204,6 +287,9 @@ public class InventoryMessage extends NetworkMessage { return rVal; } + /** + * Checks if a message of type clientRequestEquipItem can be parsed from the byte stream + */ public static boolean canParseclientRequestEquipItemMessage(CircularByteBuffer byteBuffer){ int currentStreamLength = byteBuffer.getRemaining(); List temporaryByteQueue = new LinkedList(); @@ -226,6 +312,9 @@ public class InventoryMessage extends NetworkMessage { return true; } + /** + * Parses a message of type clientRequestEquipItem + */ public static InventoryMessage parseclientRequestEquipItemMessage(CircularByteBuffer byteBuffer){ InventoryMessage rVal = new InventoryMessage(InventoryMessageType.CLIENTREQUESTEQUIPITEM); stripPacketHeader(byteBuffer); @@ -234,6 +323,9 @@ public class InventoryMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type clientRequestEquipItem + */ public static InventoryMessage constructclientRequestEquipItemMessage(String equipPointId,int entityId){ InventoryMessage rVal = new InventoryMessage(InventoryMessageType.CLIENTREQUESTEQUIPITEM); rVal.setequipPointId(equipPointId); @@ -242,6 +334,9 @@ public class InventoryMessage extends NetworkMessage { return rVal; } + /** + * Checks if a message of type serverCommandMoveItemContainer can be parsed from the byte stream + */ public static boolean canParseserverCommandMoveItemContainerMessage(CircularByteBuffer byteBuffer){ int currentStreamLength = byteBuffer.getRemaining(); List temporaryByteQueue = new LinkedList(); @@ -267,6 +362,9 @@ public class InventoryMessage extends NetworkMessage { return true; } + /** + * Parses a message of type serverCommandMoveItemContainer + */ public static InventoryMessage parseserverCommandMoveItemContainerMessage(CircularByteBuffer byteBuffer){ InventoryMessage rVal = new InventoryMessage(InventoryMessageType.SERVERCOMMANDMOVEITEMCONTAINER); stripPacketHeader(byteBuffer); @@ -276,6 +374,9 @@ public class InventoryMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type serverCommandMoveItemContainer + */ public static InventoryMessage constructserverCommandMoveItemContainerMessage(int entityId,int containerType,String equipPointId){ InventoryMessage rVal = new InventoryMessage(InventoryMessageType.SERVERCOMMANDMOVEITEMCONTAINER); rVal.setentityId(entityId); @@ -285,6 +386,9 @@ public class InventoryMessage extends NetworkMessage { return rVal; } + /** + * Checks if a message of type serverCommandEquipItem can be parsed from the byte stream + */ public static boolean canParseserverCommandEquipItemMessage(CircularByteBuffer byteBuffer){ int currentStreamLength = byteBuffer.getRemaining(); List temporaryByteQueue = new LinkedList(); @@ -326,6 +430,9 @@ public class InventoryMessage extends NetworkMessage { return true; } + /** + * Parses a message of type serverCommandEquipItem + */ public static InventoryMessage parseserverCommandEquipItemMessage(CircularByteBuffer byteBuffer){ InventoryMessage rVal = new InventoryMessage(InventoryMessageType.SERVERCOMMANDEQUIPITEM); stripPacketHeader(byteBuffer); @@ -337,6 +444,9 @@ public class InventoryMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type serverCommandEquipItem + */ public static InventoryMessage constructserverCommandEquipItemMessage(int equipperId,int containerType,String equipPointId,int entityId,String itemTemplate){ InventoryMessage rVal = new InventoryMessage(InventoryMessageType.SERVERCOMMANDEQUIPITEM); rVal.setequipperId(equipperId); @@ -348,6 +458,9 @@ public class InventoryMessage extends NetworkMessage { return rVal; } + /** + * Checks if a message of type serverCommandUnequipItem can be parsed from the byte stream + */ public static boolean canParseserverCommandUnequipItemMessage(CircularByteBuffer byteBuffer){ int currentStreamLength = byteBuffer.getRemaining(); List temporaryByteQueue = new LinkedList(); @@ -373,6 +486,9 @@ public class InventoryMessage extends NetworkMessage { return true; } + /** + * Parses a message of type serverCommandUnequipItem + */ public static InventoryMessage parseserverCommandUnequipItemMessage(CircularByteBuffer byteBuffer){ InventoryMessage rVal = new InventoryMessage(InventoryMessageType.SERVERCOMMANDUNEQUIPITEM); stripPacketHeader(byteBuffer); @@ -382,6 +498,9 @@ public class InventoryMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type serverCommandUnequipItem + */ public static InventoryMessage constructserverCommandUnequipItemMessage(int equipperId,int containerType,String equipPointId){ InventoryMessage rVal = new InventoryMessage(InventoryMessageType.SERVERCOMMANDUNEQUIPITEM); rVal.setequipperId(equipperId); @@ -391,6 +510,9 @@ public class InventoryMessage extends NetworkMessage { return rVal; } + /** + * Checks if a message of type clientRequestUnequipItem can be parsed from the byte stream + */ public static boolean canParseclientRequestUnequipItemMessage(CircularByteBuffer byteBuffer){ int currentStreamLength = byteBuffer.getRemaining(); List temporaryByteQueue = new LinkedList(); @@ -410,6 +532,9 @@ public class InventoryMessage extends NetworkMessage { return true; } + /** + * Parses a message of type clientRequestUnequipItem + */ public static InventoryMessage parseclientRequestUnequipItemMessage(CircularByteBuffer byteBuffer){ InventoryMessage rVal = new InventoryMessage(InventoryMessageType.CLIENTREQUESTUNEQUIPITEM); stripPacketHeader(byteBuffer); @@ -417,6 +542,9 @@ public class InventoryMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type clientRequestUnequipItem + */ public static InventoryMessage constructclientRequestUnequipItemMessage(String equipPointId){ InventoryMessage rVal = new InventoryMessage(InventoryMessageType.CLIENTREQUESTUNEQUIPITEM); rVal.setequipPointId(equipPointId); @@ -424,6 +552,9 @@ public class InventoryMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type clientRequestAddToolbar + */ public static InventoryMessage parseclientRequestAddToolbarMessage(CircularByteBuffer byteBuffer){ InventoryMessage rVal = new InventoryMessage(InventoryMessageType.CLIENTREQUESTADDTOOLBAR); stripPacketHeader(byteBuffer); @@ -432,6 +563,9 @@ public class InventoryMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type clientRequestAddToolbar + */ public static InventoryMessage constructclientRequestAddToolbarMessage(int entityId,int toolbarId){ InventoryMessage rVal = new InventoryMessage(InventoryMessageType.CLIENTREQUESTADDTOOLBAR); rVal.setentityId(entityId); @@ -440,6 +574,9 @@ public class InventoryMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type clientRequestAddNatural + */ public static InventoryMessage parseclientRequestAddNaturalMessage(CircularByteBuffer byteBuffer){ InventoryMessage rVal = new InventoryMessage(InventoryMessageType.CLIENTREQUESTADDNATURAL); stripPacketHeader(byteBuffer); @@ -447,6 +584,9 @@ public class InventoryMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type clientRequestAddNatural + */ public static InventoryMessage constructclientRequestAddNaturalMessage(int entityId){ InventoryMessage rVal = new InventoryMessage(InventoryMessageType.CLIENTREQUESTADDNATURAL); rVal.setentityId(entityId); @@ -454,6 +594,9 @@ public class InventoryMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type clientUpdateToolbar + */ public static InventoryMessage parseclientUpdateToolbarMessage(CircularByteBuffer byteBuffer){ InventoryMessage rVal = new InventoryMessage(InventoryMessageType.CLIENTUPDATETOOLBAR); stripPacketHeader(byteBuffer); @@ -461,6 +604,9 @@ public class InventoryMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type clientUpdateToolbar + */ public static InventoryMessage constructclientUpdateToolbarMessage(int toolbarId){ InventoryMessage rVal = new InventoryMessage(InventoryMessageType.CLIENTUPDATETOOLBAR); rVal.settoolbarId(toolbarId); @@ -468,6 +614,9 @@ public class InventoryMessage extends NetworkMessage { return rVal; } + /** + * Checks if a message of type clientRequestPerformItemAction can be parsed from the byte stream + */ public static boolean canParseclientRequestPerformItemActionMessage(CircularByteBuffer byteBuffer){ int currentStreamLength = byteBuffer.getRemaining(); List temporaryByteQueue = new LinkedList(); @@ -493,6 +642,9 @@ public class InventoryMessage extends NetworkMessage { return true; } + /** + * Parses a message of type clientRequestPerformItemAction + */ public static InventoryMessage parseclientRequestPerformItemActionMessage(CircularByteBuffer byteBuffer){ InventoryMessage rVal = new InventoryMessage(InventoryMessageType.CLIENTREQUESTPERFORMITEMACTION); stripPacketHeader(byteBuffer); @@ -502,6 +654,9 @@ public class InventoryMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type clientRequestPerformItemAction + */ public static InventoryMessage constructclientRequestPerformItemActionMessage(String equipPointId,int itemActionCode,int itemActionCodeState){ InventoryMessage rVal = new InventoryMessage(InventoryMessageType.CLIENTREQUESTPERFORMITEMACTION); rVal.setequipPointId(equipPointId); diff --git a/src/main/java/electrosphere/net/parser/net/message/LoreMessage.java b/src/main/java/electrosphere/net/parser/net/message/LoreMessage.java index 0232c523..d2f86345 100644 --- a/src/main/java/electrosphere/net/parser/net/message/LoreMessage.java +++ b/src/main/java/electrosphere/net/parser/net/message/LoreMessage.java @@ -1,20 +1,30 @@ package electrosphere.net.parser.net.message; +import io.github.studiorailgun.CircularByteBuffer; import electrosphere.net.parser.util.ByteStreamUtils; -import electrosphere.net.parser.net.raw.CircularByteBuffer; import java.util.LinkedList; import java.util.List; public class LoreMessage extends NetworkMessage { + /** + * The types of messages available in this category. + */ public enum LoreMessageType { REQUESTRACES, RESPONSERACES, } + /** + * The type of this message in particular. + */ LoreMessageType messageType; String data; + /** + * Constructor + * @param messageType The type of this message + */ LoreMessage(LoreMessageType messageType){ this.type = MessageType.LORE_MESSAGE; this.messageType = messageType; @@ -24,18 +34,34 @@ public class LoreMessage extends NetworkMessage { return this.messageType; } + /** + * Gets data + */ public String getdata() { return data; } + /** + * Sets data + */ public void setdata(String data) { this.data = data; } + /** + * Removes the packet header from the buffer + * @param byteBuffer The buffer + */ static void stripPacketHeader(CircularByteBuffer byteBuffer){ byteBuffer.read(2); } + /** + * Checks if this message can be parsed (ie are all bytes present) + * @param byteBuffer The buffer + * @param secondByte The second byte, signifying the subtype of the message + * @return true if the message can be parsed, false otherwise + */ public static boolean canParseMessage(CircularByteBuffer byteBuffer, byte secondByte){ switch(secondByte){ case TypeBytes.LORE_MESSAGE_TYPE_REQUESTRACES: @@ -50,18 +76,27 @@ public class LoreMessage extends NetworkMessage { return false; } + /** + * Parses a message of type RequestRaces + */ public static LoreMessage parseRequestRacesMessage(CircularByteBuffer byteBuffer){ LoreMessage rVal = new LoreMessage(LoreMessageType.REQUESTRACES); stripPacketHeader(byteBuffer); return rVal; } + /** + * Constructs a message of type RequestRaces + */ public static LoreMessage constructRequestRacesMessage(){ LoreMessage rVal = new LoreMessage(LoreMessageType.REQUESTRACES); rVal.serialize(); return rVal; } + /** + * Checks if a message of type ResponseRaces can be parsed from the byte stream + */ public static boolean canParseResponseRacesMessage(CircularByteBuffer byteBuffer){ int currentStreamLength = byteBuffer.getRemaining(); List temporaryByteQueue = new LinkedList(); @@ -81,6 +116,9 @@ public class LoreMessage extends NetworkMessage { return true; } + /** + * Parses a message of type ResponseRaces + */ public static LoreMessage parseResponseRacesMessage(CircularByteBuffer byteBuffer){ LoreMessage rVal = new LoreMessage(LoreMessageType.RESPONSERACES); stripPacketHeader(byteBuffer); @@ -88,6 +126,9 @@ public class LoreMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type ResponseRaces + */ public static LoreMessage constructResponseRacesMessage(String data){ LoreMessage rVal = new LoreMessage(LoreMessageType.RESPONSERACES); rVal.setdata(data); diff --git a/src/main/java/electrosphere/net/parser/net/message/NetworkMessage.java b/src/main/java/electrosphere/net/parser/net/message/NetworkMessage.java index 9101878d..6e44b898 100644 --- a/src/main/java/electrosphere/net/parser/net/message/NetworkMessage.java +++ b/src/main/java/electrosphere/net/parser/net/message/NetworkMessage.java @@ -1,37 +1,64 @@ package electrosphere.net.parser.net.message; -import electrosphere.net.parser.util.ByteStreamUtils; -import electrosphere.net.parser.net.raw.CircularByteBuffer; - -import java.util.List; +import io.github.studiorailgun.CircularByteBuffer; +/** + * A network message + */ public abstract class NetworkMessage { + /** + * The different categories of network messages + */ public enum MessageType { -ENTITY_MESSAGE, -LORE_MESSAGE, -PLAYER_MESSAGE, -TERRAIN_MESSAGE, -SERVER_MESSAGE, -AUTH_MESSAGE, -CHARACTER_MESSAGE, -INVENTORY_MESSAGE, -SYNCHRONIZATION_MESSAGE, -COMBAT_MESSAGE, + ENTITY_MESSAGE, + LORE_MESSAGE, + PLAYER_MESSAGE, + TERRAIN_MESSAGE, + SERVER_MESSAGE, + AUTH_MESSAGE, + CHARACTER_MESSAGE, + INVENTORY_MESSAGE, + SYNCHRONIZATION_MESSAGE, + COMBAT_MESSAGE, } + /** + * The type of this message + */ MessageType type; - boolean serialized; // has this message been converted to bytes? + + /** + * Tracks whether the message has been serialized to bytes or not + */ + boolean serialized; + + /** + * The raw bytes contained in the message + */ byte[] rawBytes; + /** + * Gets the type of the message + * @return The type of the message + */ public MessageType getType() { return type; } + /** + * Gets the raw bytes of the message + * @return The raw bytes + */ public byte[] getRawBytes() { return rawBytes; } + /** + * Parses the byte stream for the next message + * @param byteBuffer The byte buffer + * @return The message if one is at the front of the byte stream, null otherwise + */ public static NetworkMessage parseBytestreamForMessage(CircularByteBuffer byteBuffer){ NetworkMessage rVal = null; byte firstByte; @@ -409,10 +436,17 @@ COMBAT_MESSAGE, return rVal; } + /** + * Checks if this message is serialized or not + * @return true if it is serialized, false otherwise + */ public boolean isSerialized(){ return serialized; } + /** + * Serializes the message + */ abstract void serialize(); } diff --git a/src/main/java/electrosphere/net/parser/net/message/PlayerMessage.java b/src/main/java/electrosphere/net/parser/net/message/PlayerMessage.java index f2b9f931..5f6def56 100644 --- a/src/main/java/electrosphere/net/parser/net/message/PlayerMessage.java +++ b/src/main/java/electrosphere/net/parser/net/message/PlayerMessage.java @@ -1,23 +1,30 @@ package electrosphere.net.parser.net.message; +import io.github.studiorailgun.CircularByteBuffer; import electrosphere.net.parser.util.ByteStreamUtils; -import electrosphere.net.parser.net.raw.CircularByteBuffer; -import java.util.LinkedList; -import java.util.List; - public class PlayerMessage extends NetworkMessage { + /** + * The types of messages available in this category. + */ public enum PlayerMessageType { SET_ID, SETINITIALDISCRETEPOSITION, } + /** + * The type of this message in particular. + */ PlayerMessageType messageType; int playerID; int initialDiscretePositionX; int initialDiscretePositionY; int initialDiscretePositionZ; + /** + * Constructor + * @param messageType The type of this message + */ PlayerMessage(PlayerMessageType messageType){ this.type = MessageType.PLAYER_MESSAGE; this.messageType = messageType; @@ -27,42 +34,76 @@ public class PlayerMessage extends NetworkMessage { return this.messageType; } + /** + * Gets playerID + */ public int getplayerID() { return playerID; } + /** + * Sets playerID + */ public void setplayerID(int playerID) { this.playerID = playerID; } + /** + * Gets initialDiscretePositionX + */ public int getinitialDiscretePositionX() { return initialDiscretePositionX; } + /** + * Sets initialDiscretePositionX + */ public void setinitialDiscretePositionX(int initialDiscretePositionX) { this.initialDiscretePositionX = initialDiscretePositionX; } + /** + * Gets initialDiscretePositionY + */ public int getinitialDiscretePositionY() { return initialDiscretePositionY; } + /** + * Sets initialDiscretePositionY + */ public void setinitialDiscretePositionY(int initialDiscretePositionY) { this.initialDiscretePositionY = initialDiscretePositionY; } + /** + * Gets initialDiscretePositionZ + */ public int getinitialDiscretePositionZ() { return initialDiscretePositionZ; } + /** + * Sets initialDiscretePositionZ + */ public void setinitialDiscretePositionZ(int initialDiscretePositionZ) { this.initialDiscretePositionZ = initialDiscretePositionZ; } + /** + * Removes the packet header from the buffer + * @param byteBuffer The buffer + */ static void stripPacketHeader(CircularByteBuffer byteBuffer){ byteBuffer.read(2); } + /** + * Checks if this message can be parsed (ie are all bytes present) + * @param byteBuffer The buffer + * @param secondByte The second byte, signifying the subtype of the message + * @return true if the message can be parsed, false otherwise + */ public static boolean canParseMessage(CircularByteBuffer byteBuffer, byte secondByte){ switch(secondByte){ case TypeBytes.PLAYER_MESSAGE_TYPE_SET_ID: @@ -81,6 +122,9 @@ public class PlayerMessage extends NetworkMessage { return false; } + /** + * Parses a message of type Set_ID + */ public static PlayerMessage parseSet_IDMessage(CircularByteBuffer byteBuffer){ PlayerMessage rVal = new PlayerMessage(PlayerMessageType.SET_ID); stripPacketHeader(byteBuffer); @@ -88,6 +132,9 @@ public class PlayerMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type Set_ID + */ public static PlayerMessage constructSet_IDMessage(int playerID){ PlayerMessage rVal = new PlayerMessage(PlayerMessageType.SET_ID); rVal.setplayerID(playerID); @@ -95,6 +142,9 @@ public class PlayerMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type SetInitialDiscretePosition + */ public static PlayerMessage parseSetInitialDiscretePositionMessage(CircularByteBuffer byteBuffer){ PlayerMessage rVal = new PlayerMessage(PlayerMessageType.SETINITIALDISCRETEPOSITION); stripPacketHeader(byteBuffer); @@ -104,6 +154,9 @@ public class PlayerMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type SetInitialDiscretePosition + */ public static PlayerMessage constructSetInitialDiscretePositionMessage(int initialDiscretePositionX,int initialDiscretePositionY,int initialDiscretePositionZ){ PlayerMessage rVal = new PlayerMessage(PlayerMessageType.SETINITIALDISCRETEPOSITION); rVal.setinitialDiscretePositionX(initialDiscretePositionX); @@ -116,7 +169,6 @@ public class PlayerMessage extends NetworkMessage { @Override void serialize(){ byte[] intValues = new byte[8]; - byte[] stringBytes; switch(this.messageType){ case SET_ID: rawBytes = new byte[2+4]; diff --git a/src/main/java/electrosphere/net/parser/net/message/ServerMessage.java b/src/main/java/electrosphere/net/parser/net/message/ServerMessage.java index e5684902..baf96284 100644 --- a/src/main/java/electrosphere/net/parser/net/message/ServerMessage.java +++ b/src/main/java/electrosphere/net/parser/net/message/ServerMessage.java @@ -1,19 +1,25 @@ package electrosphere.net.parser.net.message; -import electrosphere.net.parser.util.ByteStreamUtils; -import electrosphere.net.parser.net.raw.CircularByteBuffer; -import java.util.LinkedList; -import java.util.List; - +import io.github.studiorailgun.CircularByteBuffer; public class ServerMessage extends NetworkMessage { + /** + * The types of messages available in this category. + */ public enum ServerMessageType { PING, PONG, } + /** + * The type of this message in particular. + */ ServerMessageType messageType; + /** + * Constructor + * @param messageType The type of this message + */ ServerMessage(ServerMessageType messageType){ this.type = MessageType.SERVER_MESSAGE; this.messageType = messageType; @@ -23,10 +29,20 @@ public class ServerMessage extends NetworkMessage { return this.messageType; } + /** + * Removes the packet header from the buffer + * @param byteBuffer The buffer + */ static void stripPacketHeader(CircularByteBuffer byteBuffer){ byteBuffer.read(2); } + /** + * Checks if this message can be parsed (ie are all bytes present) + * @param byteBuffer The buffer + * @param secondByte The second byte, signifying the subtype of the message + * @return true if the message can be parsed, false otherwise + */ public static boolean canParseMessage(CircularByteBuffer byteBuffer, byte secondByte){ switch(secondByte){ case TypeBytes.SERVER_MESSAGE_TYPE_PING: @@ -45,24 +61,36 @@ public class ServerMessage extends NetworkMessage { return false; } + /** + * Parses a message of type Ping + */ public static ServerMessage parsePingMessage(CircularByteBuffer byteBuffer){ ServerMessage rVal = new ServerMessage(ServerMessageType.PING); stripPacketHeader(byteBuffer); return rVal; } + /** + * Constructs a message of type Ping + */ public static ServerMessage constructPingMessage(){ ServerMessage rVal = new ServerMessage(ServerMessageType.PING); rVal.serialize(); return rVal; } + /** + * Parses a message of type Pong + */ public static ServerMessage parsePongMessage(CircularByteBuffer byteBuffer){ ServerMessage rVal = new ServerMessage(ServerMessageType.PONG); stripPacketHeader(byteBuffer); return rVal; } + /** + * Constructs a message of type Pong + */ public static ServerMessage constructPongMessage(){ ServerMessage rVal = new ServerMessage(ServerMessageType.PONG); rVal.serialize(); @@ -71,8 +99,6 @@ public class ServerMessage extends NetworkMessage { @Override void serialize(){ - byte[] intValues = new byte[8]; - byte[] stringBytes; switch(this.messageType){ case PING: rawBytes = new byte[2]; diff --git a/src/main/java/electrosphere/net/parser/net/message/SynchronizationMessage.java b/src/main/java/electrosphere/net/parser/net/message/SynchronizationMessage.java index 5a6783bc..2d557d3e 100644 --- a/src/main/java/electrosphere/net/parser/net/message/SynchronizationMessage.java +++ b/src/main/java/electrosphere/net/parser/net/message/SynchronizationMessage.java @@ -1,12 +1,15 @@ package electrosphere.net.parser.net.message; +import io.github.studiorailgun.CircularByteBuffer; import electrosphere.net.parser.util.ByteStreamUtils; -import electrosphere.net.parser.net.raw.CircularByteBuffer; import java.util.LinkedList; import java.util.List; public class SynchronizationMessage extends NetworkMessage { + /** + * The types of messages available in this category. + */ public enum SynchronizationMessageType { UPDATECLIENTSTATE, UPDATECLIENTSTRINGSTATE, @@ -21,6 +24,9 @@ public class SynchronizationMessage extends NetworkMessage { LOADSCENE, } + /** + * The type of this message in particular. + */ SynchronizationMessageType messageType; int entityId; int bTreeId; @@ -32,6 +38,10 @@ public class SynchronizationMessage extends NetworkMessage { float floatValue; double doubleValue; + /** + * Constructor + * @param messageType The type of this message + */ SynchronizationMessage(SynchronizationMessageType messageType){ this.type = MessageType.SYNCHRONIZATION_MESSAGE; this.messageType = messageType; @@ -41,82 +51,146 @@ public class SynchronizationMessage extends NetworkMessage { return this.messageType; } + /** + * Gets entityId + */ public int getentityId() { return entityId; } + /** + * Sets entityId + */ public void setentityId(int entityId) { this.entityId = entityId; } + /** + * Gets bTreeId + */ public int getbTreeId() { return bTreeId; } + /** + * Sets bTreeId + */ public void setbTreeId(int bTreeId) { this.bTreeId = bTreeId; } + /** + * Gets fieldId + */ public int getfieldId() { return fieldId; } + /** + * Sets fieldId + */ public void setfieldId(int fieldId) { this.fieldId = fieldId; } + /** + * Gets bTreeValue + */ public int getbTreeValue() { return bTreeValue; } + /** + * Sets bTreeValue + */ public void setbTreeValue(int bTreeValue) { this.bTreeValue = bTreeValue; } + /** + * Gets stringValue + */ public String getstringValue() { return stringValue; } + /** + * Sets stringValue + */ public void setstringValue(String stringValue) { this.stringValue = stringValue; } + /** + * Gets intValue + */ public int getintValue() { return intValue; } + /** + * Sets intValue + */ public void setintValue(int intValue) { this.intValue = intValue; } + /** + * Gets longValue + */ public long getlongValue() { return longValue; } + /** + * Sets longValue + */ public void setlongValue(long longValue) { this.longValue = longValue; } + /** + * Gets floatValue + */ public float getfloatValue() { return floatValue; } + /** + * Sets floatValue + */ public void setfloatValue(float floatValue) { this.floatValue = floatValue; } + /** + * Gets doubleValue + */ public double getdoubleValue() { return doubleValue; } + /** + * Sets doubleValue + */ public void setdoubleValue(double doubleValue) { this.doubleValue = doubleValue; } + /** + * Removes the packet header from the buffer + * @param byteBuffer The buffer + */ static void stripPacketHeader(CircularByteBuffer byteBuffer){ byteBuffer.read(2); } + /** + * Checks if this message can be parsed (ie are all bytes present) + * @param byteBuffer The buffer + * @param secondByte The second byte, signifying the subtype of the message + * @return true if the message can be parsed, false otherwise + */ public static boolean canParseMessage(CircularByteBuffer byteBuffer, byte secondByte){ switch(secondByte){ case TypeBytes.SYNCHRONIZATION_MESSAGE_TYPE_UPDATECLIENTSTATE: @@ -181,6 +255,9 @@ public class SynchronizationMessage extends NetworkMessage { return false; } + /** + * Parses a message of type UpdateClientState + */ public static SynchronizationMessage parseUpdateClientStateMessage(CircularByteBuffer byteBuffer){ SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.UPDATECLIENTSTATE); stripPacketHeader(byteBuffer); @@ -191,6 +268,9 @@ public class SynchronizationMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type UpdateClientState + */ public static SynchronizationMessage constructUpdateClientStateMessage(int entityId,int bTreeId,int fieldId,int bTreeValue){ SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.UPDATECLIENTSTATE); rVal.setentityId(entityId); @@ -201,6 +281,9 @@ public class SynchronizationMessage extends NetworkMessage { return rVal; } + /** + * Checks if a message of type UpdateClientStringState can be parsed from the byte stream + */ public static boolean canParseUpdateClientStringStateMessage(CircularByteBuffer byteBuffer){ int currentStreamLength = byteBuffer.getRemaining(); List temporaryByteQueue = new LinkedList(); @@ -229,6 +312,9 @@ public class SynchronizationMessage extends NetworkMessage { return true; } + /** + * Parses a message of type UpdateClientStringState + */ public static SynchronizationMessage parseUpdateClientStringStateMessage(CircularByteBuffer byteBuffer){ SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.UPDATECLIENTSTRINGSTATE); stripPacketHeader(byteBuffer); @@ -239,6 +325,9 @@ public class SynchronizationMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type UpdateClientStringState + */ public static SynchronizationMessage constructUpdateClientStringStateMessage(int entityId,int bTreeId,int fieldId,String stringValue){ SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.UPDATECLIENTSTRINGSTATE); rVal.setentityId(entityId); @@ -249,6 +338,9 @@ public class SynchronizationMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type UpdateClientIntState + */ public static SynchronizationMessage parseUpdateClientIntStateMessage(CircularByteBuffer byteBuffer){ SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.UPDATECLIENTINTSTATE); stripPacketHeader(byteBuffer); @@ -259,6 +351,9 @@ public class SynchronizationMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type UpdateClientIntState + */ public static SynchronizationMessage constructUpdateClientIntStateMessage(int entityId,int bTreeId,int fieldId,int intValue){ SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.UPDATECLIENTINTSTATE); rVal.setentityId(entityId); @@ -269,6 +364,9 @@ public class SynchronizationMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type UpdateClientLongState + */ public static SynchronizationMessage parseUpdateClientLongStateMessage(CircularByteBuffer byteBuffer){ SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.UPDATECLIENTLONGSTATE); stripPacketHeader(byteBuffer); @@ -279,6 +377,9 @@ public class SynchronizationMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type UpdateClientLongState + */ public static SynchronizationMessage constructUpdateClientLongStateMessage(int entityId,int bTreeId,int fieldId,long longValue){ SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.UPDATECLIENTLONGSTATE); rVal.setentityId(entityId); @@ -289,6 +390,9 @@ public class SynchronizationMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type UpdateClientFloatState + */ public static SynchronizationMessage parseUpdateClientFloatStateMessage(CircularByteBuffer byteBuffer){ SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.UPDATECLIENTFLOATSTATE); stripPacketHeader(byteBuffer); @@ -299,6 +403,9 @@ public class SynchronizationMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type UpdateClientFloatState + */ public static SynchronizationMessage constructUpdateClientFloatStateMessage(int entityId,int bTreeId,int fieldId,float floatValue){ SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.UPDATECLIENTFLOATSTATE); rVal.setentityId(entityId); @@ -309,6 +416,9 @@ public class SynchronizationMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type UpdateClientDoubleState + */ public static SynchronizationMessage parseUpdateClientDoubleStateMessage(CircularByteBuffer byteBuffer){ SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.UPDATECLIENTDOUBLESTATE); stripPacketHeader(byteBuffer); @@ -319,6 +429,9 @@ public class SynchronizationMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type UpdateClientDoubleState + */ public static SynchronizationMessage constructUpdateClientDoubleStateMessage(int entityId,int bTreeId,int fieldId,double doubleValue){ SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.UPDATECLIENTDOUBLESTATE); rVal.setentityId(entityId); @@ -329,6 +442,9 @@ public class SynchronizationMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type ClientRequestBTreeAction + */ public static SynchronizationMessage parseClientRequestBTreeActionMessage(CircularByteBuffer byteBuffer){ SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.CLIENTREQUESTBTREEACTION); stripPacketHeader(byteBuffer); @@ -338,6 +454,9 @@ public class SynchronizationMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type ClientRequestBTreeAction + */ public static SynchronizationMessage constructClientRequestBTreeActionMessage(int entityId,int bTreeId,int bTreeValue){ SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.CLIENTREQUESTBTREEACTION); rVal.setentityId(entityId); @@ -347,6 +466,9 @@ public class SynchronizationMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type ServerNotifyBTreeTransition + */ public static SynchronizationMessage parseServerNotifyBTreeTransitionMessage(CircularByteBuffer byteBuffer){ SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.SERVERNOTIFYBTREETRANSITION); stripPacketHeader(byteBuffer); @@ -357,6 +479,9 @@ public class SynchronizationMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type ServerNotifyBTreeTransition + */ public static SynchronizationMessage constructServerNotifyBTreeTransitionMessage(int entityId,int bTreeId,int fieldId,int bTreeValue){ SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.SERVERNOTIFYBTREETRANSITION); rVal.setentityId(entityId); @@ -367,6 +492,9 @@ public class SynchronizationMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type AttachTree + */ public static SynchronizationMessage parseAttachTreeMessage(CircularByteBuffer byteBuffer){ SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.ATTACHTREE); stripPacketHeader(byteBuffer); @@ -375,6 +503,9 @@ public class SynchronizationMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type AttachTree + */ public static SynchronizationMessage constructAttachTreeMessage(int entityId,int bTreeId){ SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.ATTACHTREE); rVal.setentityId(entityId); @@ -383,6 +514,9 @@ public class SynchronizationMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type DetatchTree + */ public static SynchronizationMessage parseDetatchTreeMessage(CircularByteBuffer byteBuffer){ SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.DETATCHTREE); stripPacketHeader(byteBuffer); @@ -391,6 +525,9 @@ public class SynchronizationMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type DetatchTree + */ public static SynchronizationMessage constructDetatchTreeMessage(int entityId,int bTreeId){ SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.DETATCHTREE); rVal.setentityId(entityId); @@ -399,6 +536,9 @@ public class SynchronizationMessage extends NetworkMessage { return rVal; } + /** + * Checks if a message of type LoadScene can be parsed from the byte stream + */ public static boolean canParseLoadSceneMessage(CircularByteBuffer byteBuffer){ int currentStreamLength = byteBuffer.getRemaining(); List temporaryByteQueue = new LinkedList(); @@ -418,6 +558,9 @@ public class SynchronizationMessage extends NetworkMessage { return true; } + /** + * Parses a message of type LoadScene + */ public static SynchronizationMessage parseLoadSceneMessage(CircularByteBuffer byteBuffer){ SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.LOADSCENE); stripPacketHeader(byteBuffer); @@ -425,6 +568,9 @@ public class SynchronizationMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type LoadScene + */ public static SynchronizationMessage constructLoadSceneMessage(String stringValue){ SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.LOADSCENE); rVal.setstringValue(stringValue); diff --git a/src/main/java/electrosphere/net/parser/net/message/TerrainMessage.java b/src/main/java/electrosphere/net/parser/net/message/TerrainMessage.java index 596297dd..8a21fc6f 100644 --- a/src/main/java/electrosphere/net/parser/net/message/TerrainMessage.java +++ b/src/main/java/electrosphere/net/parser/net/message/TerrainMessage.java @@ -1,12 +1,15 @@ package electrosphere.net.parser.net.message; +import io.github.studiorailgun.CircularByteBuffer; import electrosphere.net.parser.util.ByteStreamUtils; -import electrosphere.net.parser.net.raw.CircularByteBuffer; import java.util.LinkedList; import java.util.List; public class TerrainMessage extends NetworkMessage { + /** + * The types of messages available in this category. + */ public enum TerrainMessageType { REQUESTMETADATA, RESPONSEMETADATA, @@ -23,6 +26,9 @@ public class TerrainMessage extends NetworkMessage { UPDATEFLUIDDATA, } + /** + * The type of this message in particular. + */ TerrainMessageType messageType; int worldSizeDiscrete; int dynamicInterpolationRatio; @@ -46,6 +52,10 @@ public class TerrainMessage extends NetworkMessage { float terrainWeight; int terrainValue; + /** + * Constructor + * @param messageType The type of this message + */ TerrainMessage(TerrainMessageType messageType){ this.type = MessageType.TERRAIN_MESSAGE; this.messageType = messageType; @@ -55,178 +65,314 @@ public class TerrainMessage extends NetworkMessage { return this.messageType; } + /** + * Gets worldSizeDiscrete + */ public int getworldSizeDiscrete() { return worldSizeDiscrete; } + /** + * Sets worldSizeDiscrete + */ public void setworldSizeDiscrete(int worldSizeDiscrete) { this.worldSizeDiscrete = worldSizeDiscrete; } + /** + * Gets dynamicInterpolationRatio + */ public int getdynamicInterpolationRatio() { return dynamicInterpolationRatio; } + /** + * Sets dynamicInterpolationRatio + */ public void setdynamicInterpolationRatio(int dynamicInterpolationRatio) { this.dynamicInterpolationRatio = dynamicInterpolationRatio; } + /** + * Gets randomDampener + */ public float getrandomDampener() { return randomDampener; } + /** + * Sets randomDampener + */ public void setrandomDampener(float randomDampener) { this.randomDampener = randomDampener; } + /** + * Gets worldMinX + */ public int getworldMinX() { return worldMinX; } + /** + * Sets worldMinX + */ public void setworldMinX(int worldMinX) { this.worldMinX = worldMinX; } + /** + * Gets worldMinY + */ public int getworldMinY() { return worldMinY; } + /** + * Sets worldMinY + */ public void setworldMinY(int worldMinY) { this.worldMinY = worldMinY; } + /** + * Gets worldMaxX + */ public int getworldMaxX() { return worldMaxX; } + /** + * Sets worldMaxX + */ public void setworldMaxX(int worldMaxX) { this.worldMaxX = worldMaxX; } + /** + * Gets worldMaxY + */ public int getworldMaxY() { return worldMaxY; } + /** + * Sets worldMaxY + */ public void setworldMaxY(int worldMaxY) { this.worldMaxY = worldMaxY; } + /** + * Gets value + */ public float getvalue() { return value; } + /** + * Sets value + */ public void setvalue(float value) { this.value = value; } + /** + * Gets worldX + */ public int getworldX() { return worldX; } + /** + * Sets worldX + */ public void setworldX(int worldX) { this.worldX = worldX; } + /** + * Gets worldY + */ public int getworldY() { return worldY; } + /** + * Sets worldY + */ public void setworldY(int worldY) { this.worldY = worldY; } + /** + * Gets worldZ + */ public int getworldZ() { return worldZ; } + /** + * Sets worldZ + */ public void setworldZ(int worldZ) { this.worldZ = worldZ; } + /** + * Gets voxelX + */ public int getvoxelX() { return voxelX; } + /** + * Sets voxelX + */ public void setvoxelX(int voxelX) { this.voxelX = voxelX; } + /** + * Gets voxelY + */ public int getvoxelY() { return voxelY; } + /** + * Sets voxelY + */ public void setvoxelY(int voxelY) { this.voxelY = voxelY; } + /** + * Gets voxelZ + */ public int getvoxelZ() { return voxelZ; } + /** + * Sets voxelZ + */ public void setvoxelZ(int voxelZ) { this.voxelZ = voxelZ; } + /** + * Gets realLocationX + */ public double getrealLocationX() { return realLocationX; } + /** + * Sets realLocationX + */ public void setrealLocationX(double realLocationX) { this.realLocationX = realLocationX; } + /** + * Gets realLocationY + */ public double getrealLocationY() { return realLocationY; } + /** + * Sets realLocationY + */ public void setrealLocationY(double realLocationY) { this.realLocationY = realLocationY; } + /** + * Gets realLocationZ + */ public double getrealLocationZ() { return realLocationZ; } + /** + * Sets realLocationZ + */ public void setrealLocationZ(double realLocationZ) { this.realLocationZ = realLocationZ; } + /** + * Gets chunkData + */ public byte[] getchunkData() { return chunkData; } + /** + * Sets chunkData + */ public void setchunkData(byte[] chunkData) { this.chunkData = chunkData; } + /** + * Gets chunkResolution + */ public int getchunkResolution() { return chunkResolution; } + /** + * Sets chunkResolution + */ public void setchunkResolution(int chunkResolution) { this.chunkResolution = chunkResolution; } + /** + * Gets terrainWeight + */ public float getterrainWeight() { return terrainWeight; } + /** + * Sets terrainWeight + */ public void setterrainWeight(float terrainWeight) { this.terrainWeight = terrainWeight; } + /** + * Gets terrainValue + */ public int getterrainValue() { return terrainValue; } + /** + * Sets terrainValue + */ public void setterrainValue(int terrainValue) { this.terrainValue = terrainValue; } + /** + * Removes the packet header from the buffer + * @param byteBuffer The buffer + */ static void stripPacketHeader(CircularByteBuffer byteBuffer){ byteBuffer.read(2); } + /** + * Checks if this message can be parsed (ie are all bytes present) + * @param byteBuffer The buffer + * @param secondByte The second byte, signifying the subtype of the message + * @return true if the message can be parsed, false otherwise + */ public static boolean canParseMessage(CircularByteBuffer byteBuffer, byte secondByte){ switch(secondByte){ case TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTMETADATA: @@ -295,18 +441,27 @@ public class TerrainMessage extends NetworkMessage { return false; } + /** + * Parses a message of type RequestMetadata + */ public static TerrainMessage parseRequestMetadataMessage(CircularByteBuffer byteBuffer){ TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTMETADATA); stripPacketHeader(byteBuffer); return rVal; } + /** + * Constructs a message of type RequestMetadata + */ public static TerrainMessage constructRequestMetadataMessage(){ TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTMETADATA); rVal.serialize(); return rVal; } + /** + * Parses a message of type ResponseMetadata + */ public static TerrainMessage parseResponseMetadataMessage(CircularByteBuffer byteBuffer){ TerrainMessage rVal = new TerrainMessage(TerrainMessageType.RESPONSEMETADATA); stripPacketHeader(byteBuffer); @@ -320,6 +475,9 @@ public class TerrainMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type ResponseMetadata + */ public static TerrainMessage constructResponseMetadataMessage(int worldSizeDiscrete,int dynamicInterpolationRatio,float randomDampener,int worldMinX,int worldMinY,int worldMaxX,int worldMaxY){ TerrainMessage rVal = new TerrainMessage(TerrainMessageType.RESPONSEMETADATA); rVal.setworldSizeDiscrete(worldSizeDiscrete); @@ -333,6 +491,9 @@ public class TerrainMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type RequestEditVoxel + */ public static TerrainMessage parseRequestEditVoxelMessage(CircularByteBuffer byteBuffer){ TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTEDITVOXEL); stripPacketHeader(byteBuffer); @@ -347,6 +508,9 @@ public class TerrainMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type RequestEditVoxel + */ public static TerrainMessage constructRequestEditVoxelMessage(int worldX,int worldY,int worldZ,int voxelX,int voxelY,int voxelZ,float terrainWeight,int terrainValue){ TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTEDITVOXEL); rVal.setworldX(worldX); @@ -361,6 +525,9 @@ public class TerrainMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type UpdateVoxel + */ public static TerrainMessage parseUpdateVoxelMessage(CircularByteBuffer byteBuffer){ TerrainMessage rVal = new TerrainMessage(TerrainMessageType.UPDATEVOXEL); stripPacketHeader(byteBuffer); @@ -375,6 +542,9 @@ public class TerrainMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type UpdateVoxel + */ public static TerrainMessage constructUpdateVoxelMessage(int worldX,int worldY,int worldZ,int voxelX,int voxelY,int voxelZ,float terrainWeight,int terrainValue){ TerrainMessage rVal = new TerrainMessage(TerrainMessageType.UPDATEVOXEL); rVal.setworldX(worldX); @@ -389,6 +559,9 @@ public class TerrainMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type RequestUseTerrainPalette + */ public static TerrainMessage parseRequestUseTerrainPaletteMessage(CircularByteBuffer byteBuffer){ TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTUSETERRAINPALETTE); stripPacketHeader(byteBuffer); @@ -401,6 +574,9 @@ public class TerrainMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type RequestUseTerrainPalette + */ public static TerrainMessage constructRequestUseTerrainPaletteMessage(double realLocationX,double realLocationY,double realLocationZ,float value,float terrainWeight,int terrainValue){ TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTUSETERRAINPALETTE); rVal.setrealLocationX(realLocationX); @@ -413,6 +589,9 @@ public class TerrainMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type SpawnPosition + */ public static TerrainMessage parseSpawnPositionMessage(CircularByteBuffer byteBuffer){ TerrainMessage rVal = new TerrainMessage(TerrainMessageType.SPAWNPOSITION); stripPacketHeader(byteBuffer); @@ -422,6 +601,9 @@ public class TerrainMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type SpawnPosition + */ public static TerrainMessage constructSpawnPositionMessage(double realLocationX,double realLocationY,double realLocationZ){ TerrainMessage rVal = new TerrainMessage(TerrainMessageType.SPAWNPOSITION); rVal.setrealLocationX(realLocationX); @@ -431,6 +613,9 @@ public class TerrainMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type RequestChunkData + */ public static TerrainMessage parseRequestChunkDataMessage(CircularByteBuffer byteBuffer){ TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTCHUNKDATA); stripPacketHeader(byteBuffer); @@ -440,6 +625,9 @@ public class TerrainMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type RequestChunkData + */ public static TerrainMessage constructRequestChunkDataMessage(int worldX,int worldY,int worldZ){ TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTCHUNKDATA); rVal.setworldX(worldX); @@ -449,6 +637,9 @@ public class TerrainMessage extends NetworkMessage { return rVal; } + /** + * Checks if a message of type sendChunkData can be parsed from the byte stream + */ public static boolean canParsesendChunkDataMessage(CircularByteBuffer byteBuffer){ int currentStreamLength = byteBuffer.getRemaining(); List temporaryByteQueue = new LinkedList(); @@ -477,6 +668,9 @@ public class TerrainMessage extends NetworkMessage { return true; } + /** + * Parses a message of type sendChunkData + */ public static TerrainMessage parsesendChunkDataMessage(CircularByteBuffer byteBuffer){ TerrainMessage rVal = new TerrainMessage(TerrainMessageType.SENDCHUNKDATA); stripPacketHeader(byteBuffer); @@ -487,6 +681,9 @@ public class TerrainMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type sendChunkData + */ public static TerrainMessage constructsendChunkDataMessage(int worldX,int worldY,int worldZ,byte[] chunkData){ TerrainMessage rVal = new TerrainMessage(TerrainMessageType.SENDCHUNKDATA); rVal.setworldX(worldX); @@ -497,6 +694,9 @@ public class TerrainMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type RequestReducedChunkData + */ public static TerrainMessage parseRequestReducedChunkDataMessage(CircularByteBuffer byteBuffer){ TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTREDUCEDCHUNKDATA); stripPacketHeader(byteBuffer); @@ -507,6 +707,9 @@ public class TerrainMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type RequestReducedChunkData + */ public static TerrainMessage constructRequestReducedChunkDataMessage(int worldX,int worldY,int worldZ,int chunkResolution){ TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTREDUCEDCHUNKDATA); rVal.setworldX(worldX); @@ -517,6 +720,9 @@ public class TerrainMessage extends NetworkMessage { return rVal; } + /** + * Checks if a message of type SendReducedChunkData can be parsed from the byte stream + */ public static boolean canParseSendReducedChunkDataMessage(CircularByteBuffer byteBuffer){ int currentStreamLength = byteBuffer.getRemaining(); List temporaryByteQueue = new LinkedList(); @@ -548,6 +754,9 @@ public class TerrainMessage extends NetworkMessage { return true; } + /** + * Parses a message of type SendReducedChunkData + */ public static TerrainMessage parseSendReducedChunkDataMessage(CircularByteBuffer byteBuffer){ TerrainMessage rVal = new TerrainMessage(TerrainMessageType.SENDREDUCEDCHUNKDATA); stripPacketHeader(byteBuffer); @@ -559,6 +768,9 @@ public class TerrainMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type SendReducedChunkData + */ public static TerrainMessage constructSendReducedChunkDataMessage(int worldX,int worldY,int worldZ,int chunkResolution,byte[] chunkData){ TerrainMessage rVal = new TerrainMessage(TerrainMessageType.SENDREDUCEDCHUNKDATA); rVal.setworldX(worldX); @@ -570,6 +782,9 @@ public class TerrainMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type RequestFluidData + */ public static TerrainMessage parseRequestFluidDataMessage(CircularByteBuffer byteBuffer){ TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTFLUIDDATA); stripPacketHeader(byteBuffer); @@ -579,6 +794,9 @@ public class TerrainMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type RequestFluidData + */ public static TerrainMessage constructRequestFluidDataMessage(int worldX,int worldY,int worldZ){ TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTFLUIDDATA); rVal.setworldX(worldX); @@ -588,6 +806,9 @@ public class TerrainMessage extends NetworkMessage { return rVal; } + /** + * Checks if a message of type sendFluidData can be parsed from the byte stream + */ public static boolean canParsesendFluidDataMessage(CircularByteBuffer byteBuffer){ int currentStreamLength = byteBuffer.getRemaining(); List temporaryByteQueue = new LinkedList(); @@ -616,6 +837,9 @@ public class TerrainMessage extends NetworkMessage { return true; } + /** + * Parses a message of type sendFluidData + */ public static TerrainMessage parsesendFluidDataMessage(CircularByteBuffer byteBuffer){ TerrainMessage rVal = new TerrainMessage(TerrainMessageType.SENDFLUIDDATA); stripPacketHeader(byteBuffer); @@ -626,6 +850,9 @@ public class TerrainMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type sendFluidData + */ public static TerrainMessage constructsendFluidDataMessage(int worldX,int worldY,int worldZ,byte[] chunkData){ TerrainMessage rVal = new TerrainMessage(TerrainMessageType.SENDFLUIDDATA); rVal.setworldX(worldX); @@ -636,6 +863,9 @@ public class TerrainMessage extends NetworkMessage { return rVal; } + /** + * Checks if a message of type updateFluidData can be parsed from the byte stream + */ public static boolean canParseupdateFluidDataMessage(CircularByteBuffer byteBuffer){ int currentStreamLength = byteBuffer.getRemaining(); List temporaryByteQueue = new LinkedList(); @@ -664,6 +894,9 @@ public class TerrainMessage extends NetworkMessage { return true; } + /** + * Parses a message of type updateFluidData + */ public static TerrainMessage parseupdateFluidDataMessage(CircularByteBuffer byteBuffer){ TerrainMessage rVal = new TerrainMessage(TerrainMessageType.UPDATEFLUIDDATA); stripPacketHeader(byteBuffer); @@ -674,6 +907,9 @@ public class TerrainMessage extends NetworkMessage { return rVal; } + /** + * Constructs a message of type updateFluidData + */ public static TerrainMessage constructupdateFluidDataMessage(int worldX,int worldY,int worldZ,byte[] chunkData){ TerrainMessage rVal = new TerrainMessage(TerrainMessageType.UPDATEFLUIDDATA); rVal.setworldX(worldX); @@ -687,7 +923,6 @@ public class TerrainMessage extends NetworkMessage { @Override void serialize(){ byte[] intValues = new byte[8]; - byte[] stringBytes; switch(this.messageType){ case REQUESTMETADATA: rawBytes = new byte[2]; diff --git a/src/main/java/electrosphere/net/parser/net/message/TypeBytes.java b/src/main/java/electrosphere/net/parser/net/message/TypeBytes.java index caf8e007..4d7256b2 100644 --- a/src/main/java/electrosphere/net/parser/net/message/TypeBytes.java +++ b/src/main/java/electrosphere/net/parser/net/message/TypeBytes.java @@ -1,10 +1,12 @@ package electrosphere.net.parser.net.message; - +/** + * The constants used in serializing/deserializing messages + */ public class TypeBytes { -/* -Message categories -*/ + /** + * Message categories + */ public static final byte MESSAGE_TYPE_ENTITY = 0; public static final byte MESSAGE_TYPE_LORE = 1; public static final byte MESSAGE_TYPE_PLAYER = 2; @@ -16,7 +18,7 @@ Message categories public static final byte MESSAGE_TYPE_SYNCHRONIZATION = 8; public static final byte MESSAGE_TYPE_COMBAT = 9; /* - Entity subcategories + Entity subcategories */ public static final byte ENTITY_MESSAGE_TYPE_CREATE = 0; public static final byte ENTITY_MESSAGE_TYPE_MOVEUPDATE = 1; @@ -29,7 +31,7 @@ Message categories public static final byte ENTITY_MESSAGE_TYPE_UPDATEENTITYVIEWDIR = 8; public static final byte ENTITY_MESSAGE_TYPE_SYNCPHYSICS = 9; /* - Entity packet sizes + Entity packet sizes */ public static final byte ENTITY_MESSAGE_TYPE_MOVEUPDATE_SIZE = 86; public static final byte ENTITY_MESSAGE_TYPE_ATTACKUPDATE_SIZE = 74; @@ -39,27 +41,30 @@ Message categories public static final byte ENTITY_MESSAGE_TYPE_SETPROPERTY_SIZE = 22; public static final byte ENTITY_MESSAGE_TYPE_UPDATEENTITYVIEWDIR_SIZE = 34; public static final short ENTITY_MESSAGE_TYPE_SYNCPHYSICS_SIZE = 166; + /* - Lore subcategories + Lore subcategories */ public static final byte LORE_MESSAGE_TYPE_REQUESTRACES = 0; public static final byte LORE_MESSAGE_TYPE_RESPONSERACES = 1; /* - Lore packet sizes + Lore packet sizes */ public static final byte LORE_MESSAGE_TYPE_REQUESTRACES_SIZE = 2; + /* - Player subcategories + Player subcategories */ public static final byte PLAYER_MESSAGE_TYPE_SET_ID = 0; public static final byte PLAYER_MESSAGE_TYPE_SETINITIALDISCRETEPOSITION = 1; /* - Player packet sizes + Player packet sizes */ public static final byte PLAYER_MESSAGE_TYPE_SET_ID_SIZE = 6; public static final byte PLAYER_MESSAGE_TYPE_SETINITIALDISCRETEPOSITION_SIZE = 14; + /* - Terrain subcategories + Terrain subcategories */ public static final byte TERRAIN_MESSAGE_TYPE_REQUESTMETADATA = 0; public static final byte TERRAIN_MESSAGE_TYPE_RESPONSEMETADATA = 1; @@ -75,7 +80,7 @@ Message categories public static final byte TERRAIN_MESSAGE_TYPE_SENDFLUIDDATA = 11; public static final byte TERRAIN_MESSAGE_TYPE_UPDATEFLUIDDATA = 12; /* - Terrain packet sizes + Terrain packet sizes */ public static final byte TERRAIN_MESSAGE_TYPE_REQUESTMETADATA_SIZE = 2; public static final byte TERRAIN_MESSAGE_TYPE_RESPONSEMETADATA_SIZE = 30; @@ -86,31 +91,34 @@ Message categories public static final byte TERRAIN_MESSAGE_TYPE_REQUESTCHUNKDATA_SIZE = 14; public static final byte TERRAIN_MESSAGE_TYPE_REQUESTREDUCEDCHUNKDATA_SIZE = 18; public static final byte TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA_SIZE = 14; + /* - Server subcategories + Server subcategories */ public static final byte SERVER_MESSAGE_TYPE_PING = 0; public static final byte SERVER_MESSAGE_TYPE_PONG = 1; /* - Server packet sizes + Server packet sizes */ public static final byte SERVER_MESSAGE_TYPE_PING_SIZE = 2; public static final byte SERVER_MESSAGE_TYPE_PONG_SIZE = 2; + /* - Auth subcategories + Auth subcategories */ public static final byte AUTH_MESSAGE_TYPE_AUTHREQUEST = 0; public static final byte AUTH_MESSAGE_TYPE_AUTHDETAILS = 1; public static final byte AUTH_MESSAGE_TYPE_AUTHSUCCESS = 2; public static final byte AUTH_MESSAGE_TYPE_AUTHFAILURE = 3; /* - Auth packet sizes + Auth packet sizes */ public static final byte AUTH_MESSAGE_TYPE_AUTHREQUEST_SIZE = 2; public static final byte AUTH_MESSAGE_TYPE_AUTHSUCCESS_SIZE = 2; public static final byte AUTH_MESSAGE_TYPE_AUTHFAILURE_SIZE = 2; + /* - Character subcategories + Character subcategories */ public static final byte CHARACTER_MESSAGE_TYPE_REQUESTCHARACTERLIST = 0; public static final byte CHARACTER_MESSAGE_TYPE_RESPONSECHARACTERLIST = 1; @@ -120,14 +128,15 @@ Message categories public static final byte CHARACTER_MESSAGE_TYPE_REQUESTSPAWNCHARACTER = 5; public static final byte CHARACTER_MESSAGE_TYPE_RESPONSESPAWNCHARACTER = 6; /* - Character packet sizes + Character packet sizes */ public static final byte CHARACTER_MESSAGE_TYPE_REQUESTCHARACTERLIST_SIZE = 2; public static final byte CHARACTER_MESSAGE_TYPE_RESPONSECREATECHARACTERSUCCESS_SIZE = 2; public static final byte CHARACTER_MESSAGE_TYPE_RESPONSECREATECHARACTERFAILURE_SIZE = 2; public static final byte CHARACTER_MESSAGE_TYPE_REQUESTSPAWNCHARACTER_SIZE = 2; + /* - Inventory subcategories + Inventory subcategories */ public static final byte INVENTORY_MESSAGE_TYPE_ADDITEMTOINVENTORY = 0; public static final byte INVENTORY_MESSAGE_TYPE_REMOVEITEMFROMINVENTORY = 1; @@ -141,14 +150,15 @@ Message categories public static final byte INVENTORY_MESSAGE_TYPE_CLIENTUPDATETOOLBAR = 9; public static final byte INVENTORY_MESSAGE_TYPE_CLIENTREQUESTPERFORMITEMACTION = 10; /* - Inventory packet sizes + Inventory packet sizes */ public static final byte INVENTORY_MESSAGE_TYPE_REMOVEITEMFROMINVENTORY_SIZE = 6; public static final byte INVENTORY_MESSAGE_TYPE_CLIENTREQUESTADDTOOLBAR_SIZE = 10; public static final byte INVENTORY_MESSAGE_TYPE_CLIENTREQUESTADDNATURAL_SIZE = 6; public static final byte INVENTORY_MESSAGE_TYPE_CLIENTUPDATETOOLBAR_SIZE = 6; + /* - Synchronization subcategories + Synchronization subcategories */ public static final byte SYNCHRONIZATION_MESSAGE_TYPE_UPDATECLIENTSTATE = 0; public static final byte SYNCHRONIZATION_MESSAGE_TYPE_UPDATECLIENTSTRINGSTATE = 1; @@ -162,7 +172,7 @@ Message categories public static final byte SYNCHRONIZATION_MESSAGE_TYPE_DETATCHTREE = 9; public static final byte SYNCHRONIZATION_MESSAGE_TYPE_LOADSCENE = 10; /* - Synchronization packet sizes + Synchronization packet sizes */ public static final byte SYNCHRONIZATION_MESSAGE_TYPE_UPDATECLIENTSTATE_SIZE = 18; public static final byte SYNCHRONIZATION_MESSAGE_TYPE_UPDATECLIENTINTSTATE_SIZE = 18; @@ -173,12 +183,14 @@ Message categories public static final byte SYNCHRONIZATION_MESSAGE_TYPE_SERVERNOTIFYBTREETRANSITION_SIZE = 18; public static final byte SYNCHRONIZATION_MESSAGE_TYPE_ATTACHTREE_SIZE = 10; public static final byte SYNCHRONIZATION_MESSAGE_TYPE_DETATCHTREE_SIZE = 10; + /* - Combat subcategories + Combat subcategories */ public static final byte COMBAT_MESSAGE_TYPE_SERVERREPORTHITBOXCOLLISION = 0; /* - Combat packet sizes + Combat packet sizes */ + } diff --git a/src/main/java/electrosphere/net/parser/net/raw/CircularByteBuffer.java b/src/main/java/electrosphere/net/parser/net/raw/CircularByteBuffer.java deleted file mode 100644 index 2aadb3f8..00000000 --- a/src/main/java/electrosphere/net/parser/net/raw/CircularByteBuffer.java +++ /dev/null @@ -1,120 +0,0 @@ -package electrosphere.net.parser.net.raw; - - -import java.util.concurrent.Semaphore; - -/** - * A circular byte buffer optimized for high throughput (relative to a list) and peaking at early elements of the current position. - */ -public class CircularByteBuffer { - - //The array backing this circular byte buffer - byte[] backingArray; - //the current read position of the buffer in the backing array - int position; - //the remaining bytes to read before the read position equals the write position - int remaining; - //the capacity of the backing array - int capacity; - //Lock to make the structure threadsafe - Semaphore lock = new Semaphore(1); - - /** - * Constructs a CircularByteBuffer - * @param capacity The capacity of the backing array in bytes - */ - public CircularByteBuffer(int capacity){ - backingArray = new byte[capacity]; - position = 0; - remaining = 0; - this.capacity = capacity; - } - - /** - * Adds an array of bytes to the circular buffer - * @param bytes The bytes - * @param len The number of bytes to pull from the array bytes - */ - public void add(byte[] bytes, int len){ - lock.acquireUninterruptibly(); - // System.out.println("Add start"); - int writePosition = (position + remaining) % capacity; - //amount possible to write before wrapping - int writeBeforeWrap = capacity - writePosition; - //only run wrapping logic if necessary - if(len > writeBeforeWrap){ - System.arraycopy(bytes, 0, backingArray, writePosition, writeBeforeWrap); - System.arraycopy(bytes, writeBeforeWrap, backingArray, 0, len - writeBeforeWrap); - } else { - System.arraycopy(bytes, 0, backingArray, writePosition, len); - } - remaining = remaining + len; - lock.release(); - } - - /** - * Peeks at the next element in the buffer - * @return The value of the byte next in the buffer - */ - public byte peek(){ - byte rVal = peek(0); - return rVal; - } - - /** - * Peeks at an element @param offset elements further along the buffer from the current position - * @param offset The offset, in bytes, to look forward in the buffer - * @return The value of the byte at the current position + @param offset - */ - public byte peek(int offset){ - lock.acquireUninterruptibly(); - byte rVal = backingArray[(position + offset) % capacity]; - lock.release(); - return rVal; - } - - /** - * Gets the remaining number of bytes in the buffer - * @return The remaining number of bytes - */ - public int getRemaining(){ - lock.acquireUninterruptibly(); - int rVal = remaining; - lock.release(); - return rVal; - } - - /** - * Gets the capacity of the buffer - * @return The capacity - */ - public int getCapacity(){ - lock.acquireUninterruptibly(); - int rVal = capacity; - lock.release(); - return rVal; - } - - /** - * Reads a given number of bytes from the buffer - * @param len The number of bytes to read - * @return The bytes in an array - */ - public byte[] read(int len){ - lock.acquireUninterruptibly(); - byte[] rVal = new byte[len]; - //amount possible to read before loop - int toReadBeforeLoop = capacity - position; - if(len > capacity - position){ - System.arraycopy(backingArray, position, rVal, 0, toReadBeforeLoop); - System.arraycopy(backingArray, 0, rVal, toReadBeforeLoop, len - toReadBeforeLoop); - } else { - System.arraycopy(backingArray, position, rVal, 0, len); - } - position = (position + len) % capacity; - remaining = remaining - len; - lock.release(); - return rVal; - } - -} diff --git a/src/main/java/electrosphere/net/parser/net/raw/NetworkParser.java b/src/main/java/electrosphere/net/parser/net/raw/NetworkParser.java index f91afbb3..1422ab13 100644 --- a/src/main/java/electrosphere/net/parser/net/raw/NetworkParser.java +++ b/src/main/java/electrosphere/net/parser/net/raw/NetworkParser.java @@ -1,76 +1,132 @@ package electrosphere.net.parser.net.raw; import electrosphere.net.parser.net.message.NetworkMessage; +import io.github.studiorailgun.CircularByteBuffer; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +/** + * The main message parser. This is used to serialize/deserialize messages to/from the provided streams. + */ public class NetworkParser { + + /** + * The size of the read buffer + */ + static final int READ_BLOCK_SIZE = 16 * 1024 * 1024; + + /** + * The size of the circular buffer + */ + static final int CIRCULAR_BUFFER_SIZE = 64 * 1024 * 1024; + /** + * The input stream for the parser + */ InputStream incomingStream; + + /** + * The output stream for the parser + */ OutputStream outgoingStream; + /** + * The queue of incoming messages that have been parsed + */ CopyOnWriteArrayList incomingMessageQueue = new CopyOnWriteArrayList(); + + /** + * The queue of outgoing messages that have yet to be sent + */ CopyOnWriteArrayList outgoingMessageQueue = new CopyOnWriteArrayList(); - CircularByteBuffer incomingByteBuffer = new CircularByteBuffer(64 * 1024 * 124); + /** + * The byte buffer for storing incoming bytes + */ + CircularByteBuffer incomingByteBuffer = new CircularByteBuffer(CIRCULAR_BUFFER_SIZE); + + /** + * The block array used to read blocks of bytes in + */ + byte[] readBuffer = new byte[READ_BLOCK_SIZE]; + + /** + * The outgoing byte buffer + */ CopyOnWriteArrayList outgoingByteQueue = new CopyOnWriteArrayList(); + + /** + * The number of bytes read + */ + long totalBytesRead = 0; - + /** + * Constructor + * @param incomingStream The stream of incoming bytes + * @param outgoingStream The stream of outgoing bytes + */ public NetworkParser(InputStream incomingStream, OutputStream outgoingStream){ this.incomingStream = incomingStream; this.outgoingStream = outgoingStream; } - - public void start(){ - - } - - static final int READ_BUFFER_SIZE = 64 * 1024 * 1024; - byte[] readBuffer = new byte[READ_BUFFER_SIZE]; - public void readMessagesIn(){ - try { - //read in bytes - int bytesRead = 0; - byte currentByte = -1; - while(incomingStream.available() > 0){ - // nextValue = incomingStream.read(); - bytesRead = incomingStream.read(readBuffer, 0, READ_BUFFER_SIZE); - if(bytesRead > 0){ - incomingByteBuffer.add(readBuffer, bytesRead); - } + + /** + * Reads messages from the input stream + */ + public void readMessagesIn() throws IOException { + //read in bytes + int bytesRead = 0; + while(incomingStream.available() > 0){ + // nextValue = incomingStream.read(); + bytesRead = incomingStream.read(readBuffer, 0, READ_BLOCK_SIZE); + if(bytesRead > 0){ + incomingByteBuffer.add(readBuffer, bytesRead); } - //parse byte queue for messages - //for each message, append to clientIncomingMessageQueue - NetworkMessage newMessage; - while((newMessage = NetworkMessage.parseBytestreamForMessage(incomingByteBuffer))!=null){ - incomingMessageQueue.add(newMessage); - } - } catch (IOException ex) { - ex.printStackTrace(); - System.exit(0); + totalBytesRead = totalBytesRead + bytesRead; + } + //parse byte queue for messages + //for each message, append to clientIncomingMessageQueue + NetworkMessage newMessage; + while((newMessage = NetworkMessage.parseBytestreamForMessage(incomingByteBuffer))!=null){ + incomingMessageQueue.add(newMessage); } } + /** + * Pushes messages out across the output stream + * @throws IOException Thrown if a message fails to serialize or the output stream fails to write + */ public void pushMessagesOut() throws IOException { for(NetworkMessage message : outgoingMessageQueue){ outgoingMessageQueue.remove(message); -// System.out.println("Write message of type " + message.getType()); outgoingStream.write(message.getRawBytes()); } } + /** + * Checks if there is a fully parsed incoming message in the queue + * @return true if there is message in the queue, false otherwise + */ public boolean hasIncomingMessaage(){ return incomingMessageQueue.size() > 0; } + /** + * Pops a fully parsed incoming message from the queue + * @return The message + */ public NetworkMessage popIncomingMessage(){ return incomingMessageQueue.remove(0); } + /** + * Adds a message to the outgoing queue + * @param message The message + */ public void addOutgoingMessage(NetworkMessage message){ outgoingMessageQueue.add(message); } @@ -90,5 +146,13 @@ public class NetworkParser { public void copyOutgoingMessages(List messages){ messages.addAll(outgoingMessageQueue); } + + /** + * Gets the total number of bytes read by this connection + * @return The total number of bytes + */ + public long getNumberOfBytesRead(){ + return totalBytesRead; + } } diff --git a/src/main/java/electrosphere/net/parser/util/ByteStreamUtils.java b/src/main/java/electrosphere/net/parser/util/ByteStreamUtils.java index f1c036cc..edfb9912 100644 --- a/src/main/java/electrosphere/net/parser/util/ByteStreamUtils.java +++ b/src/main/java/electrosphere/net/parser/util/ByteStreamUtils.java @@ -1,6 +1,6 @@ package electrosphere.net.parser.util; -import electrosphere.net.parser.net.raw.CircularByteBuffer; +import io.github.studiorailgun.CircularByteBuffer; import java.nio.ByteBuffer; import java.util.List; diff --git a/src/main/java/electrosphere/net/server/MessageProtocol.java b/src/main/java/electrosphere/net/server/MessageProtocol.java index 7f506bab..2cef56d5 100644 --- a/src/main/java/electrosphere/net/server/MessageProtocol.java +++ b/src/main/java/electrosphere/net/server/MessageProtocol.java @@ -67,7 +67,7 @@ public class MessageProtocol { * @param message The message */ public void handleAsyncMessage(NetworkMessage message){ - Globals.profiler.beginAggregateCpuSample("MessageProtocol(client).handleAsyncMessage"); + Globals.profiler.beginAggregateCpuSample("MessageProtocol(server).handleAsyncMessage"); printMessage(message); NetworkMessage result = null; switch(message.getType()){ @@ -113,7 +113,7 @@ public class MessageProtocol { } public void handleSyncMessages(){ - Globals.profiler.beginAggregateCpuSample("MessageProtocol(client).handleSyncMessages"); + Globals.profiler.beginAggregateCpuSample("MessageProtocol(server).handleSyncMessages"); this.synchronousMessageLock.acquireUninterruptibly(); LoggerInterface.loggerNetworking.DEBUG_LOOP("[SERVER] HANDLE SYNC MESSAGE [Sync queue size: " + this.synchronousMessageQueue.size() + "]"); for(NetworkMessage message : synchronousMessageQueue){ diff --git a/src/main/java/electrosphere/net/server/Server.java b/src/main/java/electrosphere/net/server/Server.java index 0458673e..fb4228b6 100644 --- a/src/main/java/electrosphere/net/server/Server.java +++ b/src/main/java/electrosphere/net/server/Server.java @@ -170,8 +170,11 @@ public class Server implements Runnable { * @return The first connection */ public ServerConnectionHandler getFirstConnection(){ + ServerConnectionHandler firstCon = null; connectListLock.acquireUninterruptibly(); - ServerConnectionHandler firstCon = this.activeConnections.get(0); + if(this.activeConnections.size() > 0){ + firstCon = this.activeConnections.get(0); + } connectListLock.release(); return firstCon; } diff --git a/src/main/java/electrosphere/net/server/ServerConnectionHandler.java b/src/main/java/electrosphere/net/server/ServerConnectionHandler.java index f4d6ea82..dfea176c 100644 --- a/src/main/java/electrosphere/net/server/ServerConnectionHandler.java +++ b/src/main/java/electrosphere/net/server/ServerConnectionHandler.java @@ -8,6 +8,7 @@ import electrosphere.net.parser.net.message.NetworkMessage; import electrosphere.net.parser.net.message.ServerMessage; import electrosphere.net.parser.net.raw.NetworkParser; import electrosphere.net.server.player.Player; +import electrosphere.util.CodeUtils; import java.io.IOException; import java.io.InputStream; @@ -69,7 +70,7 @@ public class ServerConnectionHandler implements Runnable { //thresholds for determining when to send pings and when a client has disconnected static final long SEND_PING_THRESHOLD = 3000; - static final long PING_DISCONNECT_THRESHOLD = 20000; + static final long PING_DISCONNECT_THRESHOLD = 60 * 1000; //used to keep track of ping/pong messages with client long lastPingTime = 0; long lastPongTime = 0; @@ -191,18 +192,19 @@ public class ServerConnectionHandler implements Runnable { initialized = true; while(Globals.threadManager.shouldKeepRunning() && this.isConnected == true && Globals.server != null && Globals.server.isOpen()){ + boolean receivedMessageThisLoop = false; // // Main Loop // //parse messages both incoming and outgoing try { - parseMessages(); + receivedMessageThisLoop = parseMessages(); } catch (SocketException e) { //if we get a SocketException broken pipe (basically the client dc'd without telling us) //set flag to disconnect client //TODO: fix, this doesn't actually catch the socket exception which is exceedingly obnoxious socketException = true; - LoggerInterface.loggerNetworking.DEBUG(e.getLocalizedMessage()); + LoggerInterface.loggerNetworking.ERROR("Client disconnected", e); this.disconnect(); break; } catch (IOException e){ @@ -210,14 +212,18 @@ public class ServerConnectionHandler implements Runnable { //set flag to disconnect client //TODO: fix, this doesn't actually catch the socket exception which is exceedingly obnoxious socketException = true; - LoggerInterface.loggerNetworking.DEBUG(e.getLocalizedMessage()); + LoggerInterface.loggerNetworking.ERROR("Client disconnected", e); this.disconnect(); break; } // - // Pings + // Timeout logic // + //mark as alive if a message was received from client + if(receivedMessageThisLoop){ + this.markReceivedPongMessage(); + } //ping logic long currentTime = System.currentTimeMillis(); //basically if we haven't sent a ping in a while, send one @@ -235,7 +241,14 @@ public class ServerConnectionHandler implements Runnable { //check if we meet disconnection criteria //has it been too long since the last ping? //have we had a socket exception? - if(lastPingTime - lastPongTime > PING_DISCONNECT_THRESHOLD || this.socketException == true){ + if(lastPingTime - lastPongTime > PING_DISCONNECT_THRESHOLD){ + //disconnected from the server + LoggerInterface.loggerNetworking.WARNING("Client timeout"); + //run disconnect routine + disconnect(); + break; + } + if(this.socketException == true){ //disconnected from the server LoggerInterface.loggerNetworking.WARNING("Client disconnected"); //run disconnect routine @@ -251,13 +264,16 @@ public class ServerConnectionHandler implements Runnable { * Had to wrap the message parsing block in a function to throw a SocketException * without my linter freaking out * @throws SocketException + * @return true if connection is alive, false otherwise */ - void parseMessages() throws SocketException, IOException { + boolean parseMessages() throws SocketException, IOException { + boolean rVal = false; // //Read in messages // //attempt poll incoming messages networkParser.readMessagesIn(); + rVal = networkParser.hasIncomingMessaage(); // @@ -305,8 +321,9 @@ public class ServerConnectionHandler implements Runnable { TimeUnit.MILLISECONDS.sleep(1); } catch (InterruptedException ex) { //silently ignore - // CodeUtils.todo(ex, "Handle sleep interrupt on server connection"); + CodeUtils.todo(ex, "Handle sleep interrupt on server connection"); } + return rVal; } /** @@ -404,4 +421,15 @@ public class ServerConnectionHandler implements Runnable { } } + /** + * Gets the total number of bytes read by this connection + * @return The total number of bytes + */ + public long getNumBytesRead(){ + if(this.networkParser == null){ + return 0; + } + return this.networkParser.getNumberOfBytesRead(); + } + } diff --git a/src/main/java/electrosphere/net/server/protocol/TerrainProtocol.java b/src/main/java/electrosphere/net/server/protocol/TerrainProtocol.java index 61fd39dc..99b1d4f8 100644 --- a/src/main/java/electrosphere/net/server/protocol/TerrainProtocol.java +++ b/src/main/java/electrosphere/net/server/protocol/TerrainProtocol.java @@ -3,10 +3,12 @@ package electrosphere.net.server.protocol; import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; +import java.util.function.Consumer; import org.joml.Vector3d; import electrosphere.engine.Globals; +import electrosphere.logger.LoggerInterface; import electrosphere.net.parser.net.message.TerrainMessage; import electrosphere.net.server.ServerConnectionHandler; import electrosphere.net.server.player.Player; @@ -23,20 +25,32 @@ public class TerrainProtocol implements ServerProtocolTemplate { @Override public TerrainMessage handleAsyncMessage(ServerConnectionHandler connectionHandler, TerrainMessage message) { + switch(message.getMessageSubtype()){ + case REQUESTCHUNKDATA: { + sendWorldSubChunkAsync(connectionHandler, + message.getworldX(), message.getworldY(), message.getworldZ() + ); + return null; + } + default: { + } break; + } return message; } @Override public void handleSyncMessage(ServerConnectionHandler connectionHandler, TerrainMessage message) { switch(message.getMessageSubtype()){ - case REQUESTMETADATA: + case REQUESTMETADATA: { sendWorldMetadata(connectionHandler); - break; - case REQUESTCHUNKDATA: + } break; + case REQUESTCHUNKDATA: { + LoggerInterface.loggerNetworking.DEBUG("(Server) Received request for terrain " + message.getworldX() + " " + message.getworldY() + " " + message.getworldZ()); + // System.out.println("Received request for terrain " + message.getworldX() + " " + message.getworldY() + " " + message.getworldZ()); sendWorldSubChunk(connectionHandler, message.getworldX(), message.getworldY(), message.getworldZ() ); - break; + } break; case REQUESTEDITVOXEL: { attemptTerrainEdit(connectionHandler, message); } break; @@ -44,6 +58,8 @@ public class TerrainProtocol implements ServerProtocolTemplate { attemptUseTerrainEditPalette(connectionHandler, message); } break; case REQUESTFLUIDDATA: { + LoggerInterface.loggerNetworking.DEBUG("(Server) Received request for fluid " + message.getworldX() + " " + message.getworldY() + " " + message.getworldZ()); + // System.out.println("Received request for fluid " + message.getworldX() + " " + message.getworldY() + " " + message.getworldZ()); sendWorldFluidSubChunk(connectionHandler, message.getworldX(), message.getworldY(), message.getworldZ() ); @@ -70,28 +86,7 @@ public class TerrainProtocol implements ServerProtocolTemplate { * @param worldZ the world z */ static void sendWorldSubChunk(ServerConnectionHandler connectionHandler, int worldX, int worldY, int worldZ){ - /* - int locationX, - int locationY, - float macroValue00, - float macroValue01, - float macroValue02, - float macroValue10, - float macroValue11, - float macroValue12, - float macroValue20, - float macroValue21, - float macroValue22, - long randomizerValue00, - long randomizerValue01, - long randomizerValue02, - long randomizerValue10, - long randomizerValue11, - long randomizerValue12, - long randomizerValue20, - long randomizerValue21, - long randomizerValue22 - */ + Globals.profiler.beginAggregateCpuSample("TerrainProtocol(server).sendWorldSubChunk"); // System.out.println("Received request for chunk " + message.getworldX() + " " + message.getworldY()); Realm realm = Globals.playerManager.getPlayerRealm(connectionHandler.getPlayer()); @@ -99,12 +94,8 @@ public class TerrainProtocol implements ServerProtocolTemplate { return; } - //get the chunk + //request chunk ServerTerrainChunk chunk = realm.getServerWorldData().getServerTerrainManager().getChunk(worldX, worldY, worldZ); - - // float[][] macroValues = chunk.getMacroValues();//Globals.serverTerrainManager.getRad5MacroValues(message.getworldX(), message.getworldY()); - - // long[][] randomizer = chunk.getRandomizer();//Globals.serverTerrainManager.getRandomizer(message.getworldX(), message.getworldY()); //The length along each access of the chunk data. Typically, should be at least 17. //Because CHUNK_SIZE is 16, 17 adds the necessary extra value. Each chunk needs the value of the immediately following position to generate @@ -135,89 +126,68 @@ public class TerrainProtocol implements ServerProtocolTemplate { } } - + System.out.println("(Server) Send terrain at " + worldX + " " + worldY + " " + worldZ); + LoggerInterface.loggerNetworking.DEBUG("(Server) Send terrain at " + worldX + " " + worldY + " " + worldZ); connectionHandler.addMessagetoOutgoingQueue(TerrainMessage.constructsendChunkDataMessage(worldX, worldY, worldZ, buffer.array())); - // int numMessages = 2 + chunk.getModifications().size(); + Globals.profiler.endCpuSample(); + } - // connectionHandler.addMessagetoOutgoingQueue( - // TerrainMessage.constructchunkLoadStartMessage(worldX, worldY, numMessages) - // ); + /** + * Sends a subchunk to the client + * @param connectionHandler The connection handler + * @param worldX the world x + * @param worldY the world y + * @param worldZ the world z + */ + static void sendWorldSubChunkAsync(ServerConnectionHandler connectionHandler, int worldX, int worldY, int worldZ){ + Globals.profiler.beginAggregateCpuSample("TerrainProtocol(server).sendWorldSubChunk"); + + // System.out.println("Received request for chunk " + message.getworldX() + " " + message.getworldY()); + Realm realm = Globals.playerManager.getPlayerRealm(connectionHandler.getPlayer()); + if(realm.getServerWorldData().getServerTerrainManager() == null){ + return; + } - // connectionHandler.addMessagetoOutgoingQueue( - // TerrainMessage.constructMacroValueMessage( - // worldX, - // worldY, - - - // macroValues[0][0], - // macroValues[0][1], - // macroValues[0][2], - // macroValues[0][3], - // macroValues[0][4], - // macroValues[1][0], - // macroValues[1][1], - // macroValues[1][2], - // macroValues[1][3], - // macroValues[1][4], - // macroValues[2][0], - // macroValues[2][1], - // macroValues[2][2], - // macroValues[2][3], - // macroValues[2][4], - // macroValues[3][0], - // macroValues[3][1], - // macroValues[3][2], - // macroValues[3][3], - // macroValues[3][4], - // macroValues[4][0], - // macroValues[4][1], - // macroValues[4][2], - // macroValues[4][3], - // macroValues[4][4], - - - // randomizer[0][0], - // randomizer[0][1], - // randomizer[0][2], - // randomizer[0][3], - // randomizer[0][4], - // randomizer[1][0], - // randomizer[1][1], - // randomizer[1][2], - // randomizer[1][3], - // randomizer[1][4], - // randomizer[2][0], - // randomizer[2][1], - // randomizer[2][2], - // randomizer[2][3], - // randomizer[2][4], - // randomizer[3][0], - // randomizer[3][1], - // randomizer[3][2], - // randomizer[3][3], - // randomizer[3][4], - // randomizer[4][0], - // randomizer[4][1], - // randomizer[4][2], - // randomizer[4][3], - // randomizer[4][4] - // ) - // ); + Consumer onLoad = (ServerTerrainChunk chunk) -> { + //The length along each access of the chunk data. Typically, should be at least 17. + //Because CHUNK_SIZE is 16, 17 adds the necessary extra value. Each chunk needs the value of the immediately following position to generate + //chunk data that connects seamlessly to the next chunk. + int xWidth = chunk.getWeights().length; + int yWidth = chunk.getWeights()[0].length; + int zWidth = chunk.getWeights()[0][0].length; - // for(TerrainModification modification : chunk.getModifications()){ - // connectionHandler.addMessagetoOutgoingQueue( - // TerrainMessage.constructheightMapModificationMessage( - // modification.getValue(), - // modification.getWorldX(), - // 0, - // modification.getWorldY(), - // modification.getLocationX(), - // 0, - // modification.getLocationY() - // ) - // ); - // } + ByteBuffer buffer = ByteBuffer.allocate(xWidth*yWidth*zWidth*(4+4)); + FloatBuffer floatView = buffer.asFloatBuffer(); + + for(int x = 0; x < xWidth; x++){ + for(int y = 0; y < yWidth; y++){ + for(int z = 0; z < zWidth; z++){ + floatView.put(chunk.getWeights()[x][y][z]); + } + } + } + + IntBuffer intView = buffer.asIntBuffer(); + intView.position(floatView.position()); + + for(int x = 0; x < xWidth; x++){ + for(int y = 0; y < yWidth; y++){ + for(int z = 0; z < zWidth; z++){ + intView.put(chunk.getValues()[x][y][z]); + } + } + } + + // System.out.println("(Server) Send terrain at " + worldX + " " + worldY + " " + worldZ); + LoggerInterface.loggerNetworking.DEBUG("(Server) Send terrain at " + worldX + " " + worldY + " " + worldZ); + connectionHandler.addMessagetoOutgoingQueue(TerrainMessage.constructsendChunkDataMessage(worldX, worldY, worldZ, buffer.array())); + }; + + //request chunk + realm.getServerWorldData().getServerTerrainManager().getChunkAsync(worldX, worldY, worldZ, onLoad); + + Globals.profiler.endCpuSample(); } diff --git a/src/main/java/electrosphere/renderer/meshgen/TerrainChunkModelGeneration.java b/src/main/java/electrosphere/renderer/meshgen/TerrainChunkModelGeneration.java index 763008a5..880065ec 100644 --- a/src/main/java/electrosphere/renderer/meshgen/TerrainChunkModelGeneration.java +++ b/src/main/java/electrosphere/renderer/meshgen/TerrainChunkModelGeneration.java @@ -25,6 +25,11 @@ import electrosphere.server.terrain.manager.ServerTerrainChunk; public class TerrainChunkModelGeneration { + /** + * The minimum iso value + */ + public static final float MIN_ISO_VALUE = 0.01f; + //http://paulbourke.net/geometry/polygonise/ public static int edgeTable[]={ @@ -620,7 +625,7 @@ public class TerrainChunkModelGeneration { return new Vector3f(x,y,z); } - public static TerrainChunkData generateTerrainChunkData(float[][][] terrainGrid, int[][][] textureGrid, int lod){ + public static TerrainChunkData generateTerrainChunkData(float[][][] terrainGrid, int[][][] textureGrid){ // 5 6 // +-------------+ +-----5-------+ ^ Y @@ -664,7 +669,7 @@ public class TerrainChunkModelGeneration { textureGrid[x+0][y+1][z+0], textureGrid[x+0][y+1][z+1], textureGrid[x+1][y+1][z+1], textureGrid[x+1][y+1][z+0] ); //polygonize the current gridcell - polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + polygonize(currentCell, MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); } } } diff --git a/src/main/java/electrosphere/renderer/meshgen/TransvoxelModelGeneration.java b/src/main/java/electrosphere/renderer/meshgen/TransvoxelModelGeneration.java index 606b8d46..9608f7f3 100644 --- a/src/main/java/electrosphere/renderer/meshgen/TransvoxelModelGeneration.java +++ b/src/main/java/electrosphere/renderer/meshgen/TransvoxelModelGeneration.java @@ -25,6 +25,11 @@ public class TransvoxelModelGeneration { //the lower the value, the more of the low resolution chunk we will see static final float TRANSITION_CELL_WIDTH = 0.5f; + /** + * The dimension of the array for the face generator. It must be 2 * + 1. The extra 1 is for the neighbor value + */ + public static final int FACE_DATA_DIMENSIONS = ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE + ServerTerrainChunk.CHUNK_DIMENSION; + @@ -647,7 +652,7 @@ public class TransvoxelModelGeneration { if(firstSample > isolevel){ samplerIndex[i] = getTransvoxelTextureValue(transitionCell.simpleFaceAtlasValues,transitionCell.complexFaceAtlasValues,firstCornerSampleIndex); } else { - samplerIndex[i] = sampleIndexTable[1][i]; + samplerIndex[i] = getTransvoxelTextureValue(transitionCell.simpleFaceAtlasValues,transitionCell.complexFaceAtlasValues,secondCornerSampleIndex); } } } @@ -982,8 +987,7 @@ public class TransvoxelModelGeneration { /** * Generates mesh data given chunk data - * @param terrainGrid The chunk data - * @param textureGrid The chunk texture data + * @param chunkData The chunk data * @return The mesh data */ public static TerrainChunkData generateTerrainChunkData(TransvoxelChunkData chunkData){ @@ -1019,9 +1023,9 @@ public class TransvoxelModelGeneration { List samplerTriangles = new LinkedList(); //List of UVs List UVs = new LinkedList(); - - - + + + // //Generate the interior of the mesh for(int x = 1; x < chunkData.terrainGrid.length - 2; x++){ @@ -1037,7 +1041,7 @@ public class TransvoxelModelGeneration { chunkData.textureGrid[x+0][y+1][z+0], chunkData.textureGrid[x+0][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+0] ); //polygonize the current gridcell - polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); } } } @@ -1059,8 +1063,8 @@ public class TransvoxelModelGeneration { //generate the x-positive face if(chunkData.xPositiveEdgeIso != null){ int x = ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE - 2; - for(int y = yStartIndex; y < yEndIndex - 1; y++){ - for(int z = zStartIndex; z < zEndIndex - 1; z++){ + for(int y = yStartIndex; y < yEndIndex; y++){ + for(int z = zStartIndex; z < zEndIndex; z++){ // //Generate the transition cell // @@ -1087,7 +1091,7 @@ public class TransvoxelModelGeneration { chunkData.xPositiveEdgeAtlas[(y+0)*2+0][(z+0)*2+0], chunkData.xPositiveEdgeAtlas[(y+1)*2+0][(z+0)*2+0], chunkData.xPositiveEdgeAtlas[(y+0)*2+0][(z+1)*2+0], chunkData.xPositiveEdgeAtlas[(y+1)*2+0][(z+1)*2+0] ); - polygonizeTransition(currentTransitionCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + polygonizeTransition(currentTransitionCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); // //Generate the normal cell with half width @@ -1101,7 +1105,7 @@ public class TransvoxelModelGeneration { chunkData.textureGrid[x+0][y+1][z+0], chunkData.textureGrid[x+0][y+1][z+1], chunkData.xPositiveEdgeAtlas[(y+1)*2+0][(z+1)*2+0], chunkData.xPositiveEdgeAtlas[(y+1)*2+0][(z+0)*2+0] ); //polygonize the current gridcell - polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); } } } else { @@ -1118,7 +1122,7 @@ public class TransvoxelModelGeneration { chunkData.textureGrid[x+0][y+1][z+0], chunkData.textureGrid[x+0][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+0] ); //polygonize the current gridcell - polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); } } } @@ -1133,8 +1137,8 @@ public class TransvoxelModelGeneration { //generate the x-negative face if(chunkData.xNegativeEdgeIso != null){ int x = 0; - for(int y = yStartIndex; y < yEndIndex - 1; y++){ - for(int z = zStartIndex; z < zEndIndex - 1; z++){ + for(int y = yStartIndex; y < yEndIndex; y++){ + for(int z = zStartIndex; z < zEndIndex; z++){ // //Generate the transition cell // @@ -1161,7 +1165,7 @@ public class TransvoxelModelGeneration { chunkData.xNegativeEdgeAtlas[(y+0)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeAtlas[(y+0)*2+0][(z+1)*2+0], chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+1)*2+0] ); - polygonizeTransition(currentTransitionCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + polygonizeTransition(currentTransitionCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); // //Generate the normal cell with half width @@ -1175,7 +1179,7 @@ public class TransvoxelModelGeneration { chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+1)*2+0], chunkData.textureGrid[x+1][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+0] ); //polygonize the current gridcell - polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); } } } else { @@ -1192,7 +1196,7 @@ public class TransvoxelModelGeneration { chunkData.textureGrid[x+0][y+1][z+0], chunkData.textureGrid[x+0][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+0] ); //polygonize the current gridcell - polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); } } } @@ -1206,8 +1210,8 @@ public class TransvoxelModelGeneration { //generate the y-positive face if(chunkData.yPositiveEdgeIso != null){ int y = ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE - 2; - for(int x = xStartIndex; x < xEndIndex - 1; x++){ - for(int z = zStartIndex; z < zEndIndex - 1; z++){ + for(int x = xStartIndex; x < xEndIndex; x++){ + for(int z = zStartIndex; z < zEndIndex; z++){ // //Generate the transition cell // @@ -1234,7 +1238,7 @@ public class TransvoxelModelGeneration { chunkData.yPositiveEdgeAtlas[(x+0)*2+0][(z+0)*2+0], chunkData.yPositiveEdgeAtlas[(x+1)*2+0][(z+0)*2+0], chunkData.yPositiveEdgeAtlas[(x+0)*2+0][(z+1)*2+0], chunkData.yPositiveEdgeAtlas[(x+1)*2+0][(z+1)*2+0] ); - polygonizeTransition(currentTransitionCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + polygonizeTransition(currentTransitionCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); // //Generate the normal cell with half width @@ -1248,7 +1252,7 @@ public class TransvoxelModelGeneration { chunkData.yPositiveEdgeAtlas[(x+0)*2+0][(z+0)*2+0], chunkData.yPositiveEdgeAtlas[(x+0)*2+0][(z+1)*2+0], chunkData.yPositiveEdgeAtlas[(x+1)*2+0][(z+1)*2+0], chunkData.yPositiveEdgeAtlas[(x+1)*2+0][(z+0)*2+0] ); //polygonize the current gridcell - polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); } } } else { @@ -1265,7 +1269,7 @@ public class TransvoxelModelGeneration { chunkData.textureGrid[x+0][y+1][z+0], chunkData.textureGrid[x+0][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+0] ); //polygonize the current gridcell - polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); } } } @@ -1279,8 +1283,8 @@ public class TransvoxelModelGeneration { //generate the y-negative face if(chunkData.yNegativeEdgeIso != null){ int y = 0; - for(int x = xStartIndex; x < xEndIndex - 1; x++){ - for(int z = zStartIndex; z < zEndIndex - 1; z++){ + for(int x = xStartIndex; x < xEndIndex; x++){ + for(int z = zStartIndex; z < zEndIndex; z++){ // //Generate the transition cell // @@ -1307,7 +1311,7 @@ public class TransvoxelModelGeneration { chunkData.yNegativeEdgeAtlas[(x+0)*2+0][(z+0)*2+0], chunkData.yNegativeEdgeAtlas[(x+1)*2+0][(z+0)*2+0], chunkData.yNegativeEdgeAtlas[(x+0)*2+0][(z+1)*2+0], chunkData.yNegativeEdgeAtlas[(x+1)*2+0][(z+1)*2+0] ); - polygonizeTransition(currentTransitionCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + polygonizeTransition(currentTransitionCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); // //Generate the normal cell with half width @@ -1315,13 +1319,13 @@ public class TransvoxelModelGeneration { currentCell.setValues( new Vector3f(x+0,y+1,z+0), new Vector3f(x+0,y+1,z+1), new Vector3f(x+1,y+1,z+1), new Vector3f(x+1,y+1,z+0), new Vector3f(x+0,y+TRANSITION_CELL_WIDTH,z+0), new Vector3f(x+0,y+TRANSITION_CELL_WIDTH,z+1), new Vector3f(x+1,y+TRANSITION_CELL_WIDTH,z+1), new Vector3f(x+1,y+TRANSITION_CELL_WIDTH,z+0), - chunkData.terrainGrid[x+0][y+0][z+0], chunkData.terrainGrid[x+0][y+0][z+1], chunkData.terrainGrid[x+1][y+0][z+1], chunkData.terrainGrid[x+1][y+0][z+0], + chunkData.terrainGrid[x+0][y+1][z+0], chunkData.terrainGrid[x+0][y+1][z+1], chunkData.terrainGrid[x+1][y+1][z+1], chunkData.terrainGrid[x+1][y+1][z+0], chunkData.yNegativeEdgeIso[(x+0)*2+0][(z+0)*2+0], chunkData.yNegativeEdgeIso[(x+0)*2+0][(z+1)*2+0], chunkData.yNegativeEdgeIso[(x+1)*2+0][(z+1)*2+0], chunkData.yNegativeEdgeIso[(x+1)*2+0][(z+0)*2+0], - chunkData.textureGrid[x+0][y+0][z+0], chunkData.textureGrid[x+0][y+0][z+1], chunkData.textureGrid[x+1][y+0][z+1], chunkData.textureGrid[x+1][y+0][z+0], + chunkData.textureGrid[x+0][y+1][z+0], chunkData.textureGrid[x+0][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+0], chunkData.yNegativeEdgeAtlas[(x+0)*2+0][(z+0)*2+0], chunkData.yNegativeEdgeAtlas[(x+0)*2+0][(z+1)*2+0], chunkData.yNegativeEdgeAtlas[(x+1)*2+0][(z+1)*2+0], chunkData.yNegativeEdgeAtlas[(x+1)*2+0][(z+0)*2+0] ); //polygonize the current gridcell - polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); } } } else { @@ -1338,7 +1342,7 @@ public class TransvoxelModelGeneration { chunkData.textureGrid[x+0][y+1][z+0], chunkData.textureGrid[x+0][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+0] ); //polygonize the current gridcell - polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); } } } @@ -1353,8 +1357,8 @@ public class TransvoxelModelGeneration { //generate the z-positive face if(chunkData.zPositiveEdgeIso != null){ int z = ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE - 2; - for(int x = xStartIndex; x < xEndIndex - 1; x++){ - for(int y = yStartIndex; y < yEndIndex - 1; y++){ + for(int x = xStartIndex; x < xEndIndex; x++){ + for(int y = yStartIndex; y < yEndIndex; y++){ // //Generate the transition cell // @@ -1381,7 +1385,7 @@ public class TransvoxelModelGeneration { chunkData.zPositiveEdgeAtlas[(x+0)*2+0][(y+0)*2+0], chunkData.zPositiveEdgeAtlas[(x+0)*2+0][(y+1)*2+0], chunkData.zPositiveEdgeAtlas[(x+1)*2+0][(y+0)*2+0], chunkData.zPositiveEdgeAtlas[(x+1)*2+0][(y+1)*2+0] ); - polygonizeTransition(currentTransitionCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + polygonizeTransition(currentTransitionCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); // //Generate the normal cell with half width @@ -1395,7 +1399,7 @@ public class TransvoxelModelGeneration { chunkData.textureGrid[x+0][y+1][z+0], chunkData.zPositiveEdgeAtlas[(x+0)*2+0][(y+1)*2+0], chunkData.zPositiveEdgeAtlas[(x+1)*2+0][(y+1)*2+0], chunkData.textureGrid[x+1][y+1][z+0] ); //polygonize the current gridcell - polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); } } } else { @@ -1412,7 +1416,7 @@ public class TransvoxelModelGeneration { chunkData.textureGrid[x+0][y+1][z+0], chunkData.textureGrid[x+0][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+0] ); //polygonize the current gridcell - polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); } } } @@ -1427,16 +1431,16 @@ public class TransvoxelModelGeneration { //generate the z-negative face if(chunkData.zNegativeEdgeIso != null){ int z = 0; - for(int x = xStartIndex; x < xEndIndex - 1; x++){ - for(int y = yStartIndex; y < yEndIndex - 1; y++){ + for(int x = xStartIndex; x < xEndIndex; x++){ + for(int y = yStartIndex; y < yEndIndex; y++){ // //Generate the transition cell // currentTransitionCell.setValues( //complex face vertex coordinates - new Vector3f(x+0,y,z), new Vector3f(x+0,y+TRANSITION_CELL_WIDTH,z), new Vector3f(x+0,y+1,z), + new Vector3f(x+0, y,z), new Vector3f(x+0, y+TRANSITION_CELL_WIDTH,z), new Vector3f(x+0, y+1,z), new Vector3f(x+TRANSITION_CELL_WIDTH,y,z), new Vector3f(x+TRANSITION_CELL_WIDTH,y+TRANSITION_CELL_WIDTH,z), new Vector3f(x+TRANSITION_CELL_WIDTH,y+1,z), - new Vector3f(x+1,y,z), new Vector3f(x+1,y+TRANSITION_CELL_WIDTH,z), new Vector3f(x+1,y+1,z), + new Vector3f(x+1, y,z), new Vector3f(x+1, y+TRANSITION_CELL_WIDTH,z), new Vector3f(x+1, y+1,z), //simple face vertex coordinates new Vector3f(x+0,y,z+TRANSITION_CELL_WIDTH), new Vector3f(x+0,y+1,z+TRANSITION_CELL_WIDTH), new Vector3f(x+1,y,z+TRANSITION_CELL_WIDTH), new Vector3f(x+1,y+1,z+TRANSITION_CELL_WIDTH), @@ -1455,7 +1459,7 @@ public class TransvoxelModelGeneration { chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+1)*2+0], chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+1)*2+0] ); - polygonizeTransition(currentTransitionCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + polygonizeTransition(currentTransitionCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); // //Generate the normal cell with half width @@ -1463,13 +1467,13 @@ public class TransvoxelModelGeneration { currentCell.setValues( new Vector3f(x+0,y+0,z+1), new Vector3f(x+0,y+0,z+TRANSITION_CELL_WIDTH), new Vector3f(x+1,y+0,z+TRANSITION_CELL_WIDTH), new Vector3f(x+1,y+0,z+1), new Vector3f(x+0,y+1,z+1), new Vector3f(x+0,y+1,z+TRANSITION_CELL_WIDTH), new Vector3f(x+1,y+1,z+TRANSITION_CELL_WIDTH), new Vector3f(x+1,y+1,z+1), - chunkData.terrainGrid[x+0][y+0][z+0], chunkData.zNegativeEdgeIso[(x+0)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeIso[(x+1)*2+0][(y+0)*2+0], chunkData.terrainGrid[x+1][y+0][z+0], - chunkData.terrainGrid[x+0][y+1][z+0], chunkData.zNegativeEdgeIso[(x+0)*2+0][(y+1)*2+0], chunkData.zNegativeEdgeIso[(x+1)*2+0][(y+1)*2+0], chunkData.terrainGrid[x+1][y+1][z+0], - chunkData.textureGrid[x+0][y+0][z+0], chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+0)*2+0], chunkData.textureGrid[x+1][y+0][z+0], - chunkData.textureGrid[x+0][y+1][z+0], chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+1)*2+0], chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+1)*2+0], chunkData.textureGrid[x+1][y+1][z+0] + chunkData.terrainGrid[x+0][y+0][z+1], chunkData.zNegativeEdgeIso[(x+0)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeIso[(x+1)*2+0][(y+0)*2+0], chunkData.terrainGrid[x+1][y+0][z+1], + chunkData.terrainGrid[x+0][y+1][z+1], chunkData.zNegativeEdgeIso[(x+0)*2+0][(y+1)*2+0], chunkData.zNegativeEdgeIso[(x+1)*2+0][(y+1)*2+0], chunkData.terrainGrid[x+1][y+1][z+1], + chunkData.textureGrid[x+0][y+0][z+1], chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+0)*2+0], chunkData.textureGrid[x+1][y+0][z+1], + chunkData.textureGrid[x+0][y+1][z+1], chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+1)*2+0], chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+1)*2+0], chunkData.textureGrid[x+1][y+1][z+1] ); //polygonize the current gridcell - polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); } } } else { @@ -1486,11 +1490,92 @@ public class TransvoxelModelGeneration { chunkData.textureGrid[x+0][y+1][z+0], chunkData.textureGrid[x+0][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+0] ); //polygonize the current gridcell - polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); } } } + + //xn-zn edge + if(chunkData.xNegativeEdgeIso != null && chunkData.zNegativeEdgeIso != null){ + int x = 0; + int z = 0; + int edgeLength = chunkWidth - (chunkData.yNegativeEdgeIso != null ? 1 : 0) - (chunkData.yPositiveEdgeIso != null ? 1 : 0); + int startIndex = 0 + (chunkData.yNegativeEdgeIso != null ? 1 : 0); + for(int i = startIndex; i < edgeLength - 1; i++){ + int y = i; + // + //Generate the x-side transition cell + // + currentTransitionCell.setValues( + //complex face vertex coordinates + new Vector3f(x,y,z), new Vector3f(x,y+TRANSITION_CELL_WIDTH,z), new Vector3f(x,y+1,z), + new Vector3f(x,y,z+TRANSITION_CELL_WIDTH), new Vector3f(x,y+TRANSITION_CELL_WIDTH,z+TRANSITION_CELL_WIDTH), new Vector3f(x,y+1,z+TRANSITION_CELL_WIDTH), + new Vector3f(x,y,z+1), new Vector3f(x,y+TRANSITION_CELL_WIDTH,z+1), new Vector3f(x,y+1,z+1), + //simple face vertex coordinates + new Vector3f(x+TRANSITION_CELL_WIDTH,y,z+TRANSITION_CELL_WIDTH), new Vector3f(x+TRANSITION_CELL_WIDTH,y+1,z+TRANSITION_CELL_WIDTH), + new Vector3f(x+TRANSITION_CELL_WIDTH,y,z+1), new Vector3f(x+TRANSITION_CELL_WIDTH,y+1,z+1), + //complex face iso values + chunkData.xNegativeEdgeIso[(y+0)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeIso[(y+0)*2+1][(z+0)*2+0], chunkData.xNegativeEdgeIso[(y+1)*2+0][(z+0)*2+0], + chunkData.xNegativeEdgeIso[(y+0)*2+0][(z+0)*2+1], chunkData.xNegativeEdgeIso[(y+0)*2+1][(z+0)*2+1], chunkData.xNegativeEdgeIso[(y+1)*2+0][(z+0)*2+1], + chunkData.xNegativeEdgeIso[(y+0)*2+0][(z+1)*2+0], chunkData.xNegativeEdgeIso[(y+0)*2+1][(z+1)*2+0], chunkData.xNegativeEdgeIso[(y+1)*2+0][(z+1)*2+0], + //simple face iso values + chunkData.xNegativeEdgeIso[(y+0)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeIso[(y+1)*2+0][(z+0)*2+0], + chunkData.xNegativeEdgeIso[(y+0)*2+0][(z+1)*2+0], chunkData.xNegativeEdgeIso[(y+1)*2+0][(z+1)*2+0], + //complex face texture atlas values + chunkData.xNegativeEdgeAtlas[(y+0)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeAtlas[(y+0)*2+1][(z+0)*2+0], chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+0)*2+0], + chunkData.xNegativeEdgeAtlas[(y+0)*2+0][(z+0)*2+1], chunkData.xNegativeEdgeAtlas[(y+0)*2+1][(z+0)*2+1], chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+0)*2+1], + chunkData.xNegativeEdgeAtlas[(y+0)*2+0][(z+1)*2+0], chunkData.xNegativeEdgeAtlas[(y+0)*2+1][(z+1)*2+0], chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+1)*2+0], + //simple face texture atlas values + chunkData.xNegativeEdgeAtlas[(y+0)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+0)*2+0], + chunkData.xNegativeEdgeAtlas[(y+0)*2+0][(z+1)*2+0], chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+1)*2+0] + ); + polygonizeTransition(currentTransitionCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + + // + //Generate the z-side transition cell + // + currentTransitionCell.setValues( + //complex face vertex coordinates + new Vector3f(x+0, y,z), new Vector3f(x+0, y+TRANSITION_CELL_WIDTH,z), new Vector3f(x+0, y+1,z), + new Vector3f(x+TRANSITION_CELL_WIDTH,y,z), new Vector3f(x+TRANSITION_CELL_WIDTH,y+TRANSITION_CELL_WIDTH,z), new Vector3f(x+TRANSITION_CELL_WIDTH,y+1,z), + new Vector3f(x+1, y,z), new Vector3f(x+1, y+TRANSITION_CELL_WIDTH,z), new Vector3f(x+1, y+1,z), + //simple face vertex coordinates + new Vector3f(x+TRANSITION_CELL_WIDTH,y,z+TRANSITION_CELL_WIDTH), new Vector3f(x+TRANSITION_CELL_WIDTH,y+1,z+TRANSITION_CELL_WIDTH), + new Vector3f(x+1,y,z+TRANSITION_CELL_WIDTH), new Vector3f(x+1,y+1,z+TRANSITION_CELL_WIDTH), + //complex face iso values + chunkData.zNegativeEdgeIso[(x+0)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeIso[(x+0)*2+0][(y+0)*2+1], chunkData.zNegativeEdgeIso[(x+0)*2+0][(y+1)*2+0], + chunkData.zNegativeEdgeIso[(x+0)*2+1][(y+0)*2+0], chunkData.zNegativeEdgeIso[(x+0)*2+1][(y+0)*2+1], chunkData.zNegativeEdgeIso[(x+0)*2+1][(y+1)*2+0], + chunkData.zNegativeEdgeIso[(x+1)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeIso[(x+1)*2+0][(y+0)*2+1], chunkData.zNegativeEdgeIso[(x+1)*2+0][(y+1)*2+0], + //simple face iso values + chunkData.zNegativeEdgeIso[(x+0)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeIso[(x+0)*2+0][(y+1)*2+0], + chunkData.zNegativeEdgeIso[(x+1)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeIso[(x+1)*2+0][(y+1)*2+0], + //complex face texture atlas values + chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+0)*2+1], chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+1)*2+0], + chunkData.zNegativeEdgeAtlas[(x+0)*2+1][(y+0)*2+0], chunkData.zNegativeEdgeAtlas[(x+0)*2+1][(y+0)*2+1], chunkData.zNegativeEdgeAtlas[(x+0)*2+1][(y+1)*2+0], + chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+0)*2+1], chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+1)*2+0], + //simple face texture atlas values + chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+1)*2+0], + chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+1)*2+0] + ); + polygonizeTransition(currentTransitionCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + + // + //Generate the normal cell with half width + // + currentCell.setValues( + new Vector3f(x+TRANSITION_CELL_WIDTH,y+0,z+TRANSITION_CELL_WIDTH), new Vector3f(x+TRANSITION_CELL_WIDTH,y+0,z+1), new Vector3f(x+1,y+0,z+1), new Vector3f(x+1,y+0,z+TRANSITION_CELL_WIDTH), + new Vector3f(x+TRANSITION_CELL_WIDTH,y+1,z+TRANSITION_CELL_WIDTH), new Vector3f(x+TRANSITION_CELL_WIDTH,y+1,z+1), new Vector3f(x+1,y+1,z+1), new Vector3f(x+1,y+1,z+TRANSITION_CELL_WIDTH), + chunkData.xNegativeEdgeIso[(y+0)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeIso[(y+0)*2+0][(z+1)*2+0], chunkData.terrainGrid[x+1][y+0][z+1], chunkData.terrainGrid[x+1][y+0][z+0], + chunkData.xNegativeEdgeIso[(y+1)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeIso[(y+1)*2+0][(z+1)*2+0], chunkData.terrainGrid[x+1][y+1][z+1], chunkData.terrainGrid[x+1][y+1][z+0], + chunkData.xNegativeEdgeAtlas[(y+0)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeAtlas[(y+0)*2+0][(z+1)*2+0], chunkData.textureGrid[x+1][y+0][z+1], chunkData.textureGrid[x+1][y+0][z+0], + chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+1)*2+0], chunkData.textureGrid[x+1][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+0] + ); + //polygonize the current gridcell + polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + } + } + diff --git a/src/main/java/electrosphere/renderer/ui/imgui/ImGuiLinePlot.java b/src/main/java/electrosphere/renderer/ui/imgui/ImGuiLinePlot.java index 74adff72..3a9cc95b 100644 --- a/src/main/java/electrosphere/renderer/ui/imgui/ImGuiLinePlot.java +++ b/src/main/java/electrosphere/renderer/ui/imgui/ImGuiLinePlot.java @@ -18,6 +18,11 @@ public class ImGuiLinePlot implements ImGuiElement { //the data sets to draw List dataSets = new LinkedList(); + /** + * Size of the plot + */ + ImVec2 size = new ImVec2(-1,-1); + /** * Creates an im gui line plot */ @@ -25,9 +30,18 @@ public class ImGuiLinePlot implements ImGuiElement { this.plotTitle = plotTitle; } + /** + * Creates an im gui line plot + */ + public ImGuiLinePlot(String plotTitle, int sizeX, int sizeY){ + this.plotTitle = plotTitle; + this.size.x = sizeX; + this.size.y = sizeY; + } + @Override public void draw() { - if(ImPlot.beginPlot(plotTitle,"","",new ImVec2(-1,-1),0,ImPlotAxisFlags.AutoFit,ImPlotAxisFlags.AutoFit)){ + if(ImPlot.beginPlot(plotTitle,"","",size,0,ImPlotAxisFlags.AutoFit,ImPlotAxisFlags.AutoFit)){ for(ImGuiLinePlotDataset dataSet : dataSets){ double[] xs = dataSet.xData.stream().mapToDouble(Double::doubleValue).toArray();//(Double[])dataSet.xData.toArray(new Double[dataSet.xData.size()]); double[] ys = dataSet.yData.stream().mapToDouble(Double::doubleValue).toArray();//(Double[])dataSet.yData.toArray(new Double[dataSet.yData.size()]); @@ -102,6 +116,15 @@ public class ImGuiLinePlot implements ImGuiElement { } } + /** + * Zeroes out the dataset + */ + public void zeroOut(){ + for(int i = 0; i < limit; i++){ + this.addPoint(i, 0); + } + } + } diff --git a/src/main/java/electrosphere/server/MainServerFunctions.java b/src/main/java/electrosphere/server/MainServerFunctions.java index ba931dea..3c4f665e 100644 --- a/src/main/java/electrosphere/server/MainServerFunctions.java +++ b/src/main/java/electrosphere/server/MainServerFunctions.java @@ -25,6 +25,8 @@ public class MainServerFunctions { if(Globals.server != null){ Globals.server.synchronousPacketHandling(); } + Globals.profiler.endCpuSample(); + Globals.profiler.beginCpuSample("Server process synchronization messages"); if(Globals.serverSynchronizationManager != null){ Globals.serverSynchronizationManager.processMessages(); } diff --git a/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java b/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java index 93138b0f..81bfdb28 100644 --- a/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java +++ b/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java @@ -44,7 +44,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager /** * The max grid size allowed */ - public static final int MAX_GRID_SIZE = 10; + public static final int MAX_GRID_SIZE = 2000 * 1024; /** * Tracks whether this manager has been flagged to unload cells or not diff --git a/src/main/java/electrosphere/server/datacell/physics/DataCellPhysicsManager.java b/src/main/java/electrosphere/server/datacell/physics/DataCellPhysicsManager.java index c05c984c..752433f2 100644 --- a/src/main/java/electrosphere/server/datacell/physics/DataCellPhysicsManager.java +++ b/src/main/java/electrosphere/server/datacell/physics/DataCellPhysicsManager.java @@ -3,6 +3,7 @@ package electrosphere.server.datacell.physics; import electrosphere.engine.Globals; import electrosphere.renderer.shader.ShaderProgram; import electrosphere.server.datacell.Realm; +import electrosphere.server.terrain.manager.ServerTerrainChunk; import java.util.HashMap; import java.util.HashSet; @@ -65,7 +66,7 @@ public class DataCellPhysicsManager { * @param discreteY The initial discrete position Y coordinate */ public DataCellPhysicsManager(Realm realm, int discreteX, int discreteY, int discreteZ){ - worldBoundDiscreteMax = (int)(Globals.clientWorldData.getWorldBoundMin().x / Globals.clientWorldData.getDynamicInterpolationRatio() * 1.0f); + worldBoundDiscreteMax = (int)(Globals.clientWorldData.getWorldBoundMin().x / ServerTerrainChunk.CHUNK_DIMENSION * 1.0f); cells = new HashSet(); invalid = new HashSet(); updateable = new HashSet(); diff --git a/src/main/java/electrosphere/server/terrain/diskmap/ChunkDiskMap.java b/src/main/java/electrosphere/server/terrain/diskmap/ChunkDiskMap.java index 1c83190e..037b8469 100644 --- a/src/main/java/electrosphere/server/terrain/diskmap/ChunkDiskMap.java +++ b/src/main/java/electrosphere/server/terrain/diskmap/ChunkDiskMap.java @@ -7,6 +7,7 @@ import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.Semaphore; import java.util.zip.DeflaterOutputStream; import java.util.zip.InflaterOutputStream; @@ -14,6 +15,7 @@ import electrosphere.engine.Globals; import electrosphere.logger.LoggerInterface; import electrosphere.server.terrain.manager.ServerTerrainChunk; import electrosphere.util.FileUtils; +import electrosphere.util.annotation.Exclude; /** * An interface for accessing the disk map of chunk information @@ -23,6 +25,12 @@ public class ChunkDiskMap { //The map of world position+chunk type to the file that actually houses that information Map worldPosFileMap = new HashMap(); + /** + * Locks the chunk disk map for thread safety + */ + @Exclude + Semaphore lock = new Semaphore(1); + /** * Constructor */ @@ -81,7 +89,10 @@ public class ChunkDiskMap { * @return True if the map contains the chunk, false otherwise */ public boolean containsTerrainAtPosition(int worldX, int worldY, int worldZ){ - return worldPosFileMap.containsKey(getTerrainChunkKey(worldX, worldY, worldZ)); + lock.acquireUninterruptibly(); + boolean rVal = worldPosFileMap.containsKey(getTerrainChunkKey(worldX, worldY, worldZ)); + lock.release(); + return rVal; } /** @@ -92,7 +103,10 @@ public class ChunkDiskMap { * @return True if the map contains the chunk, false otherwise */ public boolean containsFluidAtPosition(int worldX, int worldY, int worldZ){ - return worldPosFileMap.containsKey(getFluidChunkKey(worldX, worldY, worldZ)); + lock.acquireUninterruptibly(); + boolean rVal = worldPosFileMap.containsKey(getFluidChunkKey(worldX, worldY, worldZ)); + lock.release(); + return rVal; } /** @@ -103,6 +117,7 @@ public class ChunkDiskMap { * @return The server terrain chunk if it exists, null otherwise */ public ServerTerrainChunk getTerrainChunk(int worldX, int worldY, int worldZ){ + lock.acquireUninterruptibly(); LoggerInterface.loggerEngine.INFO("Load chunk " + worldX + " " + worldY + " " + worldZ); ServerTerrainChunk rVal = null; if(containsTerrainAtPosition(worldX, worldY, worldZ)){ @@ -148,6 +163,7 @@ public class ChunkDiskMap { rVal = new ServerTerrainChunk(worldX, worldY, worldZ, weights, values); } } + lock.release(); return rVal; } @@ -156,6 +172,7 @@ public class ChunkDiskMap { * @param terrainChunk The terrain chunk */ public void saveToDisk(ServerTerrainChunk terrainChunk){ + lock.acquireUninterruptibly(); LoggerInterface.loggerEngine.DEBUG("Save to disk: " + terrainChunk.getWorldX() + " " + terrainChunk.getWorldY() + " " + terrainChunk.getWorldZ()); //get the file name for this chunk String fileName = null; @@ -202,6 +219,7 @@ public class ChunkDiskMap { // TODO Auto-generated catch block e.printStackTrace(); } + lock.release(); } } diff --git a/src/main/java/electrosphere/server/terrain/generation/TestGenerationChunkGenerator.java b/src/main/java/electrosphere/server/terrain/generation/TestGenerationChunkGenerator.java index 00773fff..669c6fb1 100644 --- a/src/main/java/electrosphere/server/terrain/generation/TestGenerationChunkGenerator.java +++ b/src/main/java/electrosphere/server/terrain/generation/TestGenerationChunkGenerator.java @@ -4,6 +4,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import electrosphere.engine.Globals; import electrosphere.game.data.biome.BiomeData; import electrosphere.game.data.biome.BiomeSurfaceGenerationParams; import electrosphere.game.server.world.ServerWorldData; @@ -65,6 +66,7 @@ public class TestGenerationChunkGenerator implements ChunkGenerator { @Override public ServerTerrainChunk generateChunk(int worldX, int worldY, int worldZ) { + Globals.profiler.beginAggregateCpuSample("TestGenerationChunkGenerator.generateChunk"); ServerTerrainChunk rVal = null; float[][][] weights; int[][][] values; @@ -95,19 +97,37 @@ public class TestGenerationChunkGenerator implements ChunkGenerator { values = new int[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION]; //biome of the current chunk - BiomeData biome = this.terrainModel.getSurfaceBiome(worldX, worldY, worldZ); + BiomeData surfaceBiome = this.terrainModel.getSurfaceBiome(worldX, worldY, worldZ); + + BiomeSurfaceGenerationParams surfaceParams = surfaceBiome.getSurfaceGenerationParams(); + HeightmapGenerator heightmapGen = this.tagGeneratorMap.get(surfaceParams.getSurfaceGenTag()); + if(heightmapGen == null){ + throw new Error("Undefined heightmap generator in biome! " + surfaceBiome.getId() + " " + surfaceBiome.getDisplayName() + " " + surfaceParams.getSurfaceGenTag()); + } + + //presolve heightfield + float[][] heightfield = new float[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION]; + for(int x = 0; x < ServerTerrainChunk.CHUNK_DIMENSION; x++){ + for(int z = 0; z < ServerTerrainChunk.CHUNK_DIMENSION; z++){ + heightfield[x][z] = heightmapGen.getHeight(this.terrainModel.getSeed(), this.serverWorldData.convertVoxelToRealSpace(x, worldX), this.serverWorldData.convertVoxelToRealSpace(z, worldZ)); + } + } for(int x = 0; x < ServerTerrainChunk.CHUNK_DIMENSION; x++){ + Globals.profiler.beginAggregateCpuSample("TestGenerationChunkGenerator - Generate slice"); for(int y = 0; y < ServerTerrainChunk.CHUNK_DIMENSION; y++){ for(int z = 0; z < ServerTerrainChunk.CHUNK_DIMENSION; z++){ - weights[x][y][z] = this.getChunkWeight(worldX, worldY, worldZ, x, y, z, this.terrainModel, biome); - values[x][y][z] = this.getChunkValue(worldX, worldY, worldZ, x, y, z, this.terrainModel, biome); + GeneratedVoxel voxel = this.getVoxel(worldX, worldY, worldZ, x, y, z, heightfield, this.terrainModel, surfaceBiome); + weights[x][y][z] = voxel.weight; + values[x][y][z] = voxel.type; } } + Globals.profiler.endCpuSample(); } } rVal = new ServerTerrainChunk(worldX, worldY, worldZ, weights, values); + Globals.profiler.endCpuSample(); return rVal; } @@ -116,7 +136,6 @@ public class TestGenerationChunkGenerator implements ChunkGenerator { this.terrainModel = model; } - /** * Gets the value for a chunk * @param worldX The world x pos @@ -125,71 +144,133 @@ public class TestGenerationChunkGenerator implements ChunkGenerator { * @param chunkX The chunk x pos * @param chunkY The chunk y pos * @param chunkZ The chunk z pos + * @param heightfield The precomputed heightfield * @param terrainModel The terrain model * @param surfaceBiome The surface biome of the chunk * @return The value of the chunk */ - private int getChunkValue( + private GeneratedVoxel getVoxel( int worldX, int worldY, int worldZ, int chunkX, int chunkY, int chunkZ, + float[][] heightfield, TerrainModel terrainModel, BiomeData surfaceBiome ){ + Globals.profiler.beginAggregateCpuSample("TestGenerationChunkGenerator.getChunkValue"); BiomeSurfaceGenerationParams surfaceParams = surfaceBiome.getSurfaceGenerationParams(); HeightmapGenerator heightmapGen = this.tagGeneratorMap.get(surfaceParams.getSurfaceGenTag()); if(heightmapGen == null){ throw new Error("Undefined heightmap generator in biome! " + surfaceBiome.getId() + " " + surfaceBiome.getDisplayName() + " " + surfaceParams.getSurfaceGenTag()); } - double realX = this.serverWorldData.convertVoxelToRealSpace(chunkX,worldX); + double realX = this.serverWorldData.convertVoxelToRealSpace(chunkY,worldX); double realY = this.serverWorldData.convertVoxelToRealSpace(chunkY,worldY); - double realZ = this.serverWorldData.convertVoxelToRealSpace(chunkZ,worldZ); + double realZ = this.serverWorldData.convertVoxelToRealSpace(chunkY,worldZ); - float surfaceHeight = heightmapGen.getHeight(terrainModel.getSeed(), realX, realZ); - if(realY <= surfaceHeight){ - return 1; + float surfaceHeight = heightfield[chunkX][chunkZ]; + double flooredSurfaceHeight = Math.floor(surfaceHeight); + Globals.profiler.endCpuSample(); + if(realY < surfaceHeight - 1){ + return getSubsurfaceVoxel( + worldX,worldY, worldZ, + chunkX, chunkY, chunkZ, + realX, realY, realZ, + surfaceHeight, flooredSurfaceHeight, + terrainModel, + surfaceBiome + ); + } else if(realY > flooredSurfaceHeight) { + return getOverSurfaceVoxel( + worldX,worldY, worldZ, + chunkX, chunkY, chunkZ, + realX, realY, realZ, + surfaceHeight, flooredSurfaceHeight, + terrainModel, + surfaceBiome + ); } else { - return 0; + return getSurfaceVoxel( + worldX,worldY, worldZ, + chunkX, chunkY, chunkZ, + realX, realY, realZ, + surfaceHeight, flooredSurfaceHeight, + terrainModel, + surfaceBiome + ); } } /** - * Gets the weight for a chunk - * @param worldX The world x pos - * @param worldY The world y pos - * @param worldZ The world z pos - * @param chunkX The chunk x pos - * @param chunkY The chunk y pos - * @param chunkZ The chunk z pos - * @param terrainModel The terrain model - * @param surfaceBiome The surface biome of the chunk - * @return The weight of the chunk + * Gets the voxel on the surface + * @return The voxel */ - private float getChunkWeight( + private GeneratedVoxel getSurfaceVoxel( int worldX, int worldY, int worldZ, int chunkX, int chunkY, int chunkZ, + double realX, double realY, double realZ, + float surfaceHeight, double flooredSurfaceHeight, TerrainModel terrainModel, BiomeData surfaceBiome ){ - BiomeSurfaceGenerationParams surfaceParams = surfaceBiome.getSurfaceGenerationParams(); - HeightmapGenerator heightmapGen = this.tagGeneratorMap.get(surfaceParams.getSurfaceGenTag()); - if(heightmapGen == null){ - throw new Error("Undefined heightmap generator in biome! " + surfaceBiome.getId() + " " + surfaceBiome.getDisplayName() + " " + surfaceParams.getSurfaceGenTag()); - } + GeneratedVoxel voxel = new GeneratedVoxel(); + voxel.weight = (float)(surfaceHeight - flooredSurfaceHeight) * 2 - 1; + voxel.type = 1; + return voxel; + } - double realX = this.serverWorldData.convertVoxelToRealSpace(chunkX,worldX); - double realY = this.serverWorldData.convertVoxelToRealSpace(chunkY,worldY); - double realZ = this.serverWorldData.convertVoxelToRealSpace(chunkZ,worldZ); - - float surfaceHeight = heightmapGen.getHeight(terrainModel.getSeed(), realX, realZ); - double flooredSurfaceHeight = Math.floor(surfaceHeight); - if(realY < flooredSurfaceHeight){ - return 1; - } else if(realY > flooredSurfaceHeight) { - return -1; + /** + * Gets the voxel below the surface + * @return The voxel + */ + private GeneratedVoxel getSubsurfaceVoxel( + int worldX, int worldY, int worldZ, + int chunkX, int chunkY, int chunkZ, + double realX, double realY, double realZ, + float surfaceHeight, double flooredSurfaceHeight, + TerrainModel terrainModel, + BiomeData surfaceBiome + ){ + GeneratedVoxel voxel = new GeneratedVoxel(); + if(realY < surfaceHeight - 5){ + voxel.weight = 1; + voxel.type = 6; } else { - return (float)(surfaceHeight - flooredSurfaceHeight) * 2 - 1; + voxel.weight = 1; + voxel.type = 1; } + return voxel; + } + + /** + * Gets the voxel above the service + * @return The voxel + */ + private GeneratedVoxel getOverSurfaceVoxel( + int worldX, int worldY, int worldZ, + int chunkX, int chunkY, int chunkZ, + double realX, double realY, double realZ, + float surfaceHeight, double flooredSurfaceHeight, + TerrainModel terrainModel, + BiomeData surfaceBiome + ){ + GeneratedVoxel voxel = new GeneratedVoxel(); + voxel.weight = -1; + voxel.type = 0; + return voxel; + } + + /** + * A voxel that was generated + */ + static class GeneratedVoxel { + /** + * The type of the voxel + */ + int type; + /** + * The weight of the voxel + */ + float weight; } } diff --git a/src/main/java/electrosphere/server/terrain/generation/heightmap/HillsGen.java b/src/main/java/electrosphere/server/terrain/generation/heightmap/HillsGen.java index 5522085f..f1cce43a 100644 --- a/src/main/java/electrosphere/server/terrain/generation/heightmap/HillsGen.java +++ b/src/main/java/electrosphere/server/terrain/generation/heightmap/HillsGen.java @@ -1,5 +1,6 @@ package electrosphere.server.terrain.generation.heightmap; +import electrosphere.engine.Globals; import electrosphere.util.noise.OpenSimplex2S; /** @@ -48,9 +49,12 @@ public class HillsGen implements HeightmapGenerator { * @return The height */ public float getHeight(long SEED, double x, double y){ + Globals.profiler.beginAggregateCpuSample("HillsGen.getHeight"); double scaledX = x * POSITION_SCALE; double scaledY = y * POSITION_SCALE; - return gradientHeight(SEED, scaledX, scaledY) * VERTICAL_SCALE + HEIGHT_OFFSET; + float rVal = gradientHeight(SEED, scaledX, scaledY) * VERTICAL_SCALE + HEIGHT_OFFSET; + Globals.profiler.endCpuSample(); + return rVal; } diff --git a/src/main/java/electrosphere/server/terrain/manager/ChunkGenerationThread.java b/src/main/java/electrosphere/server/terrain/manager/ChunkGenerationThread.java new file mode 100644 index 00000000..b66556d3 --- /dev/null +++ b/src/main/java/electrosphere/server/terrain/manager/ChunkGenerationThread.java @@ -0,0 +1,123 @@ +package electrosphere.server.terrain.manager; + +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import electrosphere.engine.Globals; +import electrosphere.server.terrain.diskmap.ChunkDiskMap; +import electrosphere.server.terrain.generation.interfaces.ChunkGenerator; + +/** + * A job that fetches a chunk, either by generating it or by reading it from disk + */ +public class ChunkGenerationThread implements Runnable { + + /** + * The number of milliseconds to wait per iteration + */ + static final int WAIT_TIME_MS = 2; + + /** + * The maximum number of iterations to wait before failing + */ + static final int MAX_TIME_TO_WAIT = 10; + + /** + * The chunk disk map + */ + ChunkDiskMap chunkDiskMap; + + /** + * The chunk cache on the server + */ + ServerChunkCache chunkCache; + + /** + * The chunk generator + */ + ChunkGenerator chunkGenerator; + + /** + * The world x coordinate + */ + int worldX; + + /** + * The world y coordinate + */ + int worldY; + + /** + * The world z coordinate + */ + int worldZ; + + /** + * The work to do once the chunk is available + */ + Consumer onLoad; + + /** + * Creates the chunk generation job + * @param chunkDiskMap The chunk disk map + * @param chunkCache The chunk cache on the server + * @param chunkGenerator The chunk generator + * @param worldX The world x coordinate + * @param worldY The world y coordinate + * @param worldZ The world z coordinate + * @param onLoad The work to do once the chunk is available + */ + public ChunkGenerationThread( + ChunkDiskMap chunkDiskMap, + ServerChunkCache chunkCache, + ChunkGenerator chunkGenerator, + int worldX, int worldY, int worldZ, + Consumer onLoad + ){ + this.chunkDiskMap = chunkDiskMap; + this.chunkCache = chunkCache; + this.chunkGenerator = chunkGenerator; + this.worldX = worldX; + this.worldY = worldY; + this.worldZ = worldZ; + this.onLoad = onLoad; + } + + @Override + public void run() { + ServerTerrainChunk chunk = null; + int i = 0; + while(chunk == null && i < MAX_TIME_TO_WAIT && Globals.threadManager.shouldKeepRunning()){ + if(chunkCache.containsChunk(worldX,worldY,worldZ)){ + chunk = chunkCache.get(worldX,worldY,worldZ); + } else { + //pull from disk if it exists + if(chunkDiskMap != null){ + if(chunkDiskMap.containsTerrainAtPosition(worldX, worldY, worldZ)){ + chunk = chunkDiskMap.getTerrainChunk(worldX, worldY, worldZ); + } + } + //generate if it does not exist + if(chunk == null){ + chunk = chunkGenerator.generateChunk(worldX, worldY, worldZ); + } + if(chunk != null){ + chunkCache.add(worldX, worldY, worldZ, chunk); + } + } + if(chunk == null){ + try { + TimeUnit.MILLISECONDS.sleep(WAIT_TIME_MS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + i++; + } + if(i >= MAX_TIME_TO_WAIT){ + throw new Error("Failed to resolve chunk!"); + } + this.onLoad.accept(chunk); + } + +} diff --git a/src/main/java/electrosphere/server/terrain/manager/ServerChunkCache.java b/src/main/java/electrosphere/server/terrain/manager/ServerChunkCache.java new file mode 100644 index 00000000..3d8fbc9b --- /dev/null +++ b/src/main/java/electrosphere/server/terrain/manager/ServerChunkCache.java @@ -0,0 +1,166 @@ +package electrosphere.server.terrain.manager; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Semaphore; + +/** + * Caches chunk data on the server + */ +public class ServerChunkCache { + + /** + * Number of chunks to cache + */ + static final int CACHE_SIZE = 5000; + + /** + * The size of the cache + */ + int cacheSize = CACHE_SIZE; + + /** + * The cached data + */ + Map chunkCache = new HashMap(); + + /** + * Tracks how recently a chunk has been queries for (used for evicting old chunks from cache) + */ + List queryRecencyQueue = new LinkedList(); + + /** + * Tracks what chunks are already queued to be asynchronously loaded. Used so we don't have two threads generating/fetching the same chunk + */ + Map queuedChunkMap = new HashMap(); + + /** + * The lock for thread safety + */ + Semaphore lock = new Semaphore(1); + + /** + * Gets the collection of server terrain chunks that are cached + * @return The collection of chunks + */ + public Collection getContents(){ + lock.acquireUninterruptibly(); + Collection rVal = Collections.unmodifiableCollection(chunkCache.values()); + lock.release(); + return rVal; + } + + /** + * Evicts all chunks in the cache + */ + public void clear(){ + lock.acquireUninterruptibly(); + chunkCache.clear(); + lock.release(); + } + + /** + * Gets the chunk at a given world position + * @param worldX The world x coordinate + * @param worldY The world y coordinate + * @param worldZ The world z coordinate + * @return The chunk + */ + public ServerTerrainChunk get(int worldX, int worldY, int worldZ){ + ServerTerrainChunk rVal = null; + String key = this.getKey(worldX, worldY, worldZ); + lock.acquireUninterruptibly(); + queryRecencyQueue.remove(key); + queryRecencyQueue.add(0, key); + rVal = this.chunkCache.get(key); + lock.release(); + return rVal; + } + + /** + * Adds a chunk to the cache + * @param worldX The world x coordinate of the chunk + * @param worldY The world y coordinate of the chunk + * @param worldZ The world z coordinate of the chunk + * @param chunk The chunk itself + */ + public void add(int worldX, int worldY, int worldZ, ServerTerrainChunk chunk){ + String key = this.getKey(worldX, worldY, worldZ); + lock.acquireUninterruptibly(); + queryRecencyQueue.add(0, key); + this.chunkCache.put(key, chunk); + lock.release(); + } + + /** + * Checks if the cache contains the chunk at a given world position + * @param worldX The world x coordinate + * @param worldY The world y coordinate + * @param worldZ The world z coordinate + * @return true if the cache contains this chunk, false otherwise + */ + public boolean containsChunk(int worldX, int worldY, int worldZ){ + String key = this.getKey(worldX,worldY,worldZ); + lock.acquireUninterruptibly(); + boolean rVal = this.chunkCache.containsKey(key); + lock.release(); + return rVal; + } + + /** + * Gets the key for a given world position + * @param worldX The x component + * @param worldY The y component + * @param worldZ The z component + * @return The key + */ + public String getKey(int worldX, int worldY, int worldZ){ + return worldX + "_" + worldY + "_" + worldZ; + } + + /** + * Checks if the chunk is already queued or not + * @param worldX The world x position of the chunk + * @param worldY The world y position of the chunk + * @param worldZ The world z position of the chunk + * @return true if the chunk is already queued, false otherwise + */ + public boolean chunkIsQueued(int worldX, int worldY, int worldZ){ + String key = this.getKey(worldX,worldY,worldZ); + lock.acquireUninterruptibly(); + boolean rVal = this.queuedChunkMap.containsKey(key); + lock.release(); + return rVal; + } + + /** + * Flags a chunk as queued + * @param worldX The world x position of the chunk + * @param worldY The world y position of the chunk + * @param worldZ The world z position of the chunk + */ + public void queueChunk(int worldX, int worldY, int worldZ){ + String key = this.getKey(worldX,worldY,worldZ); + lock.acquireUninterruptibly(); + this.queuedChunkMap.put(key,true); + lock.release(); + } + + /** + * Unflags a chunk as queued + * @param worldX The world x position of the chunk + * @param worldY The world y position of the chunk + * @param worldZ The world z position of the chunk + */ + public void unqueueChunk(int worldX, int worldY, int worldZ){ + String key = this.getKey(worldX,worldY,worldZ); + lock.acquireUninterruptibly(); + this.queuedChunkMap.remove(key); + lock.release(); + } + +} diff --git a/src/main/java/electrosphere/server/terrain/manager/ServerTerrainManager.java b/src/main/java/electrosphere/server/terrain/manager/ServerTerrainManager.java index 66bb1277..2c9adec1 100644 --- a/src/main/java/electrosphere/server/terrain/manager/ServerTerrainManager.java +++ b/src/main/java/electrosphere/server/terrain/manager/ServerTerrainManager.java @@ -1,5 +1,6 @@ package electrosphere.server.terrain.manager; +import electrosphere.engine.Globals; import electrosphere.game.server.world.ServerWorldData; import electrosphere.server.terrain.diskmap.ChunkDiskMap; import electrosphere.server.terrain.generation.TestGenerationChunkGenerator; @@ -8,12 +9,13 @@ import electrosphere.server.terrain.generation.interfaces.ChunkGenerator; import electrosphere.server.terrain.models.TerrainModel; import electrosphere.server.terrain.models.TerrainModification; import electrosphere.util.FileUtils; +import electrosphere.util.annotation.Exclude; + import java.nio.ByteBuffer; import java.nio.FloatBuffer; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Consumer; import org.joml.Vector3i; @@ -22,6 +24,11 @@ import org.joml.Vector3i; */ public class ServerTerrainManager { + /** + * The number of threads for chunk generation + */ + public static final int GENERATION_THREAD_POOL_SIZE = 1; + /** * Full world discrete size */ @@ -51,21 +58,24 @@ public class ServerTerrainManager { //The model of the terrain this manager is managing TerrainModel model; - - //In memory cache of chunk data - //Basic idea is we associate string that contains chunk x&y&z with elevation - //While we incur a penalty with converting ints -> string, think this will - //offset regenerating the array every time we want a new one - int cacheSize = 500; - Map chunkCache; - List chunkCacheContents; + /** + * The cache of chunks + */ + @Exclude + ServerChunkCache chunkCache = new ServerChunkCache(); //The map of chunk position <-> file on disk containing chunk data ChunkDiskMap chunkDiskMap = null; //The generation algorithm for this terrain manager + @Exclude ChunkGenerator chunkGenerator; - + + /** + * The threadpool for chunk generation + */ + @Exclude + ExecutorService chunkExecutorService = Executors.newFixedThreadPool(GENERATION_THREAD_POOL_SIZE); /** * Constructor @@ -76,8 +86,6 @@ public class ServerTerrainManager { ChunkGenerator chunkGenerator ){ this.parent = parent; - this.chunkCache = new ConcurrentHashMap(); - this.chunkCacheContents = new CopyOnWriteArrayList(); this.seed = seed; this.chunkGenerator = chunkGenerator; } @@ -118,8 +126,7 @@ public class ServerTerrainManager { FileUtils.saveBinaryToSavePath(saveName, "./terrain.dat", buffer.array()); } //for each chunk, save via disk map - for(String chunkKey : chunkCacheContents){ - ServerTerrainChunk chunk = chunkCache.get(chunkKey); + for(ServerTerrainChunk chunk : this.chunkCache.getContents()){ chunkDiskMap.saveToDisk(chunk); } //save disk map itself @@ -167,7 +174,6 @@ public class ServerTerrainManager { */ public void evictAll(){ this.chunkCache.clear(); - this.chunkCacheContents.clear(); } public float[][] getTerrainAtChunk(int x, int y){ @@ -215,37 +221,19 @@ public class ServerTerrainManager { } /** - * Gets the key for a given world position - * @param worldX The x component - * @param worldY The y component - * @param worldZ The z component - * @return The key - */ - public String getKey(int worldX, int worldY, int worldZ){ - return worldX + "_" + worldY + "_" + worldZ; - } - - /** - * Gets a server terrain chunk + * Performs logic once a server chunk is available * @param worldX The world x position * @param worldY The world y position * @param worldZ The world z position * @return The ServerTerrainChunk */ public ServerTerrainChunk getChunk(int worldX, int worldY, int worldZ){ + Globals.profiler.beginCpuSample("ServerTerrainManager.getChunk"); //THIS FIRES IF THERE IS A MAIN GAME WORLD RUNNING - String key = getKey(worldX,worldY,worldZ); ServerTerrainChunk returnedChunk = null; - if(chunkCache.containsKey(key)){ - chunkCacheContents.remove(key); - chunkCacheContents.add(0, key); - returnedChunk = chunkCache.get(key); - return returnedChunk; + if(chunkCache.containsChunk(worldX,worldY,worldZ)){ + returnedChunk = chunkCache.get(worldX,worldY,worldZ); } else { - if(chunkCacheContents.size() >= cacheSize){ - String oldChunk = chunkCacheContents.remove(chunkCacheContents.size() - 1); - chunkCache.remove(oldChunk); - } //pull from disk if it exists if(chunkDiskMap != null){ if(chunkDiskMap.containsTerrainAtPosition(worldX, worldY, worldZ)){ @@ -256,10 +244,23 @@ public class ServerTerrainManager { if(returnedChunk == null){ returnedChunk = chunkGenerator.generateChunk(worldX, worldY, worldZ); } - chunkCache.put(key, returnedChunk); - chunkCacheContents.add(key); - return returnedChunk; + this.chunkCache.add(worldX, worldY, worldZ, returnedChunk); } + Globals.profiler.endCpuSample(); + return returnedChunk; + } + + /** + * Performs logic once a server chunk is available + * @param worldX The world x position + * @param worldY The world y position + * @param worldZ The world z position + * @param onLoad The logic to run once the chunk is available + */ + public void getChunkAsync(int worldX, int worldY, int worldZ, Consumer onLoad){ + Globals.profiler.beginCpuSample("ServerTerrainManager.getChunkAsync"); + this.chunkExecutorService.submit(new ChunkGenerationThread(chunkDiskMap, chunkCache, chunkGenerator, worldX, worldY, worldZ, onLoad)); + Globals.profiler.endCpuSample(); } /** @@ -286,9 +287,8 @@ public class ServerTerrainManager { if(model != null){ model.addModification(modification); } - String key = getKey(worldPos.x,worldPos.y,worldPos.z); - if(chunkCache.containsKey(key)){ - ServerTerrainChunk chunk = chunkCache.get(key); + if(chunkCache.containsChunk(worldPos.x,worldPos.y,worldPos.z)){ + ServerTerrainChunk chunk = chunkCache.get(worldPos.x,worldPos.y,worldPos.z); chunk.addModification(modification); } } @@ -309,4 +309,11 @@ public class ServerTerrainManager { return chunkGenerator; } + /** + * Closes the generation threadpool + */ + public void closeThreads(){ + this.chunkExecutorService.shutdownNow(); + } + } diff --git a/src/main/java/electrosphere/util/ds/octree/WorldOctTree.java b/src/main/java/electrosphere/util/ds/octree/WorldOctTree.java new file mode 100644 index 00000000..d0a25519 --- /dev/null +++ b/src/main/java/electrosphere/util/ds/octree/WorldOctTree.java @@ -0,0 +1,435 @@ +package electrosphere.util.ds.octree; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import org.joml.Vector3i; + +import io.github.studiorailgun.MathUtils; + +/** + * A power of two oct tree that supports arbitrary world size (not fixed) + */ +public class WorldOctTree { + /** + * The maximum level of the chunk tree + */ + int maxLevel; + + /** + * The root node + */ + private FloatingChunkTreeNode root = null; + + /** + * The list of all nodes in the tree + */ + List> nodes = null; + + /** + * The minimum position + */ + Vector3i min; + + /** + * The maximum position + */ + Vector3i max; + + /** + * Constructor + * @param min The minimum position of the world + * @param max The maximum position of the world + */ + public WorldOctTree(Vector3i min, Vector3i max){ + //check that dimensions are a multiple of 2 + if( + ((max.x - min.x) & (max.x - min.x - 1)) != 0 || + ((max.y - min.y) & (max.y - min.y - 1)) != 0 || + ((max.z - min.z) & (max.z - min.z - 1)) != 0 + ){ + throw new Error("Invalid dimensions! Must be a power of two! " + min + " " + max); + } + if(max.x - min.x != max.y - min.y || max.x - min.x != max.z - min.z){ + throw new Error("Invalid dimensions! Must be the same size along all three axis! " + min + " " + max); + } + this.min = new Vector3i(min); + this.max = new Vector3i(max); + //calculate max level + int dimRaw = max.x - min.x; + this.maxLevel = (int)MathUtils.log2(dimRaw); + this.nodes = new ArrayList>(); + this.root = new FloatingChunkTreeNode(this, 0, new Vector3i(min), new Vector3i(max)); + this.root.isLeaf = true; + this.nodes.add(this.root); + } + + /** + * Splits a parent into child nodes + * @param parent The parent + * @return The new non-leaf node + */ + public FloatingChunkTreeNode split(FloatingChunkTreeNode existing){ + if(!existing.isLeaf()){ + throw new IllegalArgumentException("Tried to split non-leaf!"); + } + Vector3i min = existing.getMinBound(); + Vector3i max = existing.getMaxBound(); + int midX = (max.x - min.x) / 2 + min.x; + int midY = (max.y - min.y) / 2 + min.y; + int midZ = (max.z - min.z) / 2 + min.z; + int currentLevel = existing.getLevel(); + FloatingChunkTreeNode newContainer = new FloatingChunkTreeNode<>(this, currentLevel, min, max); + //add children + newContainer.addChild(new FloatingChunkTreeNode(this, currentLevel + 1, new Vector3i(min.x,min.y,min.z), new Vector3i(midX,midY,midZ))); + newContainer.addChild(new FloatingChunkTreeNode(this, currentLevel + 1, new Vector3i(midX,min.y,min.z), new Vector3i(max.x,midY,midZ))); + newContainer.addChild(new FloatingChunkTreeNode(this, currentLevel + 1, new Vector3i(min.x,midY,min.z), new Vector3i(midX,max.y,midZ))); + newContainer.addChild(new FloatingChunkTreeNode(this, currentLevel + 1, new Vector3i(midX,midY,min.z), new Vector3i(max.x,max.y,midZ))); + // + newContainer.addChild(new FloatingChunkTreeNode(this, currentLevel + 1, new Vector3i(min.x,min.y,midZ), new Vector3i(midX,midY,max.z))); + newContainer.addChild(new FloatingChunkTreeNode(this, currentLevel + 1, new Vector3i(midX,min.y,midZ), new Vector3i(max.x,midY,max.z))); + newContainer.addChild(new FloatingChunkTreeNode(this, currentLevel + 1, new Vector3i(min.x,midY,midZ), new Vector3i(midX,max.y,max.z))); + newContainer.addChild(new FloatingChunkTreeNode(this, currentLevel + 1, new Vector3i(midX,midY,midZ), new Vector3i(max.x,max.y,max.z))); + + boolean foundMin = false; + for(FloatingChunkTreeNode child : newContainer.getChildren()){ + if(child.getMinBound().distance(newContainer.getMinBound()) == 0){ + foundMin = true; + } + } + if(!foundMin){ + String message = "Failed to sanity check!\n"; + message = message + min + " " + max + "\n"; + message = message + midX + " " + midY + " " + midZ + "\n"; + message = message + "container mid: " + newContainer.getMinBound(); + for(FloatingChunkTreeNode child : newContainer.getChildren()){ + message = message + "child min: " + child.getMinBound() + "\n"; + } + throw new Error(message); + } + + //replace existing node + replaceNode(existing,newContainer); + + //update tracking + this.nodes.remove(existing); + this.nodes.add(newContainer); + this.nodes.addAll(newContainer.getChildren()); + + return newContainer; + } + + /** + * Joins a non-leaf node's children into a single node + * @param parent The non-leaf + * @return The new leaf node + */ + public FloatingChunkTreeNode join(FloatingChunkTreeNode existing){ + if(existing.isLeaf()){ + throw new IllegalArgumentException("Tried to split non-leaf!"); + } + Vector3i min = existing.getMinBound(); + Vector3i max = existing.getMaxBound(); + int currentLevel = existing.getLevel(); + FloatingChunkTreeNode newContainer = new FloatingChunkTreeNode<>(this, currentLevel, min, max); + + //replace existing node + replaceNode(existing,newContainer); + + //update tracking + this.nodes.remove(existing); + this.nodes.removeAll(existing.getChildren()); + this.nodes.add(newContainer); + + return newContainer; + } + + /** + * Replaces an existing node with a new node + * @param existing the existing node + * @param newNode the new node + */ + private void replaceNode(FloatingChunkTreeNode existing, FloatingChunkTreeNode newNode){ + if(existing == this.root){ + this.root = newNode; + } else { + FloatingChunkTreeNode parent = existing.getParent(); + parent.removeChild(existing); + parent.addChild(newNode); + } + } + + /** + * Gets the root node of the tree + */ + public FloatingChunkTreeNode getRoot() { + return this.root; + } + + /** + * Sets the max level of the tree + * @param maxLevel The max level + */ + public void setMaxLevel(int maxLevel){ + this.maxLevel = maxLevel; + } + + /** + * Gets the max level allowed for the tree + * @return The max level + */ + public int getMaxLevel(){ + return maxLevel; + } + + /** + * Clears the tree + */ + public void clear(){ + this.nodes.clear(); + this.root = new FloatingChunkTreeNode(this, 0, new Vector3i(min), new Vector3i(max)); + this.root.isLeaf = true; + this.nodes.add(this.root); + } + + /** + * Searches for the node at a given position + * @param position The position + * @param returnNonLeaf If true, the function can return non-leaf nodes, otherwise will only return leaf nodes + * @return The leaf if it exists, null otherwise + */ + public FloatingChunkTreeNode search(Vector3i position, boolean returnNonLeaf){ + return this.search(position,returnNonLeaf,this.maxLevel); + } + + /** + * Searches for the node at a given position + * @param position The position + * @param returnNonLeaf If true, the function can return non-leaf nodes, otherwise will only return leaf nodes + * @param maxLevel The maximum level to search for + * @return The leaf if it exists, null otherwise + */ + public FloatingChunkTreeNode search(Vector3i position, boolean returnNonLeaf, int maxLevel){ + //out of bounds check + if( + position.x < min.x || position.x > max.x || + position.y < min.y || position.y > max.y || + position.z < min.z || position.z > max.z + ){ + throw new Error("Trying to search for node outside tree range!"); + } + FloatingChunkTreeNode searchResult = recursiveSearchUnsafe(root,position,maxLevel); + if(!returnNonLeaf && !searchResult.isLeaf()){ + return null; + } + return searchResult; + } + + /** + * Recursively searches for the node at the position. Unsafe because it does not bounds check. + * @param currentNode The current node searching from + * @param position The position to search at + * @param maxLevel The maximum level to search for + * @return The found node + */ + private FloatingChunkTreeNode recursiveSearchUnsafe(FloatingChunkTreeNode currentNode, Vector3i position, int maxLevel){ + if(maxLevel < 0){ + throw new Error("Provided invalid max level! Must be created than 0! " + maxLevel); + } + if(currentNode.level > maxLevel){ + throw new Error("Failed to stop before max level!"); + } + if(currentNode.level == maxLevel){ + return currentNode; + } + if(currentNode.getChildren().size() > 0){ + for(FloatingChunkTreeNode child : currentNode.getChildren()){ + if( + position.x < child.getMaxBound().x && position.x >= child.getMinBound().x && + position.y < child.getMaxBound().y && position.y >= child.getMinBound().y && + position.z < child.getMaxBound().z && position.z >= child.getMinBound().z + ){ + return recursiveSearchUnsafe(child, position, maxLevel); + } + } + String message = "Current node is within range, but no children are! This does not make any sense.\n"; + + message = message + " current pos: " + currentNode.getMinBound() + " " + currentNode.getMaxBound() + "\n"; + for(FloatingChunkTreeNode child : currentNode.getChildren()){ + message = message + " child " + child + " pos: " + child.getMinBound() + " " + child.getMaxBound() + "\n"; + } + message = message + "position to search: " + position + "\n"; + throw new Error(message); + } else { + return currentNode; + } + } + + + /** + * A node in a chunk tree + */ + public static class FloatingChunkTreeNode { + + //True if this is a leaf node, false otherwise + private boolean isLeaf; + + //the parent node + private FloatingChunkTreeNode parent; + + /** + * The tree containing this node + */ + private WorldOctTree containingTree; + + //the children of this node + private List> children = new LinkedList>(); + + //The data at the node + private T data; + + /** + * The min bound + */ + private Vector3i min; + + /** + * The max bound. + * !!NOTE!! max is exclusive, not inclusive + */ + private Vector3i max; + + /** + * The level of the chunk tree node + */ + int level; + + /** + * Constructor for non-leaf node + * @param tree The parent tree + * @param level The level of the node + * @param min The minimum position of the node + * @param max The maximum position of then ode + */ + private FloatingChunkTreeNode(WorldOctTree tree, int level, Vector3i min, Vector3i max){ + if(tree == null){ + throw new Error("Invalid tree provided " + tree); + } + int maxPos = (int)Math.pow(2,tree.getMaxLevel()); + if(min.x == maxPos || min.y == maxPos || min.z == maxPos){ + throw new IllegalArgumentException("Invalid minimum! " + min); + } + if(level < 0 || level > tree.getMaxLevel()){ + throw new IllegalArgumentException("Invalid level! " + level); + } + this.containingTree = tree; + this.isLeaf = false; + this.level = level; + this.min = min; + this.max = max; + } + + /** + * Constructor for use in tests + * @param tree The Tree + * @param level The level + * @param min The min point + * @param max The max point + * @return The node + */ + public static FloatingChunkTreeNode constructorForTests(WorldOctTree tree, int level, Vector3i min, Vector3i max){ + return new FloatingChunkTreeNode(tree, level, min, max); + } + + /** + * Converts this node to a leaf + * @param data The data to put in the leaf + */ + public void convertToLeaf(T data){ + this.isLeaf = true; + this.data = data; + } + + /** + * Gets the data associated with this node + */ + public T getData() { + return data; + } + + /** + * Gets the parent of this node + */ + public FloatingChunkTreeNode getParent() { + return parent; + } + + /** + * Gets the children of this node + */ + public List> getChildren() { + return Collections.unmodifiableList(this.children); + } + + /** + * Checks if this node is a leaf + * @return true if it is a leaf, false otherwise + */ + public boolean isLeaf() { + return isLeaf; + } + + /** + * Checks if the node can split + * @return true if can split, false otherwise + */ + public boolean canSplit(){ + return isLeaf && level < containingTree.getMaxLevel(); + } + + /** + * Gets the level of the node + * @return The level of the node + */ + public int getLevel(){ + return level; + } + + /** + * Gets the min bound of this node + * @return The min bound + */ + public Vector3i getMinBound(){ + return new Vector3i(min); + } + + /** + * Gets the max bound of this node + * @return The max bound + */ + public Vector3i getMaxBound(){ + return new Vector3i(max); + } + + /** + * Adds a child to this node + * @param child The child + */ + private void addChild(FloatingChunkTreeNode child){ + this.children.add(child); + child.parent = this; + } + + /** + * Removes a child node + * @param child the child + */ + private void removeChild(FloatingChunkTreeNode child){ + this.children.remove(child); + child.parent = null; + } + + } +} diff --git a/src/test/java/electrosphere/client/terrain/cells/ClientDrawCellManagerTests.java b/src/test/java/electrosphere/client/terrain/cells/ClientDrawCellManagerTests.java new file mode 100644 index 00000000..e98db024 --- /dev/null +++ b/src/test/java/electrosphere/client/terrain/cells/ClientDrawCellManagerTests.java @@ -0,0 +1,51 @@ +package electrosphere.client.terrain.cells; + +import static org.junit.jupiter.api.Assertions.*; + +import org.joml.Vector3d; +import org.joml.Vector3f; +import org.joml.Vector3i; +import org.junit.jupiter.api.extension.ExtendWith; + +import electrosphere.client.scene.ClientWorldData; +import electrosphere.engine.Globals; +import electrosphere.engine.Main; +import electrosphere.server.terrain.manager.ServerTerrainChunk; +import electrosphere.test.annotations.UnitTest; +import electrosphere.test.template.extensions.StateCleanupCheckerExtension; +import electrosphere.util.ds.octree.WorldOctTree.FloatingChunkTreeNode; + +/** + * Tests for the client draw cell manager + */ +@ExtendWith(StateCleanupCheckerExtension.class) +public class ClientDrawCellManagerTests { + + + /** + * Test creating a manager + */ + @UnitTest + public void testCreation(){ + assertDoesNotThrow(() -> { + new ClientDrawCellManager(null, 64); + }); + } + + @UnitTest + public void testJoinCase(){ + + int worldDiscreteSize = 64; + Globals.clientWorldData = new ClientWorldData(new Vector3f(0), new Vector3f(worldDiscreteSize * ServerTerrainChunk.CHUNK_DIMENSION), 0, worldDiscreteSize); + ClientDrawCellManager manager = new ClientDrawCellManager(null, 64); + Vector3d playerPos = new Vector3d(0,0,0); + FloatingChunkTreeNode node = FloatingChunkTreeNode.constructorForTests(manager.chunkTree, 1, new Vector3i(16,0,0), new Vector3i(32,16,16)); + node.convertToLeaf(DrawCell.generateTerrainCell(new Vector3i(0,0,0))); + + assertFalse(manager.shouldSplit(playerPos, node)); + + //cleanup + Main.shutdown(); + } + +} diff --git a/src/test/java/electrosphere/test/template/extensions/StateCleanupCheckerExtension.java b/src/test/java/electrosphere/test/template/extensions/StateCleanupCheckerExtension.java index 44777fde..a3e86652 100644 --- a/src/test/java/electrosphere/test/template/extensions/StateCleanupCheckerExtension.java +++ b/src/test/java/electrosphere/test/template/extensions/StateCleanupCheckerExtension.java @@ -30,6 +30,7 @@ public class StateCleanupCheckerExtension implements AfterEachCallback { Globals.playerManager, LoggerInterface.loggerEngine, RenderingEngine.screenFramebuffer, + Globals.clientWorldData, }; for(Object object : objectsToCheck){ if(object != null){ diff --git a/src/test/java/electrosphere/util/ds/octree/ChunkTreeTests.java b/src/test/java/electrosphere/util/ds/octree/ChunkTreeTests.java index d592588a..dbc2369e 100644 --- a/src/test/java/electrosphere/util/ds/octree/ChunkTreeTests.java +++ b/src/test/java/electrosphere/util/ds/octree/ChunkTreeTests.java @@ -2,9 +2,20 @@ package electrosphere.util.ds.octree; import static org.junit.jupiter.api.Assertions.*; +import electrosphere.test.annotations.UnitTest; + /** * Unit testing for the chunk octree implementation */ public class ChunkTreeTests { + /** + * Creates a chunk tree + */ + @UnitTest + public void testCreateChunkTree(){ + ChunkTree tree = new ChunkTree(); + assertNotNull(tree); + } + } diff --git a/src/test/java/electrosphere/util/ds/octree/WorldOctTreeTests.java b/src/test/java/electrosphere/util/ds/octree/WorldOctTreeTests.java new file mode 100644 index 00000000..a1008e3b --- /dev/null +++ b/src/test/java/electrosphere/util/ds/octree/WorldOctTreeTests.java @@ -0,0 +1,54 @@ +package electrosphere.util.ds.octree; + +import static org.junit.jupiter.api.Assertions.*; + +import org.joml.Vector3i; + +import electrosphere.test.annotations.UnitTest; + +/** + * Tests the floating position chunk tree implementation + */ +public class WorldOctTreeTests { + + /** + * Creates a chunk tree + */ + @UnitTest + public void testCreateFloatingChunkTree(){ + WorldOctTree tree = new WorldOctTree(new Vector3i(0,0,0), new Vector3i(64,64,64)); + assertNotNull(tree); + } + + /** + * Test changing the centered point of the floating tree + */ + @UnitTest + public void testMaxLevelSetting(){ + WorldOctTree tree = new WorldOctTree(new Vector3i(0,0,0), new Vector3i(64,64,64)); + assertEquals(6,tree.getMaxLevel()); + } + + /** + * Assert non-power-of-two dims fail + */ + @UnitTest + public void testFailOnNonPowTwoDim(){ + assertThrows(Error.class, () -> { + new WorldOctTree(new Vector3i(0,0,0), new Vector3i(63,63,63)); + }); + } + + /** + * Assert unequal dims fail + */ + @UnitTest + public void testFailOnUnequalDim(){ + assertThrows(Error.class, () -> { + new WorldOctTree(new Vector3i(0,0,0), new Vector3i(64,1,64)); + }); + } + + + +} diff --git a/template.json b/template.json index 00a3414c..c1463157 100644 --- a/template.json +++ b/template.json @@ -10,7 +10,6 @@ "./net/lore.json", "./net/player.json", "./net/terrain.json", - "./net/world.json", "./net/server.json", "./net/character.json", "./net/inventory.json",