js chunkgen + fixes + scriptengine work
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good

This commit is contained in:
austin 2024-11-09 20:04:44 -05:00
parent 86e61b7a0e
commit cc60818e35
32 changed files with 819 additions and 72 deletions

View File

@ -4,6 +4,7 @@ import { HookManager } from '/Scripts/engine/hooks/hook-manager'
import { SceneLoader } from '/Scripts/engine/scene/scene-loader'
import { Engine } from '/Scripts/types/engine'
import { clientHooks } from '/Scripts/client/clienthooks'
import { ChunkGeneratorManager } from '/Scripts/server/chunk/chunkgeneratormanager'
/**
* The core engine values
@ -13,6 +14,7 @@ export const engine: Engine = {
singletons: {},
hookManager: new HookManager(),
sceneLoader: new SceneLoader(),
chunkGeneratorManager: new ChunkGeneratorManager(),
}
/**
@ -25,6 +27,7 @@ export const ENGINE_onInit = () => {
let client: NamespaceClient = Client
engine.sceneLoader.engine = engine
engine.hookManager.engine = engine
engine.chunkGeneratorManager.engine = engine
//load global hooks
clientHooks.forEach(hook => {

View File

@ -0,0 +1,39 @@
import { TestGen } from "/Scripts/server/chunk/generators/testgen"
import { Engine } from "/Scripts/types/engine"
import { ChunkGenerator, VoxelFunction } from "/Scripts/types/host/server/chunk/chunkgenerator"
/**
* Manages all the chunk generators defined script-side
*/
export class ChunkGeneratorManager {
/**
* The parent engine object
*/
engine: Engine
/**
* The list of registered chunk generators
*/
readonly registeredGenerators: ChunkGenerator[] = [
TestGen,
]
/**
* Gets the voxel function for the tag
* @param tag The tag
* @returns The voxel function if it exists, null otherwise
*/
readonly getVoxelFunction = (tag: string): VoxelFunction => {
let rVal: VoxelFunction = null
this.registeredGenerators.forEach(generator => {
if(generator.getTag() === tag){
rVal = generator.getVoxelFunction(this.engine)
}
})
return rVal
}
}

View File

@ -0,0 +1,159 @@
import { Engine } from "/Scripts/types/engine";
import { CHUNK_WIDTH, ChunkGenerator, GeneratedVoxel, VoxelFunction } from "/Scripts/types/host/server/chunk/chunkgenerator";
import { BiomeData } from "/Scripts/types/host/server/data/biomedata";
/**
* Converts a chunk coordinate to a real coordinate
* @param chunk The chunk pos
* @param world The world pos
* @returns The real pos
*/
const voxelToReal = (chunk: number, world: number) => {
return world * CHUNK_WIDTH + chunk
}
/**
* Gets the voxel on the surface
* @return The voxel
*/
const getSurfaceVoxel = (
voxel: GeneratedVoxel,
worldX: number, worldY: number, worldZ: number,
chunkX: number, chunkY: number, chunkZ: number,
realX: number, realY: number, realZ: number,
surfacePercent: number,
surfaceHeight: number,
surfaceBiome: BiomeData
) => {
voxel.weight = surfacePercent * 2 - 1;
voxel.type = 2;
return voxel;
}
/**
* Gets the voxel below the surface
* @return The voxel
*/
const getSubsurfaceVoxel = (
voxel: GeneratedVoxel,
worldX: number, worldY: number, worldZ: number,
chunkX: number, chunkY: number, chunkZ: number,
realX: number, realY: number, realZ: number,
surfacePercent: number,
surfaceHeight: number,
surfaceBiome: BiomeData
) => {
if(realY < surfaceHeight - 5){
voxel.weight = 1;
voxel.type = 6;
} else {
voxel.weight = 1;
voxel.type = 1;
}
return voxel;
}
/**
* Gets the voxel above the service
* @return The voxel
*/
const getOverSurfaceVoxel = (
voxel: GeneratedVoxel,
worldX: number, worldY: number, worldZ: number,
chunkX: number, chunkY: number, chunkZ: number,
realX: number, realY: number, realZ: number,
surfacePercent: number,
surfaceHeight: number,
surfaceBiome: BiomeData
) => {
voxel.weight = -1;
voxel.type = 0;
return voxel;
}
/**
* A test generator
*/
export const TestGen: ChunkGenerator = {
/**
* Gets the tag for this generator
* @returns The tag
*/
getTag: () => "test",
/**
* The elevation function
* @param worldX The world x coordinate
* @param worldZ The world z coordinate
* @param chunkX The chunk x coordinate
* @param chunkZ The chunk z coordinate
*/
getElevation: (worldX: number, worldZ: number, chunkX: number, chunkZ: number): number => {
return 1
},
/**
* Gets the function to actually get voxels
* @param engine The engine reference
*/
getVoxelFunction: (
engine: Engine
): VoxelFunction => {
const rVal = (
worldX: number, worldY: number, worldZ: number,
chunkX: number, chunkY: number, chunkZ: number,
stride: number,
surfaceHeight: number,
surfaceBiome: BiomeData
): GeneratedVoxel => {
let rVal: GeneratedVoxel = {
type: 0,
weight: -1,
}
const realX = voxelToReal(chunkX,worldX)
const realY = voxelToReal(chunkY,worldY)
const realZ = voxelToReal(chunkZ,worldZ)
const strideMultiplier = engine.classes.math.static.pow(2,stride)
// const strideMultiplier = 1
const heightDiff = realY - surfaceHeight
const surfacePercent = (surfaceHeight - realY) / strideMultiplier
if(heightDiff < -strideMultiplier){
return getSubsurfaceVoxel(
rVal,
worldX, worldY, worldZ,
chunkX, chunkY, chunkZ,
realX, realY, realZ,
surfacePercent,
surfaceHeight,
surfaceBiome
);
} else if(heightDiff > 0) {
return getOverSurfaceVoxel(
rVal,
worldX, worldY, worldZ,
chunkX, chunkY, chunkZ,
realX, realY, realZ,-
surfacePercent,
surfaceHeight,
surfaceBiome
);
} else {
return getSurfaceVoxel(
rVal,
worldX, worldY, worldZ,
chunkX, chunkY, chunkZ,
realX, realY, realZ,
surfacePercent,
surfaceHeight,
surfaceBiome
);
}
}
return rVal
},
}

