rewrite client side chunks + transvoxel integration
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good

This commit is contained in:
austin 2024-11-04 13:51:04 -05:00
parent d6c476b4a3
commit d2ccf3c479
73 changed files with 4366 additions and 747 deletions

2
.gitignore vendored
View File

@ -11,7 +11,7 @@
/nb-configuration.xml
/Telephone-*.jar
/NetArranger-*.jar
/NetArranger*.jar
/lwjglx-debug-*.jar
/hs_err_pid*
/replay_pid*

View File

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

View File

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

View File

@ -4,4 +4,5 @@
- @subpage biomeselection
- @subpage biomegenerationproblems
- @subpage terraingenerationprocess
- @subpage voxelgenideas
- @subpage voxelgenideas
- @subpage chunkgenoptimizations

View File

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

43
pom.xml
View File

@ -305,7 +305,15 @@
<dependency>
<groupId>io.github.studiorailgun</groupId>
<artifactId>MathUtils</artifactId>
<version>1.0.1</version>
<version>1.1.0</version>
</dependency>
<!--DataStructures-->
<!--License: MIT-->
<dependency>
<groupId>io.github.studiorailgun</groupId>
<artifactId>DataStructures</artifactId>
<version>1.1.0</version>
</dependency>
@ -440,6 +448,24 @@
</arguments>
</configuration>
</execution>
<execution>
<id>Run Net Arranger</id>
<phase>generate-sources</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>java.exe</executable>
<arguments>
<argument>-jar</argument>
<argument>NetArranger.jar</argument>
</arguments>
<successCodes>
<successCode>0</successCode>
<successCode>1</successCode>
</successCodes>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
@ -452,6 +478,20 @@
<!-- To execute this profile, run a command like "mvn test -P integration" -->
<profile>
<id>integration</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<!--If you want the logs to throw exceptions on opengl errors, remove the "n" after .jar=t-->
<argLine>-javaagent:./lwjglx-debug-1.0.0.jar=t;o=trace.log</argLine>
<forkCount>0</forkCount>
</configuration>
</plugin>
</plugins>
</build>
<properties>
<!--The tests to run-->
<groups>fast,unit,integration</groups>
@ -471,6 +511,7 @@
<configuration>
<!--If you want the logs to throw exceptions on opengl errors, remove the "n" after .jar=t-->
<argLine>-javaagent:./lwjglx-debug-1.0.0.jar=t;o=trace.log</argLine>
<forkCount>0</forkCount>
</configuration>
</plugin>
</plugins>

View File

