412 lines
13 KiB
Java
412 lines
13 KiB
Java
package electrosphere.server.fluid.manager;
|
|
|
|
|
|
import electrosphere.engine.Globals;
|
|
import electrosphere.game.server.world.ServerWorldData;
|
|
import electrosphere.server.fluid.diskmap.FluidDiskMap;
|
|
import electrosphere.server.fluid.generation.FluidGenerator;
|
|
import electrosphere.server.fluid.models.FluidModel;
|
|
import electrosphere.server.fluid.simulator.FluidAcceleratedSimulator;
|
|
import electrosphere.server.fluid.simulator.ServerFluidSimulator;
|
|
import electrosphere.server.terrain.manager.ServerTerrainManager;
|
|
import electrosphere.util.FileUtils;
|
|
import electrosphere.util.annotation.Exclude;
|
|
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.FloatBuffer;
|
|
import java.util.HashMap;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.concurrent.locks.ReentrantLock;
|
|
|
|
import org.joml.Vector3i;
|
|
|
|
/**
|
|
* Provides an interface for the server to query information about fluid
|
|
*/
|
|
public class ServerFluidManager {
|
|
|
|
/**
|
|
* DEfault size of the cache
|
|
*/
|
|
static final int DEFAULT_CACHE_SIZE = 500;
|
|
|
|
/**
|
|
* The number of frames to wait between updates
|
|
*/
|
|
static final int UPDATE_RATE = 0;
|
|
|
|
//the seed for the water
|
|
long seed;
|
|
|
|
//The model of the fluid this manager is managing
|
|
FluidModel 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 = DEFAULT_CACHE_SIZE;
|
|
@Exclude
|
|
Map<String, ServerFluidChunk> chunkCache = new HashMap<String, ServerFluidChunk>();
|
|
@Exclude
|
|
List<String> chunkCacheContents = new LinkedList<String>();
|
|
|
|
//The map of chunk position <-> file on disk containing chunk data
|
|
FluidDiskMap chunkDiskMap = null;
|
|
|
|
@Exclude
|
|
//The generation algorithm for this fluid manager
|
|
FluidGenerator chunkGenerator;
|
|
|
|
@Exclude
|
|
//the fluid simulator
|
|
ServerFluidSimulator serverFluidSimulator;
|
|
|
|
@Exclude
|
|
//the terrain manager associated
|
|
ServerTerrainManager serverTerrainManager;
|
|
|
|
//controls whether fluid simulation should actually happen or not
|
|
boolean simulate = false;
|
|
|
|
@Exclude
|
|
/**
|
|
* The parent world data
|
|
*/
|
|
ServerWorldData parent;
|
|
|
|
@Exclude
|
|
/**
|
|
* Locks the fluid manager
|
|
*/
|
|
ReentrantLock lock = new ReentrantLock();
|
|
|
|
@Exclude
|
|
/**
|
|
* The queued of chunks to simulate
|
|
*/
|
|
List<ServerFluidChunk> simulationQueue = new LinkedList<ServerFluidChunk>();
|
|
|
|
@Exclude
|
|
/**
|
|
* The queue of chunks to broadcast
|
|
*/
|
|
List<ServerFluidChunk> broadcastQueue = new LinkedList<ServerFluidChunk>();
|
|
|
|
@Exclude
|
|
/**
|
|
* The update frame-skipping tracking variable
|
|
*/
|
|
int updatePhase = 0;
|
|
|
|
@Exclude
|
|
/**
|
|
* The number of chunks broadcast this frame
|
|
*/
|
|
int broadcastSize = 0;
|
|
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
public ServerFluidManager(
|
|
ServerWorldData parent,
|
|
ServerTerrainManager serverTerrainManager,
|
|
long seed,
|
|
FluidGenerator chunkGenerator
|
|
){
|
|
this.parent = parent;
|
|
this.serverTerrainManager = serverTerrainManager;
|
|
this.seed = seed;
|
|
this.chunkGenerator = chunkGenerator;
|
|
this.serverFluidSimulator = new FluidAcceleratedSimulator();
|
|
}
|
|
|
|
ServerFluidManager(){
|
|
|
|
}
|
|
|
|
/**
|
|
* Generates a fluid model for the manager
|
|
*/
|
|
public void generate(){
|
|
}
|
|
|
|
/**
|
|
* Saves the fluid model backing this manager to a save file
|
|
* @param saveName The name of the save
|
|
*/
|
|
public void save(String saveName){
|
|
lock.lock();
|
|
if(model != null){
|
|
ByteBuffer buffer = ByteBuffer.allocate(model.getElevation().length * model.getElevation()[0].length * 4);
|
|
FloatBuffer floatView = buffer.asFloatBuffer();
|
|
for(int x = 0; x < model.getElevation().length; x++){
|
|
floatView.put(model.getElevation()[x]);
|
|
}
|
|
if(floatView.position() > 0){
|
|
floatView.flip();
|
|
}
|
|
FileUtils.saveBinaryToSavePath(saveName, "./fluid.dat", buffer.array());
|
|
FileUtils.serializeObjectToSavePath(saveName, "./fluid.json", model);
|
|
}
|
|
//for each chunk, save via disk map
|
|
for(String chunkKey : chunkCacheContents){
|
|
ServerFluidChunk chunk = chunkCache.get(chunkKey);
|
|
chunkDiskMap.saveToDisk(chunk);
|
|
}
|
|
//save disk map itself
|
|
if(chunkDiskMap != null){
|
|
chunkDiskMap.save();
|
|
}
|
|
lock.unlock();
|
|
}
|
|
|
|
/**
|
|
* Loads a fluid manager from a save file
|
|
* @param saveName The name of the save
|
|
*/
|
|
public void load(String saveName){
|
|
//load fluid model
|
|
model = FileUtils.loadObjectFromSavePath(saveName, "./fluid.json", FluidModel.class);
|
|
chunkGenerator.setModel(model);
|
|
byte[] data = FileUtils.loadBinaryFromSavePath(saveName, "./fluid.dat");
|
|
ByteBuffer buffer = ByteBuffer.wrap(data);
|
|
FloatBuffer floatView = buffer.asFloatBuffer();
|
|
float[][] elevation = new float[parent.getWorldSizeDiscrete()][parent.getWorldSizeDiscrete()];
|
|
for(int x = 0; x < parent.getWorldSizeDiscrete(); x++){
|
|
for(int y = 0; y < parent.getWorldSizeDiscrete(); y++){
|
|
elevation[x][y] = floatView.get();
|
|
}
|
|
}
|
|
model.setElevationArray(elevation);
|
|
//load chunk disk map
|
|
chunkDiskMap = new FluidDiskMap();
|
|
chunkDiskMap.init(saveName);
|
|
}
|
|
|
|
/**
|
|
* 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 fluid chunk
|
|
* @param worldX The world x position
|
|
* @param worldY The world y position
|
|
* @param worldZ The world z position
|
|
* @return The ServerFluidChunk
|
|
*/
|
|
public ServerFluidChunk getChunk(int worldX, int worldY, int worldZ){
|
|
ServerFluidChunk returnedChunk = null;
|
|
lock.lock();
|
|
//THIS FIRES IF THERE IS A MAIN GAME WORLD RUNNING
|
|
String key = getKey(worldX,worldY,worldZ);
|
|
if(chunkCache.containsKey(key)){
|
|
chunkCacheContents.remove(key);
|
|
chunkCacheContents.add(0, key);
|
|
returnedChunk = chunkCache.get(key);
|
|
} else {
|
|
if(chunkCacheContents.size() > cacheSize){
|
|
String oldChunkKey = chunkCacheContents.remove(chunkCacheContents.size() - 1);
|
|
ServerFluidChunk oldChunk = chunkCache.remove(oldChunkKey);
|
|
oldChunk.freeBuffers();
|
|
this.linkNeighbors(null, oldChunk.getWorldX(), oldChunk.getWorldY(), oldChunk.getWorldZ());
|
|
}
|
|
//pull from disk if it exists
|
|
if(chunkDiskMap != null){
|
|
if(chunkDiskMap.containsFluidAtPosition(worldX, worldY, worldZ)){
|
|
returnedChunk = chunkDiskMap.getFluidChunk(worldX, worldY, worldZ);
|
|
}
|
|
}
|
|
//generate if it does not exist
|
|
if(returnedChunk == null){
|
|
returnedChunk = this.generateChunk(worldX, worldY, worldZ);
|
|
}
|
|
chunkCache.put(key, returnedChunk);
|
|
chunkCacheContents.add(key);
|
|
}
|
|
lock.unlock();
|
|
return returnedChunk;
|
|
}
|
|
|
|
/**
|
|
* Saves a given position's chunk to disk.
|
|
* Uses the current global save name
|
|
* @param position The position to save
|
|
*/
|
|
public void savePositionToDisk(Vector3i position){
|
|
lock.lock();
|
|
chunkDiskMap.saveToDisk(getChunk(position.x, position.y, position.z));
|
|
lock.unlock();
|
|
}
|
|
|
|
/**
|
|
* Applies a deform to fluid at a given location
|
|
* @param worldPos The world coordinates of the chunk to modify
|
|
* @param voxelPos The voxel coordinates of the voxel to modify
|
|
* @param weight The weight to set it to
|
|
* @param value The value to set it to
|
|
*/
|
|
public void deformFluidAtLocationToValue(Vector3i worldPos, Vector3i voxelPos, float weight, int value){
|
|
if(voxelPos.x < 0 || voxelPos.y < 0 || voxelPos.z < 0){
|
|
return;
|
|
}
|
|
if(worldPos.x < 0 || worldPos.y < 0 || worldPos.z < 0){
|
|
return;
|
|
}
|
|
// TerrainModification modification = new TerrainModification(worldPos,voxelPos,weight,value);
|
|
// //could be null if, for instance, arena mode
|
|
// if(model != null){
|
|
// model.addModification(modification);
|
|
// }
|
|
// String key = getKey(worldPos.x,worldPos.y,worldPos.z);
|
|
// if(chunkCache.containsKey(key)){
|
|
// ServerFluidChunk chunk = chunkCache.get(key);
|
|
// chunk.addModification(modification);
|
|
// }
|
|
lock.lock();
|
|
ServerFluidChunk fluidChunk = this.getChunk(worldPos.x, worldPos.y, worldPos.z);
|
|
fluidChunk.setWeight(voxelPos.x, voxelPos.y, voxelPos.z, weight);
|
|
lock.unlock();
|
|
}
|
|
|
|
/**
|
|
* Adds a chunk to the queue to be simulated
|
|
* @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 queue(int worldX, int worldY, int worldZ){
|
|
ServerFluidChunk fluidChunk = this.getChunk(worldX, worldY, worldZ);
|
|
this.simulationQueue.add(fluidChunk);
|
|
}
|
|
|
|
/**
|
|
* Simulates a chunk
|
|
*/
|
|
public void simulate(){
|
|
Globals.profiler.beginAggregateCpuSample("ServerFluidManager.simulate");
|
|
lock.lock();
|
|
if(simulate){
|
|
if(updatePhase == UPDATE_RATE){
|
|
if(this.serverFluidSimulator != null){
|
|
this.serverFluidSimulator.simulate(this.simulationQueue,this.broadcastQueue);
|
|
}
|
|
}
|
|
|
|
this.simulationQueue.clear();
|
|
this.broadcastSize = this.broadcastQueue.size();
|
|
|
|
updatePhase++;
|
|
if(updatePhase > UPDATE_RATE){
|
|
updatePhase = 0;
|
|
}
|
|
}
|
|
lock.unlock();
|
|
Globals.profiler.endCpuSample();
|
|
}
|
|
|
|
/**
|
|
* Generates a fluid chunk
|
|
* @param worldX The world x position
|
|
* @param worldY The world y position
|
|
* @param worldZ The world z position
|
|
* @return The fluid chunk
|
|
*/
|
|
private ServerFluidChunk generateChunk(int worldX, int worldY, int worldZ){
|
|
lock.lock();
|
|
ServerFluidChunk rVal = chunkGenerator.generateChunk(worldX, worldY, worldZ);
|
|
this.linkNeighbors(rVal, worldX, worldY, worldZ);
|
|
lock.unlock();
|
|
return rVal;
|
|
}
|
|
|
|
/**
|
|
* Links the world position to all its neighboring fluid chunks
|
|
* @param current The chunk to link
|
|
* @param worldX The world x position
|
|
* @param worldY The world y position
|
|
* @param worldZ The world z position
|
|
*/
|
|
private void linkNeighbors(ServerFluidChunk current, int worldX, int worldY, int worldZ){
|
|
String key = this.getKey(worldX,worldY,worldZ);
|
|
for(int i = -1; i < 2; i++){
|
|
for(int j = -1; j < 2; j++){
|
|
for(int k = -1; k < 2; k++){
|
|
if(i == j && j == k && k == 0){
|
|
continue;
|
|
}
|
|
if(
|
|
0 <= worldX + i && worldX + i < this.parent.getWorldSizeDiscrete() &&
|
|
0 <= worldY + j && worldY + j < this.parent.getWorldSizeDiscrete() &&
|
|
0 <= worldZ + k && worldZ + k < this.parent.getWorldSizeDiscrete()
|
|
){
|
|
key = this.getKey(worldX + i,worldY +j,worldZ + k);
|
|
ServerFluidChunk neighbor = this.chunkCache.get(key);
|
|
if(current != null){
|
|
current.setNeighbor(i+1,j+1,k+1,neighbor);
|
|
}
|
|
if(neighbor != null){
|
|
neighbor.setNeighbor(1-i,1-j,1-k,current);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the broadcast queue
|
|
* @return The broadcast queue
|
|
*/
|
|
public List<ServerFluidChunk> getBroadcastQueue(){
|
|
return this.broadcastQueue;
|
|
}
|
|
|
|
//getter for simulate
|
|
public boolean getSimulate(){
|
|
return simulate;
|
|
}
|
|
|
|
//setter for simulate
|
|
public void setSimulate(boolean simulate){
|
|
this.simulate = simulate;
|
|
}
|
|
|
|
/**
|
|
* Sets the parent world data of this manager
|
|
* @param serverWorldData The parent world data
|
|
*/
|
|
public void setParent(ServerWorldData serverWorldData){
|
|
this.parent = serverWorldData;
|
|
}
|
|
|
|
/**
|
|
* Gets the server fluid simulator for this manager
|
|
* @return The server fluid simulator
|
|
*/
|
|
public ServerFluidSimulator getSimulator(){
|
|
return serverFluidSimulator;
|
|
}
|
|
|
|
/**
|
|
* Gets the number of chunks broadcast this frame
|
|
* @return The number of chunks
|
|
*/
|
|
public int getBroadcastSize(){
|
|
return broadcastSize;
|
|
}
|
|
|
|
|
|
}
|