View File

@ -1,5 +1,6 @@
import { HookManager } from "/Scripts/engine/hooks/hook-manager";
import { SceneLoader } from "/Scripts/engine/scene/scene-loader";
import { ChunkGeneratorManager } from "/Scripts/server/chunk/chunkgeneratormanager";
import { SingletonsMap } from "/Scripts/types/host/singletons";
import { StaticClasses } from "/Scripts/types/host/static-classes";
@ -29,5 +30,10 @@ export interface Engine {
*/
readonly sceneLoader: SceneLoader,
/**
* The chunk generator manager
*/
readonly chunkGeneratorManager: ChunkGeneratorManager,
}

View File

@ -0,0 +1,95 @@
import { Engine } from "/Scripts/types/engine";
import { BiomeData } from "/Scripts/types/host/server/data/biomedata";
/**
* The width of a chunk
*/
export const CHUNK_WIDTH = 16;
/**
* A voxel generated by a ChunkGenerator
*/
export interface GeneratedVoxel {
/**
* The id of the voxel type
*/
type: number,
/**
* The weight of the voxel
*/
weight: number,
}
/**
* Gets a voxel for a given position
* @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 chunkX The chunk x coordinate
* @param chunkY The chunk y coordinate
* @param chunkZ The chunk z coordinate
* @param stride The stride of the data (this is used for LOD, it ranges 0->4 with 0 being the highest level of detail and 4 being the lowest)
* @param surfaceHeight The height of the surface at the given x,z coordinate
* @param terrainModel The terrain model
* @param surfaceBiome The surface biome
* @returns The voxel at the provided position
*/
export type VoxelFunction = (
worldX: number, worldY: number, worldZ: number,
chunkX: number, chunkY: number, chunkZ: number,
stride: number,
surfaceHeight: number,
surfaceBiome: BiomeData
) => GeneratedVoxel;
/**
* Gets a chunk for a given world position
* @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 stride The stride of the data (this is used for LOD, it ranges 0->4 with 0 being the highest level of detail and 4 being the lowest)
* @param surfaceHeight The height of the surface at the given x,z coordinate
* @param terrainModel The terrain model
* @param surfaceBiome The surface biome
* @returns The voxel at the provided position
*/
export type ChunkFunction = (
worldX: number, worldY: number, worldZ: number,
chunkX: number, chunkY: number, chunkZ: number,
stride: number,
surfaceHeight: number,
surfaceBiome: BiomeData
) => number[][][];
/**
* A chunk generator
*/
export interface ChunkGenerator {
/**
* Gets the tag for this generator
* @returns The tag
*/
getTag: () => string
/**
* Retrieves the elevation for the world at a given x,z coordinate
* @param worldX The world x coordinate
* @param worldZ The world z coordinate
* @param chunkX The x coordinate of the chunk within the specified world coordinate
* @param chunkZ The z coordinate of the chunk within the specified world coordinate
* @returns The elevation at that specific position
*/
getElevation: (worldX: number, worldZ: number, chunkX: number, chunkZ: number) => number
/**
* The function to get a voxel for a given position
*/
getVoxelFunction: (engine: Engine) => VoxelFunction
}

View File

@ -0,0 +1,95 @@
/**
* Describes a type of foliage that can be generated in a biome
*/
export interface BiomeFoliageDescription {
/**
* The list of entity IDs in the type
*/
entityIDs: string[],
/**
* How regular the placement of the foliage is.
* Low values make the placement more random.
* High values make the placement more orderly (aligned with a grid).
*/
regularity: number,
/**
* The percentage of the ground to cover with foliage
*/
threshold: number,
/**
* The priority of this type in particular
*/
priority: number,
/**
* The scale of the noise used to place foliage
*/
scale: number,
}
/**
* The parameters for generating the surface of a biome
*/
export interface BiomeSurfaceGenerationParams {
/**
* The tag for the generation algorithm to generate the surface of the biome
*/
surfaceGenTag: string,
/**
* The offset added to the generated heightfield to get the final height of the surface at a given position.
* This is most useful for cases like plateaus where you really want to put the surface noise higher than its surroundings.
*/
heightOffset: number,
/**
* The different foliage types that can be generated on this surface
*/
foliageDescriptions: BiomeFoliageDescription[],
}
/**
* Biome data
*/
export interface BiomeData {
/**
* The id of the biome type
*/
id: string,
/**
* The display name of the biome
*/
displayName: string,
/**
* True if this is an aerial biome
*/
isAerial: boolean,
/**
* True if this is a surface biome
*/
isSurface: boolean,
/**
* True if this is a subterranean biome
*/
isSubterranean: boolean,
/**
* The parameters for generating the surface of this biome
*/
surfaceGenerationParams: BiomeSurfaceGenerationParams,
}