@ -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<FluidCell>();
hasNotRequested = new HashSet<String>();
@ -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();
}
/**

View File

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

View File

@ -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<String> foliageTypesSupported = Globals.gameConfigCurrent.getVoxelData().getTypeFromId(data.getType(voxelPosition)).getAmbientFoliage();

View File

@ -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<FoliageCell> 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<FoliageCell> 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<String> 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<FoliageCell> node, Vector3d playerPos){
boolean updated = false;
if(this.shouldSplit(playerPos, node)){
Globals.profiler.beginCpuSample("FoliageChunk.split");
//perform op
ChunkTreeNode<FoliageCell> 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<FoliageCell> 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<ChunkTreeNode<FoliageCell>> children = new LinkedList<ChunkTreeNode<FoliageCell>>(node.getChildren());

View File

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

View File

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

View File

@ -26,6 +26,31 @@ public class ChunkData {
//Used in DrawCell to keep track of which positions to invalidate
Set<String> modifiedSinceLastGeneration = new HashSet<String>();
/**
* 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);
}
}

View File

@ -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<String,ChunkData> cacheMap = new ConcurrentHashMap<String,ChunkData>();
Map<Long,ChunkData> cacheMap = new ConcurrentHashMap<Long,ChunkData>();
//the list of keys in the cache
List<String> cacheList = new CopyOnWriteArrayList<String>();
List<Long> cacheList = new CopyOnWriteArrayList<Long>();
//A map of chunk to its world position
Map<ChunkData,Vector3i> chunkPositionMap = new ConcurrentHashMap<ChunkData,Vector3i>();
/**
* The map tracking chunks that have been requested
*/
Map<Long,Boolean> requestedChunks = new ConcurrentHashMap<Long,Boolean>();
/**
* 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);
}
}

View File

@ -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<DrawCell> 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<DrawCell>(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<DrawCell> 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<DrawCell> 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<DrawCell> 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<DrawCell> 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<DrawCellFace> 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<FloatingChunkTreeNode<DrawCell>> children = new LinkedList<FloatingChunkTreeNode<DrawCell>>(node.getChildren());
for(FloatingChunkTreeNode<DrawCell> 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<DrawCell> 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<DrawCell> 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<DrawCell> 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<DrawCellFace> solveHighResFace(FloatingChunkTreeNode<DrawCell> 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<DrawCellFace> faces = new LinkedList<DrawCellFace>();
if(node.getMinBound().x - 1 >= 0){
FloatingChunkTreeNode<DrawCell> 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<DrawCell> 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<DrawCell> 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<DrawCell> 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<DrawCell> 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<DrawCell> 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<DrawCell> 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<DrawCell> 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<DrawCell> 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<DrawCell> 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<DrawCell> 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<DrawCell> 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<DrawCell> 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<DrawCell> 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<DrawCell> 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<DrawCell> 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<DrawCell> 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<DrawCell> 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<DrawCell> 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<DrawCell> node, List<DrawCellFace> 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<DrawCell> 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<FloatingChunkTreeNode<DrawCell>> children = new LinkedList<FloatingChunkTreeNode<DrawCell>>(node.getChildren());
for(FloatingChunkTreeNode<DrawCell> 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;
}
}

View File

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

View File

@ -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<DrawCellFace> 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<DrawCellFace> 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);
}
/**

View File

@ -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<TerrainMessage> messageQueue = new CopyOnWriteArrayList<TerrainMessage>();
/**
* 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<TerrainMessage> bouncedMessages = new LinkedList<TerrainMessage>();
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();
}
}

View File

@ -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
*/

View File

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

View File

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

View File

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

View File

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

View File

@ -62,7 +62,7 @@ public class CollisionWorldData {
public int getDynamicInterpolationRatio(){
if(clientWorldData != null){
return clientWorldData.getDynamicInterpolationRatio();
return clientWorldData.getWorldDiscreteSize();
} else {
return serverWorldData.getDynamicInterpolationRatio();
}

View File

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

View File

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

View File

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

View File

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

View File

@ -29,6 +29,14 @@ public class Signal {
YOGA_DESTROY,
UI_MODIFICATION,
//
//Terrain
//
REQUEST_CHUNK,
CHUNK_CREATED,
REQUEST_CHUNK_EDIT,
CHUNK_EDITED,
}
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -34,7 +34,6 @@ public class TerrainProtocol implements ClientProtocolTemplate<TerrainMessage> {
//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<TerrainMessage> {
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<TerrainMessage> {
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: {

View File

@ -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<Byte> temporaryByteQueue = new LinkedList<Byte>();
@ -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();

View File

@ -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<Byte> temporaryByteQueue = new LinkedList<Byte>();
@ -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<Byte> temporaryByteQueue = new LinkedList<Byte>();
@ -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<Byte> temporaryByteQueue = new LinkedList<Byte>();
@ -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);

View File

@ -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<Byte> temporaryByteQueue = new LinkedList<Byte>();
@ -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);

View File

@ -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<Byte> temporaryByteQueue = new LinkedList<Byte>();
@ -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<Byte> temporaryByteQueue = new LinkedList<Byte>();
@ -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);

View File

@ -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<Byte> temporaryByteQueue = new LinkedList<Byte>();
@ -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<Byte> temporaryByteQueue = new LinkedList<Byte>();
@ -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<Byte> temporaryByteQueue = new LinkedList<Byte>();
@ -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<Byte> temporaryByteQueue = new LinkedList<Byte>();
@ -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<Byte> temporaryByteQueue = new LinkedList<Byte>();
@ -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<Byte> temporaryByteQueue = new LinkedList<Byte>();
@ -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<Byte> temporaryByteQueue = new LinkedList<Byte>();
@ -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);

View File

@ -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<Byte> temporaryByteQueue = new LinkedList<Byte>();
@ -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);

View File

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

View File

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

View File

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

View File

@ -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<Byte> temporaryByteQueue = new LinkedList<Byte>();
@ -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<Byte> temporaryByteQueue = new LinkedList<Byte>();
@ -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);

View File

@ -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<Byte> temporaryByteQueue = new LinkedList<Byte>();
@ -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<Byte> temporaryByteQueue = new LinkedList<Byte>();
@ -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<Byte> temporaryByteQueue = new LinkedList<Byte>();
@ -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<Byte> temporaryByteQueue = new LinkedList<Byte>();
@ -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];

View File

@ -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
*/
}

View File

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

View File

@ -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<NetworkMessage> incomingMessageQueue = new CopyOnWriteArrayList<NetworkMessage>();
/**
* The queue of outgoing messages that have yet to be sent
*/
CopyOnWriteArrayList<NetworkMessage> outgoingMessageQueue = new CopyOnWriteArrayList<NetworkMessage>();
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<Byte> outgoingByteQueue = new CopyOnWriteArrayList<Byte>();
/**
* 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<NetworkMessage> 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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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<TerrainMessage> {
@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<TerrainMessage> {
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<TerrainMessage> {
* @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<TerrainMessage> {
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<TerrainMessage> {
}
}
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<ServerTerrainChunk> 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();
}

View File

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

View File

@ -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 * <size of chunk> + 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<Vector3f> samplerTriangles = new LinkedList<Vector3f>();
//List of UVs
List<Float> UVs = new LinkedList<Float>();
//
//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);
}
}

View File

@ -18,6 +18,11 @@ public class ImGuiLinePlot implements ImGuiElement {
//the data sets to draw
List<ImGuiLinePlotDataset> dataSets = new LinkedList<ImGuiLinePlotDataset>();
/**
* 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);
}
}
}

View File

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

View File

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

View File

@ -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<PhysicsDataCell>();
invalid = new HashSet<String>();
updateable = new HashSet<String>();

View File

@ -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<String,String> worldPosFileMap = new HashMap<String,String>();
/**
* 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();
}
}

View File

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

View File

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

View File

@ -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<ServerTerrainChunk> 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<ServerTerrainChunk> 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);
}
}

View File

@ -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<String, ServerTerrainChunk> chunkCache = new HashMap<String,ServerTerrainChunk>();
/**
* Tracks how recently a chunk has been queries for (used for evicting old chunks from cache)
*/
List<String> queryRecencyQueue = new LinkedList<String>();
/**
* Tracks what chunks are already queued to be asynchronously loaded. Used so we don't have two threads generating/fetching the same chunk
*/
Map<String, Boolean> queuedChunkMap = new HashMap<String,Boolean>();
/**
* 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<ServerTerrainChunk> getContents(){
lock.acquireUninterruptibly();
Collection<ServerTerrainChunk> 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();
}
}

View File

@ -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<String, ServerTerrainChunk> chunkCache;
List<String> 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<String, ServerTerrainChunk>();
this.chunkCacheContents = new CopyOnWriteArrayList<String>();
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<ServerTerrainChunk> 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();
}
}

View File

@ -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 <T> {
/**
* The maximum level of the chunk tree
*/
int maxLevel;
/**
* The root node
*/
private FloatingChunkTreeNode<T> root = null;
/**
* The list of all nodes in the tree
*/
List<FloatingChunkTreeNode<T>> 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<FloatingChunkTreeNode<T>>();
this.root = new FloatingChunkTreeNode<T>(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<T> split(FloatingChunkTreeNode<T> 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<T> newContainer = new FloatingChunkTreeNode<>(this, currentLevel, min, max);
//add children
newContainer.addChild(new FloatingChunkTreeNode<T>(this, currentLevel + 1, new Vector3i(min.x,min.y,min.z), new Vector3i(midX,midY,midZ)));
newContainer.addChild(new FloatingChunkTreeNode<T>(this, currentLevel + 1, new Vector3i(midX,min.y,min.z), new Vector3i(max.x,midY,midZ)));
newContainer.addChild(new FloatingChunkTreeNode<T>(this, currentLevel + 1, new Vector3i(min.x,midY,min.z), new Vector3i(midX,max.y,midZ)));
newContainer.addChild(new FloatingChunkTreeNode<T>(this, currentLevel + 1, new Vector3i(midX,midY,min.z), new Vector3i(max.x,max.y,midZ)));
//
newContainer.addChild(new FloatingChunkTreeNode<T>(this, currentLevel + 1, new Vector3i(min.x,min.y,midZ), new Vector3i(midX,midY,max.z)));
newContainer.addChild(new FloatingChunkTreeNode<T>(this, currentLevel + 1, new Vector3i(midX,min.y,midZ), new Vector3i(max.x,midY,max.z)));
newContainer.addChild(new FloatingChunkTreeNode<T>(this, currentLevel + 1, new Vector3i(min.x,midY,midZ), new Vector3i(midX,max.y,max.z)));
newContainer.addChild(new FloatingChunkTreeNode<T>(this, currentLevel + 1, new Vector3i(midX,midY,midZ), new Vector3i(max.x,max.y,max.z)));
boolean foundMin = false;
for(FloatingChunkTreeNode<T> 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<T> 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<T> join(FloatingChunkTreeNode<T> 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<T> 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<T> existing, FloatingChunkTreeNode<T> newNode){
if(existing == this.root){
this.root = newNode;
} else {
FloatingChunkTreeNode<T> parent = existing.getParent();
parent.removeChild(existing);
parent.addChild(newNode);
}
}
/**
* Gets the root node of the tree
*/
public FloatingChunkTreeNode<T> 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<T>(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<T> 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<T> 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<T> 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<T> recursiveSearchUnsafe(FloatingChunkTreeNode<T> 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<T> 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<T> 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<T> {
//True if this is a leaf node, false otherwise
private boolean isLeaf;
//the parent node
private FloatingChunkTreeNode<T> parent;
/**
* The tree containing this node
*/
private WorldOctTree<T> containingTree;
//the children of this node
private List<FloatingChunkTreeNode<T>> children = new LinkedList<FloatingChunkTreeNode<T>>();
//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<T> 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 <T> FloatingChunkTreeNode<T> constructorForTests(WorldOctTree<T> tree, int level, Vector3i min, Vector3i max){
return new FloatingChunkTreeNode<T>(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<T> getParent() {
return parent;
}
/**
* Gets the children of this node
*/
public List<FloatingChunkTreeNode<T>> 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<T> child){
this.children.add(child);
child.parent = this;
}
/**
* Removes a child node
* @param child the child
*/
private void removeChild(FloatingChunkTreeNode<T> child){
this.children.remove(child);
child.parent = null;
}
}
}

View File

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

View File

@ -30,6 +30,7 @@ public class StateCleanupCheckerExtension implements AfterEachCallback {
Globals.playerManager,
LoggerInterface.loggerEngine,
RenderingEngine.screenFramebuffer,
Globals.clientWorldData,
};
for(Object object : objectsToCheck){
if(object != null){

View File

@ -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<String> tree = new ChunkTree<String>();
assertNotNull(tree);
}
}

View File

@ -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<String> tree = new WorldOctTree<String>(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<String> tree = new WorldOctTree<String>(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<String>(new Vector3i(0,0,0), new Vector3i(63,63,63));
});
}
/**
* Assert unequal dims fail
*/
@UnitTest
public void testFailOnUnequalDim(){
assertThrows(Error.class, () -> {
new WorldOctTree<String>(new Vector3i(0,0,0), new Vector3i(64,1,64));
});
}
}

View File

@ -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",