View File

@ -46,6 +46,11 @@ export interface StaticClasses {
*/
readonly levelEditorUtils?: Class<ClientLevelEditorUtils>,
/**
* Math functions
*/
readonly math?: Class<Math>
}
/**

View File

@ -0,0 +1,14 @@
/**
* Math functions
*/
export interface Math {
/**
* Computes an arbitrary power of a number
* @param num1 The number
* @param num2 The power
* @returns The result of the power
*/
readonly pow: (num1: number, num2: number) => number
}

View File

@ -963,6 +963,15 @@ Fix foliage rendering
Fix async physics gen on client
Convert volumetric + shadow pass to entity tags
(11/09/2024)
Fix eighth res chunk radius
Fix chunk gen debug ui regenerate button
Script-defined chunk generators
Script engine synchronization utility
Convert ScriptEngine to service
ScriptEngine full re-initialization signal
Add surface width to test generator
# TODO

View File

@ -14,9 +14,11 @@ public class ClientScriptUtils {
* @param args The arguments provided alongside the signal
*/
public static void fireSignal(String signalName, Object ... args){
if(Globals.scriptEngine != null && Globals.scriptEngine.isInitialized()){
Globals.scriptEngine.fireSignal(signalName, ScriptEngine.GLOBAL_SCENE, args);
}
Globals.scriptEngine.executeSynchronously(() -> {
if(Globals.scriptEngine != null && Globals.scriptEngine.isInitialized()){
Globals.scriptEngine.fireSignal(signalName, ScriptEngine.GLOBAL_SCENE, args);
}
});
}
}

View File

@ -97,6 +97,9 @@ public class ClientTerrainCache {
this.cacheList.clear();
this.cacheMapFullRes.clear();
this.cacheMapHalfRes.clear();
this.cacheMapQuarterRes.clear();
this.cacheMapEighthRes.clear();
this.cacheMapSixteenthRes.clear();
this.chunkPositionMap.clear();
lock.release();
}

View File

@ -98,6 +98,11 @@ public class ClientDrawCellManager {
*/
Map<FloatingChunkTreeNode<DrawCell>,Boolean> evaluationMap = new HashMap<FloatingChunkTreeNode<DrawCell>,Boolean>();
/**
* All draw cells currently tracked
*/
List<DrawCell> activeCells = new LinkedList<DrawCell>();
/**
* Tracks whether the cell manager updated last frame or not
*/
@ -227,6 +232,7 @@ public class ClientDrawCellManager {
child.getMinBound().z
);
DrawCell drawCell = DrawCell.generateTerrainCell(cellWorldPos);
activeCells.add(drawCell);
child.convertToLeaf(drawCell);
evaluationMap.put(child,true);
});
@ -246,6 +252,7 @@ public class ClientDrawCellManager {
//do creations
DrawCell drawCell = DrawCell.generateTerrainCell(node.getMinBound());
activeCells.add(drawCell);
newLeaf.convertToLeaf(drawCell);
//update neighbors
@ -544,7 +551,7 @@ public class ClientDrawCellManager {
(
node.getLevel() == this.chunkTree.getMaxLevel() - EIGHTH_RES_LOD
&&
this.getMinDistance(pos, node) <= EIGHTH_RES_DIST
this.getMinDistance(pos, node) <= SIXTEENTH_RES_DIST
)
||
(
@ -591,7 +598,7 @@ public class ClientDrawCellManager {
(
node.getLevel() == this.chunkTree.getMaxLevel() - EIGHTH_RES_LOD
&&
this.getMinDistance(pos, node) <= EIGHTH_RES_DIST
this.getMinDistance(pos, node) <= SIXTEENTH_RES_DIST
)
||
(
@ -631,6 +638,7 @@ public class ClientDrawCellManager {
node.getChildren().forEach(child -> recursivelyDestroy(child));
}
if(node.getData() != null){
activeCells.remove(node.getData());
node.getData().destroy();
}
}
@ -659,6 +667,7 @@ public class ClientDrawCellManager {
* Evicts all cells
*/
public void evictAll(){
this.recursivelyDestroy(this.chunkTree.getRoot());
this.chunkTree.clear();
}

View File

@ -1,6 +1,7 @@
package electrosphere.client.ui.menu.debug;
import electrosphere.engine.Globals;
import electrosphere.engine.signal.Signal.SignalType;
import electrosphere.renderer.ui.imgui.ImGuiWindow;
import electrosphere.renderer.ui.imgui.ImGuiWindow.ImGuiWindowCallback;
import electrosphere.server.datacell.GriddedDataCellManager;
@ -45,10 +46,19 @@ public class ImGuiTestGen {
//regenerate the test area
if(ImGui.button("Regenerate")){
GriddedDataCellManager gridManager = (GriddedDataCellManager)Globals.realmManager.first().getDataCellManager();
gridManager.evictAll();
Globals.clientDrawCellManager.evictAll();
Globals.clientTerrainManager.evictAll();
//recompile script engine
Globals.signalSystem.post(SignalType.SCRIPT_RECOMPILE, () -> {
//run once script recompilation has completed
//clear server
GriddedDataCellManager gridManager = (GriddedDataCellManager)Globals.realmManager.first().getDataCellManager();
gridManager.evictAll();
//clear client
Globals.clientDrawCellManager.evictAll();
Globals.clientTerrainManager.evictAll();
});
}
//set macro data scale in terrain model
@ -82,7 +92,7 @@ public class ImGuiTestGen {
}
//Toggles whether the client draws cell manager should update or not
if(ImGui.button("Toggle ClientDrawCellManager updates")){
if(ImGui.button("Toggle ClientDrawCellManager updates " + (Globals.clientDrawCellManager.getShouldUpdate() ? "off" : "on"))){
Globals.clientDrawCellManager.setShouldUpdate(!Globals.clientDrawCellManager.getShouldUpdate());
}

View File

@ -481,8 +481,6 @@ public class Globals {
skyboxColors = new ArrayList<Vector3f>();
//load asset manager
assetManager = new AssetManager();
//script engine
scriptEngine = new ScriptEngine();
//ai manager
aiManager = new AIManager(0);
//realm & data cell manager
@ -526,6 +524,7 @@ public class Globals {
Globals.signalSystem = (SignalSystem)serviceManager.registerService(new SignalSystem());
Globals.elementService = (ElementService)serviceManager.registerService(new ElementService());
Globals.particleService = (ParticleService)serviceManager.registerService(new ParticleService());
Globals.scriptEngine = (ScriptEngine)serviceManager.registerService(new ScriptEngine());
serviceManager.instantiate();
//
//End service manager
@ -537,6 +536,7 @@ public class Globals {
Globals.signalSystem.registerService(SignalType.YOGA_DESTROY, Globals.elementService);
Globals.signalSystem.registerService(SignalType.UI_MODIFICATION, Globals.elementService);
Globals.signalSystem.registerService(SignalType.RENDERING_ENGINE_READY, Globals.particleService);
Globals.signalSystem.registerService(SignalType.SCRIPT_RECOMPILE, Globals.scriptEngine);
}

View File

@ -291,7 +291,13 @@ public class Main {
RenderingEngine.recaptureIfNecessary();
Globals.profiler.endCpuSample();
}
///
/// S Y N C H R O N O U S S I G N A L H A N D L I N G
///
Globals.scriptEngine.handleAllSignals();
///
///

View File

@ -14,6 +14,7 @@ import electrosphere.net.parser.net.message.TerrainMessage;
import electrosphere.net.server.ServerConnectionHandler;
import electrosphere.renderer.ui.elements.Window;
import electrosphere.server.saves.SaveUtils;
import electrosphere.server.terrain.generation.TestGenerationChunkGenerator;
/**
* Loads the chunk generation testing realm
@ -42,6 +43,17 @@ public class ChunkGenerationTestLoading {
loadingWindow.setVisible(true);
});
//wait on script engine to load
if(TestGenerationChunkGenerator.DEFAULT_USE_JAVASCRIPT){
WindowUtils.updateLoadingWindow("Waiting on scripting engine");
while(!Globals.scriptEngine.isInitialized()){
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException ex) {}
}
WindowUtils.updateLoadingWindow("LOADING");
}
String saveName = "generation_testing";

View File

@ -11,7 +11,7 @@ public class EngineInitLoading {
* Loads the core assets of the scripting engine from disk and initializes the engine
*/
protected static void loadScriptingEngine(Object[] params){
Globals.scriptEngine.init();
Globals.scriptEngine.initScripts();
}
}

View File

@ -37,6 +37,11 @@ public class Signal {
REQUEST_CHUNK_EDIT,
CHUNK_EDITED,
//
//Script
//
SCRIPT_RECOMPILE,
}
/**

View File

@ -124,8 +124,11 @@ public class SceneLoader {
}
//load scripts
if(!isLevelEditor && file.getInitScriptPath() != null){
int sceneInstanceId = Globals.scriptEngine.initScene(file.getInitScriptPath());
realm.setSceneInstanceId(sceneInstanceId);
Realm finalRealm = realm;
Globals.scriptEngine.executeSynchronously(() -> {
int sceneInstanceId = Globals.scriptEngine.initScene(file.getInitScriptPath());
finalRealm.setSceneInstanceId(sceneInstanceId);
});
}
//TODO: integrate scripts for client side of scenes
// for(String scriptPath : file.getScriptPaths()){

View File

@ -2,6 +2,8 @@ package electrosphere.game.data.biome;
import java.util.List;
import org.graalvm.polyglot.HostAccess.Export;
/**
* Data about a given biome
*/
@ -10,11 +12,13 @@ public class BiomeData {
/**
* The id of the biome
*/
@Export
String id;
/**
* The display name of the biome
*/
@Export
String displayName;
/**
@ -25,21 +29,25 @@ public class BiomeData {
/**
* True if this region applies above the surface
*/
@Export
Boolean isAerial;
/**
* True if this region applies to the surface
*/
@Export
Boolean isSurface;
/**
* True if this region applies below the surface
*/
@Export
Boolean isSubterranean;
/**
* The surface generation params
*/
@Export
BiomeSurfaceGenerationParams surfaceGenerationParams;

View File

@ -2,34 +2,41 @@ package electrosphere.game.data.biome;
import java.util.List;
import org.graalvm.polyglot.HostAccess.Export;
/**
* Describes behavior for spawning a specific type of foliage in the biome
*/
public class BiomeFoliageDescription {
/**
* The liust of entity IDs of this foliage type in particular
* The list of entity IDs of this foliage type in particular
*/
@Export
List<String> entityIDs;
/**
* How regular the placement of foliage is. Low values will create very uneven foliage, while high values will place them along a grid.
*/
@Export
Double regularity;
/**
* The percentage of the ground to cover with foliage
*/
@Export
Double threshold;
/**
* The priority of this floor element in particular
*/
@Export
Double priority;
/**
* The scale of the noise used to place foliage
*/
@Export
Double scale;
/**

View File

@ -2,6 +2,8 @@ package electrosphere.game.data.biome;
import java.util.List;
import org.graalvm.polyglot.HostAccess.Export;
/**
* Params for the surface generation algorithm
*/
@ -10,11 +12,13 @@ public class BiomeSurfaceGenerationParams {
/**
* The tag for the generation algorithm for generating the surface
*/
@Export
String surfaceGenTag;
/**
* The offset from baseline for height generation with this biome
*/
@Export
Float heightOffset;
/**
@ -25,6 +29,7 @@ public class BiomeSurfaceGenerationParams {
/**
* The list of foliage descriptions available to this biome type
*/
@Export
List<BiomeFoliageDescription> foliageDescriptions;
/**

View File

@ -153,7 +153,7 @@ public class ServerWorldData {
//test terrain gen
{
TestGenerationChunkGenerator chunkGen = new TestGenerationChunkGenerator(serverWorldData);
TestGenerationChunkGenerator chunkGen = new TestGenerationChunkGenerator(serverWorldData, TestGenerationChunkGenerator.DEFAULT_USE_JAVASCRIPT);
serverTerrainManager = new ServerTerrainManager(serverWorldData, 0, chunkGen);
serverTerrainManager.genTestData(chunkGen);
}

View File

@ -4,6 +4,7 @@ import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Engine;
@ -19,15 +20,18 @@ import electrosphere.client.ui.menu.script.ScriptMenuUtils;
import electrosphere.client.ui.menu.tutorial.TutorialMenus;
import electrosphere.engine.Globals;
import electrosphere.engine.Main;
import electrosphere.engine.signal.Signal;
import electrosphere.engine.signal.SignalServiceImpl;
import electrosphere.logger.LoggerInterface;
import electrosphere.script.translation.JSServerUtils;
import electrosphere.script.utils.ScriptMathInterface;
import electrosphere.util.FileUtils;
import electrosphere.util.math.SpatialMathUtils;
/**
* Interface for executing scripts in the game engine
*/
public class ScriptEngine {
public class ScriptEngine extends SignalServiceImpl {
//the default namespaces for
public static String SCRIPT_NAMESPACE_ENGINE = "engine"; //namespace for the engine functions exposed to the script engine
@ -67,6 +71,11 @@ public class ScriptEngine {
*/
boolean initialized = false;
/**
* Locks the script engine to enforce synchronization
*/
ReentrantLock lock = new ReentrantLock();
//The files that are loaded on init to bootstrap the script engine
static final String[] filesToLoadOnInit = new String[]{
//polyfills
@ -98,6 +107,7 @@ public class ScriptEngine {
{"menuUtils",ScriptMenuUtils.class},
{"voxelUtils",ScriptClientVoxelUtils.class},
{"levelEditorUtils",ScriptLevelEditorUtils.class},
{"math",ScriptMathInterface.class},
};
//singletons from the host that are provided to the javascript context
@ -107,10 +117,18 @@ public class ScriptEngine {
{"loggerScripts",LoggerInterface.loggerScripts},
};
/**
* Constructor
*/
public ScriptEngine(){
super("ScriptEngine");
}
/**
* Initializes the engine
*/
public void init(){
public void initScripts(){
//init datastructures
sourceMap = new HashMap<String,Source>();
initialized = false;
@ -320,6 +338,22 @@ public class ScriptEngine {
invokeMemberFunction("COMPILER", "run");
}
/**
* Recompiles the scripting engine
*/
private void recompile(Runnable onCompletion){
Thread recompileThread = new Thread(() -> {
Globals.scriptEngine.executeSynchronously(() -> {
this.initScripts();
});
if(onCompletion != null){
onCompletion.run();
}
});
recompileThread.setName("Recompile Script Engine");
recompileThread.start();
}
/**
* Initializes a scene script
* @param scenePath The scene's init script path
@ -398,6 +432,40 @@ public class ScriptEngine {
invokeFunction("require", filePath);
}
/**
* Invokes a function on a member of arbitrary depth on the engine object
* @param memberName The member name
* @param functionName The function's name
* @param className The class of the expected return value
* @param args The args to pass to the function
* @return The results of the invocation or null if there was no result
*/
public Value invokeEngineMember(String memberName, String functionName, Object ... args){
Value member = this.engineObject.getMember(memberName);
if(member == null){
throw new Error("Member is null!");
}
Value function = member.getMember(functionName);
if(function == null || !function.canExecute()){
throw new Error("Function is not executable! " + function);
}
Value executionResult = function.execute(args);
if(executionResult == null){
return null;
}
return executionResult;
}
/**
* Executes some code synchronously that requires script engine access
* @param function The function
*/
public void executeSynchronously(Runnable function){
lock.lock();
function.run();
lock.unlock();
}
/**
* Defines host members within javascript context
*/
@ -431,5 +499,23 @@ public class ScriptEngine {
return this.initialized;
}
@Override
public boolean handle(Signal signal){
boolean rVal = false;
switch(signal.getType()){
case SCRIPT_RECOMPILE: {
if(signal.getData() != null && signal.getData() instanceof Runnable){
this.recompile((Runnable)signal.getData());
} else {
this.recompile(null);
}
rVal = true;
} break;
default: {
} break;
}
return rVal;
}
}

View File

@ -0,0 +1,21 @@
package electrosphere.script.utils;
import org.graalvm.polyglot.HostAccess.Export;
/**
* Script access to specific math functions
*/
public class ScriptMathInterface {
/**
* Power function
* @param val1 The number
* @param val2 The exponent
* @return The power
*/
@Export
public static double pow(double val1, double val2){
return Math.pow(val1,val2);
}
}

View File

@ -421,9 +421,6 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
/**
* Evicts all loaded chunks.
* <p>
* Note: Does not save to disk.
* </p>
*/
public void evictAll(){
//TODO: improve to make have less performance impact

View File

@ -274,11 +274,13 @@ public class Realm {
*/
public void fireSignal(String signalName, Object ... args){
if(Globals.scriptEngine != null && Globals.scriptEngine.isInitialized()){
if(this.sceneInstanceId != NO_SCENE_INSTANCE){
Globals.scriptEngine.fireSignal(signalName, sceneInstanceId, args);
} else {
Globals.scriptEngine.fireSignal(signalName, ScriptEngine.GLOBAL_SCENE, args);
}
Globals.scriptEngine.executeSynchronously(() -> {
if(this.sceneInstanceId != NO_SCENE_INSTANCE){
Globals.scriptEngine.fireSignal(signalName, sceneInstanceId, args);
} else {
Globals.scriptEngine.fireSignal(signalName, ScriptEngine.GLOBAL_SCENE, args);
}
});
}
}

View File

@ -3,6 +3,8 @@ package electrosphere.server.terrain.generation;
import java.util.HashMap;
import java.util.Map;
import org.graalvm.polyglot.Value;
import electrosphere.engine.Globals;
import electrosphere.game.data.biome.BiomeData;
import electrosphere.game.data.biome.BiomeSurfaceGenerationParams;
@ -12,6 +14,7 @@ import electrosphere.server.terrain.generation.heightmap.HeightmapGenerator;
import electrosphere.server.terrain.generation.heightmap.HillsGen;
import electrosphere.server.terrain.generation.heightmap.PlainsGen;
import electrosphere.server.terrain.generation.interfaces.ChunkGenerator;
import electrosphere.server.terrain.generation.interfaces.GeneratedVoxel;
import electrosphere.server.terrain.manager.ServerTerrainChunk;
import electrosphere.server.terrain.models.TerrainModel;
@ -29,6 +32,21 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
* The default biome index
*/
public static final int DEFAULT_BIOME_INDEX = 1;
/**
* The width of the surface in number of voxels
*/
public static final int SURFACE_VOXEL_WIDTH = 2;
/**
* Tag for the test generator
*/
public static final String SCRIPT_GEN_TEST_TAG = "test";
/**
* Controls the default setting for whether to use javascript or not
*/
public static final boolean DEFAULT_USE_JAVASCRIPT = false;
/**
* The terreain model for the generator
@ -45,14 +63,20 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
*/
Map<String,HeightmapGenerator> tagGeneratorMap = new HashMap<String,HeightmapGenerator>();
/**
* Tracks whether to use javascript generation or not
*/
boolean useJavascript = false;
/**
* Constructor
*/
public TestGenerationChunkGenerator(ServerWorldData serverWorldData){
public TestGenerationChunkGenerator(ServerWorldData serverWorldData, boolean useJavascript){
this.serverWorldData = serverWorldData;
registerGenerator(new EmptySkyGen());
registerGenerator(new HillsGen());
registerGenerator(new PlainsGen());
this.useJavascript = useJavascript;
}
/**
@ -101,22 +125,61 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
}
}
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++){
int finalWorldX = worldX + ((x * strideValue) / ServerTerrainChunk.CHUNK_DIMENSION);
int finalWorldY = worldY + ((y * strideValue) / ServerTerrainChunk.CHUNK_DIMENSION);
int finalWorldZ = worldZ + ((z * strideValue) / ServerTerrainChunk.CHUNK_DIMENSION);
int finalChunkX = (x * strideValue) % ServerTerrainChunk.CHUNK_DIMENSION;
int finalChunkY = (y * strideValue) % ServerTerrainChunk.CHUNK_DIMENSION;
int finalChunkZ = (z * strideValue) % ServerTerrainChunk.CHUNK_DIMENSION;
GeneratedVoxel voxel = this.getVoxel(finalWorldX, finalWorldY, finalWorldZ, finalChunkX, finalChunkY, finalChunkZ, heightfield[x][z], this.terrainModel, surfaceBiome);
weights[x][y][z] = voxel.weight;
values[x][y][z] = voxel.type;
if(this.useJavascript){
Globals.scriptEngine.executeSynchronously(() -> {
Value getVoxelFunc = Globals.scriptEngine.invokeEngineMember("chunkGeneratorManager", "getVoxelFunction", SCRIPT_GEN_TEST_TAG);
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++){
int finalWorldX = worldX + ((x * strideValue) / ServerTerrainChunk.CHUNK_DIMENSION);
int finalWorldY = worldY + ((y * strideValue) / ServerTerrainChunk.CHUNK_DIMENSION);
int finalWorldZ = worldZ + ((z * strideValue) / ServerTerrainChunk.CHUNK_DIMENSION);
int finalChunkX = (x * strideValue) % ServerTerrainChunk.CHUNK_DIMENSION;
int finalChunkY = (y * strideValue) % ServerTerrainChunk.CHUNK_DIMENSION;
int finalChunkZ = (z * strideValue) % ServerTerrainChunk.CHUNK_DIMENSION;
Value result = getVoxelFunc.execute(
finalWorldX, finalWorldY, finalWorldZ,
finalChunkX, finalChunkY, finalChunkZ,
stride,
heightfield[x][z],
surfaceBiome
);
if(result != null){
weights[x][y][z] = result.getMember("weight").asFloat();
values[x][y][z] = result.getMember("type").asInt();
}
}
}
Globals.profiler.endCpuSample();
}
});
} else {
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++){
int finalWorldX = worldX + ((x * strideValue) / ServerTerrainChunk.CHUNK_DIMENSION);
int finalWorldY = worldY + ((y * strideValue) / ServerTerrainChunk.CHUNK_DIMENSION);
int finalWorldZ = worldZ + ((z * strideValue) / ServerTerrainChunk.CHUNK_DIMENSION);
int finalChunkX = (x * strideValue) % ServerTerrainChunk.CHUNK_DIMENSION;
int finalChunkY = (y * strideValue) % ServerTerrainChunk.CHUNK_DIMENSION;
int finalChunkZ = (z * strideValue) % ServerTerrainChunk.CHUNK_DIMENSION;
GeneratedVoxel voxel = this.getVoxel(
finalWorldX, finalWorldY, finalWorldZ,
finalChunkX, finalChunkY, finalChunkZ,
stride,
heightfield[x][z],
surfaceBiome
);
if(voxel != null){
weights[x][y][z] = voxel.weight;
values[x][y][z] = voxel.type;
}
}
}
Globals.profiler.endCpuSample();
}
Globals.profiler.endCpuSample();
}
} catch(Exception ex){
ex.printStackTrace();
@ -156,16 +219,16 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
* @param chunkX The chunk x pos
* @param chunkY The chunk y pos
* @param chunkZ The chunk z pos
* @param stride The stride of the data
* @param surfaceHeight The height of the surface at x,z
* @param terrainModel The terrain model
* @param surfaceBiome The surface biome of the chunk
* @return The value of the chunk
*/
private GeneratedVoxel getVoxel(
int worldX, int worldY, int worldZ,
int chunkX, int chunkY, int chunkZ,
int stride,
float surfaceHeight,
TerrainModel terrainModel,
BiomeData surfaceBiome
){
Globals.profiler.beginAggregateCpuSample("TestGenerationChunkGenerator.getChunkValue");
@ -179,24 +242,26 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
double realY = this.serverWorldData.convertVoxelToRealSpace(chunkY,worldY);
double realZ = this.serverWorldData.convertVoxelToRealSpace(chunkZ,worldZ);
double flooredSurfaceHeight = Math.floor(surfaceHeight);
double strideMultiplier = Math.pow(2,stride);
double heightDiff = realY - surfaceHeight;
double surfacePercent = TestGenerationChunkGenerator.getSurfaceWeight(surfaceHeight,realY,strideMultiplier);
Globals.profiler.endCpuSample();
if(realY < surfaceHeight - 1){
if(heightDiff < -strideMultiplier * SURFACE_VOXEL_WIDTH){
return getSubsurfaceVoxel(
worldX, worldY, worldZ,
chunkX, chunkY, chunkZ,
realX, realY, realZ,
surfaceHeight, flooredSurfaceHeight,
terrainModel,
surfacePercent,
surfaceHeight,
surfaceBiome
);
} else if(realY > flooredSurfaceHeight) {
} else if(heightDiff > 0) {
return getOverSurfaceVoxel(
worldX, worldY, worldZ,
chunkX, chunkY, chunkZ,
realX, realY, realZ,
surfaceHeight, flooredSurfaceHeight,
terrainModel,
realX, realY, realZ,-
surfacePercent,
surfaceHeight,
surfaceBiome
);
} else {
@ -204,8 +269,8 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
worldX, worldY, worldZ,
chunkX, chunkY, chunkZ,
realX, realY, realZ,
surfaceHeight, flooredSurfaceHeight,
terrainModel,
surfacePercent,
surfaceHeight,
surfaceBiome
);
}
@ -219,12 +284,12 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
int worldX, int worldY, int worldZ,
int chunkX, int chunkY, int chunkZ,
double realX, double realY, double realZ,
float surfaceHeight, double flooredSurfaceHeight,
TerrainModel terrainModel,
double surfacePercent,
float surfaceHeight,
BiomeData surfaceBiome
){
GeneratedVoxel voxel = new GeneratedVoxel();
voxel.weight = (float)(surfaceHeight - flooredSurfaceHeight) * 2 - 1;
voxel.weight = (float)surfacePercent * 2 - 1;
voxel.type = 2;
return voxel;
}
@ -237,8 +302,8 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
int worldX, int worldY, int worldZ,
int chunkX, int chunkY, int chunkZ,
double realX, double realY, double realZ,
float surfaceHeight, double flooredSurfaceHeight,
TerrainModel terrainModel,
double surfacePercent,
float surfaceHeight,
BiomeData surfaceBiome
){
GeneratedVoxel voxel = new GeneratedVoxel();
@ -260,8 +325,8 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
int worldX, int worldY, int worldZ,
int chunkX, int chunkY, int chunkZ,
double realX, double realY, double realZ,
float surfaceHeight, double flooredSurfaceHeight,
TerrainModel terrainModel,
double surfacePercent,
float surfaceHeight,
BiomeData surfaceBiome
){
GeneratedVoxel voxel = new GeneratedVoxel();
@ -271,17 +336,14 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
}
/**
* A voxel that was generated
* Calculates the weight of a voxel on the surface based on the surface height, the position of the voxel, and the stride multiplier
* @param surfaceHeight The surface height
* @param realPosY The position of the voxel
* @param strideMultiplier The stride multiplier
* @return The weight of the voxel
*/
static class GeneratedVoxel {
/**
* The type of the voxel
*/
int type;
/**
* The weight of the voxel
*/
float weight;
protected static double getSurfaceWeight(double surfaceHeight, double realPosY, double strideMultiplier){
return ((surfaceHeight - realPosY) / strideMultiplier);
}
}

View File

@ -0,0 +1,22 @@
package electrosphere.server.terrain.generation.interfaces;
import org.graalvm.polyglot.HostAccess.Export;
/**
* A voxel generated by a chunk generator
*/
public class GeneratedVoxel {
/**
* The type of the voxel
*/
@Export
public int type;
/**
* The weight of the voxel
*/
@Export
public float weight;
}

View File

@ -0,0 +1,37 @@
package electrosphere.server.terrain.generation.interfaces;
import electrosphere.game.data.biome.BiomeData;
/**
* A script-defined chunk generator
*/
public interface JSChunkGenerator {
/**
* Gets the tag for this generator
* @returns The tag
*/
public String getTag();
/**
* Retrieves the elevation for the world at a given x,z coordinate
* @param worldX The world x coordinate
* @param worldZ The world z coordinate
* @param chunkX The x coordinate of the chunk within the specified world coordinate
* @param chunkZ The z coordinate of the chunk within the specified world coordinate
* @returns The elevation at that specific position
*/
public float getElevation(int worldX, int worldZ, int chunkX, int chunkZ);
/**
* The function to get a voxel for a given position
*/
public GeneratedVoxel getVoxel(
int worldX, int worldY, int worldZ,
int chunkX, int chunkY, int chunkZ,
int stride,
double surfaceHeight,
BiomeData surfaceBiome
);
}

View File

@ -84,6 +84,9 @@ public class ServerChunkCache {
lock.acquireUninterruptibly();
cacheMapFullRes.clear();
cacheMapHalfRes.clear();
cacheMapQuarterRes.clear();
cacheMapEighthRes.clear();
cacheMapSixteenthRes.clear();
lock.release();
}

View File

@ -0,0 +1,22 @@
package electrosphere.server.terrain.generation;
import static org.junit.jupiter.api.Assertions.assertEquals;
import electrosphere.test.annotations.FastTest;
import electrosphere.test.annotations.UnitTest;
/**
* Unit tests for the test generation chunk generator
*/
public class TestGenerationChunkGeneratorTests {
@UnitTest
@FastTest
public void getSurfaceWeight_ValueTests(){
assertEquals(0.5,TestGenerationChunkGenerator.getSurfaceWeight(0.5, 0, 1));
assertEquals(0.1,TestGenerationChunkGenerator.getSurfaceWeight(0.1, 0, 1));
assertEquals(0.9,TestGenerationChunkGenerator.getSurfaceWeight(0.9, 0, 1));
assertEquals(0.95,TestGenerationChunkGenerator.getSurfaceWeight(1.9, 0, 2));
}
}