major script engine work
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good
This commit is contained in:
parent
64a3628316
commit
d0235a01d6
@ -60,6 +60,28 @@ let COMPILER = {
|
||||
*/
|
||||
currentDirectory : { },
|
||||
|
||||
|
||||
/**
|
||||
* Preloads a file from the host system's cache
|
||||
* @param {*} fileName The name of the file
|
||||
* @param {*} content The content of the file
|
||||
*/
|
||||
preloadFile: (fileName, content) => {
|
||||
COMPILER.fileMap[fileName] = COMPILER.createFile(fileName, content)
|
||||
COMPILER.fileMap[fileName].moduleContent = COMPILER.getModuleContent(content)
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the module content from generic file content
|
||||
* @param {*} content The file content
|
||||
* @returns The module content
|
||||
*/
|
||||
getModuleContent: (content) => {
|
||||
return "let exports = { }\n" +
|
||||
content + "\n" +
|
||||
"return exports"
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers a file with the compiler
|
||||
* @param {string} fileName The file's name
|
||||
@ -409,10 +431,7 @@ COMPILER.customCompilerHost = {
|
||||
writeFile: (fileName, data) => {
|
||||
loggerScripts.INFO("EMIT FILE " + fileName)
|
||||
//wrap in require logic
|
||||
let finalData =
|
||||
"let exports = { }\n" +
|
||||
data + "\n" +
|
||||
"return exports"
|
||||
let finalData = COMPILER.getModuleContent(data)
|
||||
|
||||
//create file
|
||||
COMPILER.createFile(fileName,finalData)
|
||||
|
||||
@ -1042,6 +1042,13 @@ Add concept of interaction definition in common entity type
|
||||
Fix bug with entity ray casting selecting player entity
|
||||
Fix crafting menu yoga appliation logic
|
||||
Workbench can open crafting menu
|
||||
Fix character customization panel layout
|
||||
|
||||
(11/15/2024)
|
||||
Script engine preloading
|
||||
Fix YogaUtils.refreshComponent breaking when passed null window
|
||||
Remove FontUtils .testcache creation
|
||||
File watching scripts source dir
|
||||
|
||||
|
||||
|
||||
@ -1073,7 +1080,6 @@ Implement gadgets
|
||||
- Recipe definitions
|
||||
- Reagent items
|
||||
|
||||
|
||||
Ability to fully reload game engine state without exiting client
|
||||
- Back out to main menu and load a new level without any values persisting
|
||||
- Receive a teleport packet from server and flush all game state before requesting state from server again
|
||||
@ -1083,7 +1089,6 @@ Bug Fixes
|
||||
- Fix not all grass tiles update when updating a nearby voxel (ie it doesn't go into negative coordinates to scan for foliage updates)
|
||||
- Fix typescript load error
|
||||
- Calculate bounding sphere for meshes by deforming vertices with bone default pose instead of no bone deform
|
||||
- Fix character creation menu
|
||||
- Fix threads not synchronizing when returning to main menu (rendering still running when player entity deleted, race condition)
|
||||
- Fix light cluster mapping for foliage shader
|
||||
- Fix foliage placement
|
||||
@ -1092,8 +1097,8 @@ Bug Fixes
|
||||
- Fix block tree preventing initiating an attack
|
||||
- Fix return to title menu synchronization bug
|
||||
- Fix particles not spawning in correct positions
|
||||
- Fix level creation menu button alignment
|
||||
- Fix spawn palette menu alignment
|
||||
- Fix flickering when applying yoga signal (may need to rethink arch here)
|
||||
- Fix virtual scrollables not working
|
||||
|
||||
Startup Performance
|
||||
- Cache loaded typescript
|
||||
@ -1127,16 +1132,6 @@ Code cleanup
|
||||
- Rename "ShaderProgram" to "VisualShader"
|
||||
- Have ComputeShader and VisualShader use same static method for uploading uniforms
|
||||
|
||||
Transvoxel implementation
|
||||
- Fix draw cell manager requesting far-out chunks
|
||||
- Properly update to higher LOD meshes as you get closer
|
||||
Client Terrain Entity Management (specifically creation/teardown for client)
|
||||
- Also queries for far out chunks to load far away terrain
|
||||
Server Terrain Management (specifically for collision)
|
||||
- Handles communicating far out LOD chunks to client as well
|
||||
Terrain Interface Positional Access Interface
|
||||
- Ability to get terrain at point for interactions with game world eg placing grass/water collision
|
||||
|
||||
Build system to allow specifying certain audio files to load as stereo
|
||||
|
||||
Rework how chunks are written to disk to make them more cache friendly
|
||||
@ -1186,8 +1181,6 @@ Revisit first attempt at instancing (its really laggy lol)
|
||||
Shader library system
|
||||
- Abiltiy to include the shader library in individual files (ie implement #include)
|
||||
|
||||
Break control handlers into separate files with new logic to transition between control handler states
|
||||
|
||||
Another pass at grass
|
||||
- Multiple foliage models in same cell
|
||||
|
||||
|
||||
@ -40,6 +40,9 @@ public class ItemActions {
|
||||
Vector3d eyePos = new Vector3d(CameraEntityUtils.getCameraEye(Globals.playerCamera));
|
||||
Vector3d centerPos = new Vector3d(CameraEntityUtils.getCameraCenter(Globals.playerCamera));
|
||||
Vector3d cursorPos = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(centerPos), new Vector3d(eyePos).mul(-1.0), CollisionEngine.DEFAULT_INTERACT_DISTANCE);
|
||||
if(cursorPos == null){
|
||||
cursorPos = new Vector3d(centerPos).add(new Vector3d(eyePos).normalize().mul(-CollisionEngine.DEFAULT_INTERACT_DISTANCE));
|
||||
}
|
||||
//tell the server we want the secondary hand item to START doing something
|
||||
Globals.clientConnection.queueOutgoingMessage(InventoryMessage.constructclientRequestPerformItemActionMessage(
|
||||
"handRight",
|
||||
@ -77,6 +80,9 @@ public class ItemActions {
|
||||
Vector3d eyePos = new Vector3d(CameraEntityUtils.getCameraEye(Globals.playerCamera));
|
||||
Vector3d centerPos = new Vector3d(CameraEntityUtils.getCameraCenter(Globals.playerCamera));
|
||||
Vector3d cursorPos = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(centerPos), new Vector3d(eyePos).mul(-1.0), CollisionEngine.DEFAULT_INTERACT_DISTANCE);
|
||||
if(cursorPos == null){
|
||||
cursorPos = new Vector3d(centerPos).add(new Vector3d(eyePos).normalize().mul(-CollisionEngine.DEFAULT_INTERACT_DISTANCE));
|
||||
}
|
||||
//tell the server we want the secondary hand item to STOP doing something
|
||||
Globals.clientConnection.queueOutgoingMessage(InventoryMessage.constructclientRequestPerformItemActionMessage(
|
||||
"handRight",
|
||||
@ -96,6 +102,9 @@ public class ItemActions {
|
||||
Vector3d eyePos = new Vector3d(CameraEntityUtils.getCameraEye(Globals.playerCamera));
|
||||
Vector3d centerPos = new Vector3d(CameraEntityUtils.getCameraCenter(Globals.playerCamera));
|
||||
Vector3d cursorPos = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(centerPos), new Vector3d(eyePos).mul(-1.0), CollisionEngine.DEFAULT_INTERACT_DISTANCE);
|
||||
if(cursorPos == null){
|
||||
cursorPos = new Vector3d(centerPos).add(new Vector3d(eyePos).normalize().mul(-CollisionEngine.DEFAULT_INTERACT_DISTANCE));
|
||||
}
|
||||
//tell the server we want the secondary hand item to STOP doing something
|
||||
Globals.clientConnection.queueOutgoingMessage(InventoryMessage.constructclientRequestPerformItemActionMessage(
|
||||
"handRight",
|
||||
@ -123,6 +132,9 @@ public class ItemActions {
|
||||
Vector3d eyePos = new Vector3d(CameraEntityUtils.getCameraEye(Globals.playerCamera));
|
||||
Vector3d centerPos = new Vector3d(CameraEntityUtils.getCameraCenter(Globals.playerCamera));
|
||||
Vector3d cursorPos = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(centerPos), new Vector3d(eyePos).mul(-1.0), CollisionEngine.DEFAULT_INTERACT_DISTANCE);
|
||||
if(cursorPos == null){
|
||||
cursorPos = new Vector3d(centerPos).add(new Vector3d(eyePos).normalize().mul(-CollisionEngine.DEFAULT_INTERACT_DISTANCE));
|
||||
}
|
||||
//tell the server we want the secondary hand item to START doing something
|
||||
Globals.clientConnection.queueOutgoingMessage(InventoryMessage.constructclientRequestPerformItemActionMessage(
|
||||
"handRight",
|
||||
@ -152,6 +164,9 @@ public class ItemActions {
|
||||
Vector3d eyePos = new Vector3d(CameraEntityUtils.getCameraEye(Globals.playerCamera));
|
||||
Vector3d centerPos = new Vector3d(CameraEntityUtils.getCameraCenter(Globals.playerCamera));
|
||||
Vector3d cursorPos = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(centerPos), new Vector3d(eyePos).mul(-1.0), CollisionEngine.DEFAULT_INTERACT_DISTANCE);
|
||||
if(cursorPos == null){
|
||||
cursorPos = new Vector3d(centerPos).add(new Vector3d(eyePos).normalize().mul(-CollisionEngine.DEFAULT_INTERACT_DISTANCE));
|
||||
}
|
||||
//tell the server we want the secondary hand item to STOP doing something
|
||||
Globals.clientConnection.queueOutgoingMessage(InventoryMessage.constructclientRequestPerformItemActionMessage(
|
||||
"handRight",
|
||||
|
||||
@ -14,9 +14,9 @@ public class ClientScriptUtils {
|
||||
* @param args The arguments provided alongside the signal
|
||||
*/
|
||||
public static void fireSignal(String signalName, Object ... args){
|
||||
Globals.scriptEngine.executeSynchronously(() -> {
|
||||
Globals.scriptEngine.getScriptContext().executeSynchronously(() -> {
|
||||
if(Globals.scriptEngine != null && Globals.scriptEngine.isInitialized()){
|
||||
Globals.scriptEngine.fireSignal(signalName, ScriptEngine.GLOBAL_SCENE, args);
|
||||
Globals.scriptEngine.getScriptContext().fireSignal(signalName, ScriptEngine.GLOBAL_SCENE, args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package electrosphere.client.ui.components;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.joml.Vector3f;
|
||||
@ -45,7 +46,7 @@ public class CharacterCustomizer {
|
||||
* @param race The race of the character
|
||||
* @return The panel component
|
||||
*/
|
||||
public static Element createCharacterCustomizerPanel(String race){
|
||||
public static Element createCharacterCustomizerPanel(String race, Consumer<CreatureTemplate> onConfirm){
|
||||
//figure out race data
|
||||
CreatureData selectedRaceType = Globals.gameConfigCurrent.getCreatureTypeLoader().getType(race);
|
||||
|
||||
@ -149,6 +150,7 @@ public class CharacterCustomizer {
|
||||
),
|
||||
Button.createButton("Create", () -> {
|
||||
Globals.clientConnection.queueOutgoingMessage(CharacterMessage.constructRequestCreateCharacterMessage(Utilities.stringify(template)));
|
||||
onConfirm.accept(template);
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@ -0,0 +1,176 @@
|
||||
package electrosphere.client.ui.components;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import electrosphere.client.ui.menu.WindowStrings;
|
||||
import electrosphere.client.ui.menu.YogaUtils;
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.game.data.voxel.VoxelData;
|
||||
import electrosphere.game.data.voxel.VoxelType;
|
||||
import electrosphere.renderer.ui.elements.Button;
|
||||
import electrosphere.renderer.ui.elements.Div;
|
||||
import electrosphere.renderer.ui.elements.ImagePanel;
|
||||
import electrosphere.renderer.ui.elements.Label;
|
||||
import electrosphere.renderer.ui.elements.TextInput;
|
||||
import electrosphere.renderer.ui.elements.VirtualScrollable;
|
||||
import electrosphere.renderer.ui.elementtypes.ClickableElement.ClickEventCallback;
|
||||
import electrosphere.renderer.ui.elementtypes.ContainerElement.YogaAlignment;
|
||||
import electrosphere.renderer.ui.elementtypes.ContainerElement.YogaFlexDirection;
|
||||
import electrosphere.renderer.ui.elementtypes.ContainerElement.YogaJustification;
|
||||
import electrosphere.renderer.ui.elementtypes.Element;
|
||||
import electrosphere.renderer.ui.elementtypes.HoverableElement.HoverEventCallback;
|
||||
import electrosphere.renderer.ui.elementtypes.KeyEventElement.KeyboardEventCallback;
|
||||
import electrosphere.renderer.ui.events.ClickEvent;
|
||||
import electrosphere.renderer.ui.events.HoverEvent;
|
||||
import electrosphere.renderer.ui.events.KeyboardEvent;
|
||||
|
||||
/**
|
||||
* A panel that provides a voxel selection
|
||||
*/
|
||||
public class VoxelSelectionPanel {
|
||||
|
||||
//text input
|
||||
static final int TEXT_INPUT_HEIGHT = 50;
|
||||
static final int TEXT_INPUT_WIDTH = 200;
|
||||
|
||||
//single voxel button
|
||||
static final int VOXEL_BUTTON_WIDTH = 90;
|
||||
static final int VOXEL_BUTTON_HEIGHT = 90;
|
||||
static final int VOXEL_BUTTON_TEXTURE_DIM = 70;
|
||||
static final int MARGIN_EACH_SIDE = 5;
|
||||
|
||||
//voxel selection
|
||||
static final int VOXEL_SCROLLABLE_WIDTH = VOXEL_BUTTON_WIDTH * 5;
|
||||
static final int VOXEL_SCROLLABLE_HEIGHT = VOXEL_BUTTON_HEIGHT * 5;
|
||||
|
||||
/**
|
||||
* The color of the select voxel type
|
||||
*/
|
||||
static final Vector3f ELEMENT_COLOR_SELECTED = new Vector3f(1,0,0);
|
||||
|
||||
|
||||
/**
|
||||
* Creates the level editor side panel top view
|
||||
* @return
|
||||
*/
|
||||
public static Div createVoxelTypeSelectionPanel(Consumer<VoxelType> onSelectType){
|
||||
//setup window
|
||||
Div rVal = Div.createDiv();
|
||||
rVal.setAlignContent(YogaAlignment.Center);
|
||||
rVal.setAlignItems(YogaAlignment.Center);
|
||||
rVal.setJustifyContent(YogaJustification.Center);
|
||||
rVal.setFlexDirection(YogaFlexDirection.Column);
|
||||
|
||||
//scrollable that contains all the voxel types
|
||||
VirtualScrollable scrollable = new VirtualScrollable(VOXEL_SCROLLABLE_WIDTH, VOXEL_SCROLLABLE_HEIGHT);
|
||||
scrollable.setFlexDirection(YogaFlexDirection.Column);
|
||||
scrollable.setAlignItems(YogaAlignment.Start);
|
||||
|
||||
//search input
|
||||
TextInput searchInput = TextInput.createTextInput();
|
||||
searchInput.setWidth(TEXT_INPUT_WIDTH);
|
||||
searchInput.setMinWidth(TEXT_INPUT_WIDTH);
|
||||
searchInput.setMinHeight(20);
|
||||
searchInput.setOnPress(new KeyboardEventCallback() {public boolean execute(KeyboardEvent event){
|
||||
boolean rVal = searchInput.defaultKeyHandling(event);
|
||||
VoxelSelectionPanel.fillInVoxelSelectors(scrollable, searchInput.getText(), onSelectType);
|
||||
return rVal;
|
||||
}});
|
||||
rVal.addChild(searchInput);
|
||||
|
||||
|
||||
//attach scrollable after search input for organzation purposes
|
||||
rVal.addChild(scrollable);
|
||||
|
||||
//final step
|
||||
VoxelSelectionPanel.fillInVoxelSelectors(scrollable, searchInput.getText(), onSelectType);
|
||||
|
||||
return rVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills in the voxels to display based on the contents of the search string
|
||||
* @param scrollable the scrollable to drop selection buttons in to
|
||||
* @param searchString the string to search based on
|
||||
*/
|
||||
static void fillInVoxelSelectors(VirtualScrollable scrollable, String searchString, Consumer<VoxelType> onSelectType){
|
||||
Element containingWindow = null;
|
||||
if(Globals.elementService.getWindow(WindowStrings.VOXEL_TYPE_SELECTION) != null){
|
||||
containingWindow = Globals.elementService.getWindow(WindowStrings.VOXEL_TYPE_SELECTION);
|
||||
} else if(Globals.elementService.getWindow(WindowStrings.WINDOW_MENU_MAIN) != null){
|
||||
containingWindow = Globals.elementService.getWindow(WindowStrings.WINDOW_MENU_MAIN);
|
||||
}
|
||||
YogaUtils.refreshComponent(containingWindow, () -> {
|
||||
scrollable.clearChildren();
|
||||
VoxelData voxelData = Globals.gameConfigCurrent.getVoxelData();
|
||||
List<VoxelType> matchingVoxels = voxelData.getTypes().stream().filter((type)->type.getName().toLowerCase().contains(searchString.toLowerCase())).toList();
|
||||
Div currentRow = null;
|
||||
int incrementer = 0;
|
||||
//generate voxel buttons
|
||||
for(VoxelType type : matchingVoxels){
|
||||
if(incrementer % 4 == 0){
|
||||
currentRow = Div.createRow();
|
||||
currentRow.setJustifyContent(YogaJustification.Evenly);
|
||||
scrollable.addChild(currentRow);
|
||||
}
|
||||
Div containerDiv = Div.createDiv();
|
||||
containerDiv.setMinWidthPercent(25.0f);
|
||||
currentRow.addChild(containerDiv);
|
||||
|
||||
Button newButton = new Button();
|
||||
newButton.setAlignItems(YogaAlignment.Center);
|
||||
//dimensions
|
||||
newButton.setMinWidth(VOXEL_BUTTON_WIDTH);
|
||||
newButton.setMinHeight(VOXEL_BUTTON_HEIGHT);
|
||||
//margin
|
||||
newButton.setMarginBottom(MARGIN_EACH_SIDE);
|
||||
newButton.setMarginLeft(MARGIN_EACH_SIDE);
|
||||
newButton.setMarginRight(MARGIN_EACH_SIDE);
|
||||
newButton.setMarginTop(MARGIN_EACH_SIDE);
|
||||
//set color if this is the selected voxel type
|
||||
if(type == Globals.clientSelectedVoxelType){
|
||||
newButton.setColor(ELEMENT_COLOR_SELECTED);
|
||||
}
|
||||
//label
|
||||
Label voxelLabel = Label.createLabel(type.getName());
|
||||
//icon/model
|
||||
ImagePanel texturePanel = ImagePanel.createImagePanel(type.getTexture());
|
||||
if(type.getTexture() != null){
|
||||
Globals.assetManager.addTexturePathtoQueue(type.getTexture());
|
||||
}
|
||||
texturePanel.setWidth(VOXEL_BUTTON_TEXTURE_DIM);
|
||||
texturePanel.setHeight(VOXEL_BUTTON_TEXTURE_DIM);
|
||||
texturePanel.setMarginBottom(MARGIN_EACH_SIDE);
|
||||
texturePanel.setMarginLeft(MARGIN_EACH_SIDE);
|
||||
texturePanel.setMarginRight(MARGIN_EACH_SIDE);
|
||||
texturePanel.setMarginTop(MARGIN_EACH_SIDE);
|
||||
newButton.addChild(texturePanel);
|
||||
texturePanel.setAlignSelf(YogaAlignment.Center);
|
||||
//causes the texture panel to also behave as if the button was hovered
|
||||
texturePanel.setOnHoverCallback(new HoverEventCallback() {public boolean execute(HoverEvent event) {
|
||||
return newButton.handleEvent(event);
|
||||
}});
|
||||
//button handling
|
||||
newButton.addChild(voxelLabel);
|
||||
newButton.setOnClick(new ClickEventCallback() {public boolean execute(ClickEvent event){
|
||||
//set voxel type to this type
|
||||
onSelectType.accept(type);
|
||||
Globals.clientSelectedVoxelType = type;
|
||||
VoxelSelectionPanel.fillInVoxelSelectors(scrollable, searchString, onSelectType);
|
||||
return false;
|
||||
}});
|
||||
containerDiv.addChild(newButton);
|
||||
incrementer++;
|
||||
}
|
||||
for(int i = incrementer; i % 4 != 0; i++){
|
||||
Div spacerDiv = Div.createDiv();
|
||||
spacerDiv.setMinWidthPercent(25.0f);
|
||||
currentRow.addChild(spacerDiv);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
24
src/main/java/electrosphere/client/ui/menu/YogaUtils.java
Normal file
24
src/main/java/electrosphere/client/ui/menu/YogaUtils.java
Normal file
@ -0,0 +1,24 @@
|
||||
package electrosphere.client.ui.menu;
|
||||
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.engine.signal.Signal.SignalType;
|
||||
import electrosphere.renderer.ui.elementtypes.Element;
|
||||
|
||||
/**
|
||||
* Utilities for working with yoga
|
||||
*/
|
||||
public class YogaUtils {
|
||||
|
||||
/**
|
||||
* Refreshes a component
|
||||
* @param containingWindow The window containing the component
|
||||
* @param render The function that renders the component's content
|
||||
*/
|
||||
public static void refreshComponent(Element containingWindow, Runnable render){
|
||||
Globals.signalSystem.post(SignalType.UI_MODIFICATION,render);
|
||||
if(containingWindow != null){
|
||||
Globals.signalSystem.post(SignalType.YOGA_APPLY, containingWindow);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,32 +1,19 @@
|
||||
package electrosphere.client.ui.menu.ingame;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import electrosphere.client.ui.components.SpawnSelectionPanel;
|
||||
import electrosphere.client.ui.components.VoxelSelectionPanel;
|
||||
import electrosphere.client.ui.menu.WindowStrings;
|
||||
import electrosphere.client.ui.menu.WindowUtils;
|
||||
import electrosphere.controls.ControlHandler.ControlsState;
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.engine.signal.Signal.SignalType;
|
||||
import electrosphere.game.data.common.CommonEntityType;
|
||||
import electrosphere.game.data.voxel.VoxelData;
|
||||
import electrosphere.game.data.voxel.VoxelType;
|
||||
import electrosphere.renderer.ui.elements.Button;
|
||||
import electrosphere.renderer.ui.elements.Div;
|
||||
import electrosphere.renderer.ui.elements.ImagePanel;
|
||||
import electrosphere.renderer.ui.elements.Label;
|
||||
import electrosphere.renderer.ui.elements.TextInput;
|
||||
import electrosphere.renderer.ui.elements.VirtualScrollable;
|
||||
import electrosphere.renderer.ui.elements.Window;
|
||||
import electrosphere.renderer.ui.elementtypes.ClickableElement.ClickEventCallback;
|
||||
import electrosphere.renderer.ui.elementtypes.ContainerElement.YogaAlignment;
|
||||
import electrosphere.renderer.ui.elementtypes.ContainerElement.YogaFlexDirection;
|
||||
import electrosphere.renderer.ui.elementtypes.ContainerElement.YogaJustification;
|
||||
import electrosphere.renderer.ui.elementtypes.KeyEventElement.KeyboardEventCallback;
|
||||
import electrosphere.renderer.ui.elementtypes.NavigableElement.NavigationEventCallback;
|
||||
import electrosphere.renderer.ui.events.ClickEvent;
|
||||
import electrosphere.renderer.ui.events.KeyboardEvent;
|
||||
import electrosphere.renderer.ui.events.NavigationEvent;
|
||||
|
||||
/**
|
||||
@ -34,23 +21,8 @@ import electrosphere.renderer.ui.events.NavigationEvent;
|
||||
*/
|
||||
public class MenuGeneratorsTerrainEditing {
|
||||
|
||||
static Window terrainEditingSidePanelWindow;
|
||||
static Window entitySelectionWindow;
|
||||
|
||||
//text input
|
||||
static final int TEXT_INPUT_HEIGHT = 50;
|
||||
static final int TEXT_INPUT_WIDTH = 200;
|
||||
|
||||
//single voxel button
|
||||
static final int VOXEL_BUTTON_WIDTH = 90;
|
||||
static final int VOXEL_BUTTON_HEIGHT = 90;
|
||||
static final int VOXEL_BUTTON_TEXTURE_DIM = 70;
|
||||
static final int MARGIN_EACH_SIDE = 5;
|
||||
|
||||
//voxel selection
|
||||
static final int VOXEL_SCROLLABLE_WIDTH = VOXEL_BUTTON_WIDTH * 5;
|
||||
static final int VOXEL_SCROLLABLE_HEIGHT = VOXEL_BUTTON_HEIGHT * 5;
|
||||
|
||||
//width of the side panel
|
||||
static final int WINDOW_WIDTH = 550;
|
||||
static final int WINDOW_HEIGHT = 550;
|
||||
@ -61,7 +33,7 @@ public class MenuGeneratorsTerrainEditing {
|
||||
*/
|
||||
public static Window createVoxelTypeSelectionPanel(){
|
||||
//setup window
|
||||
terrainEditingSidePanelWindow = Window.create(Globals.renderingEngine.getOpenGLState(), 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, true);
|
||||
Window terrainEditingSidePanelWindow = Window.create(Globals.renderingEngine.getOpenGLState(), 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, true);
|
||||
terrainEditingSidePanelWindow.setParentAlignContent(YogaAlignment.Center);
|
||||
terrainEditingSidePanelWindow.setParentJustifyContent(YogaJustification.Center);
|
||||
terrainEditingSidePanelWindow.setParentAlignItem(YogaAlignment.Center);
|
||||
@ -79,120 +51,15 @@ public class MenuGeneratorsTerrainEditing {
|
||||
}});
|
||||
|
||||
//attach scrollable after search input for organzation purposes
|
||||
terrainEditingSidePanelWindow.addChild(createVoxelTypeSelectionPanel((VoxelType type) -> {
|
||||
terrainEditingSidePanelWindow.addChild(VoxelSelectionPanel.createVoxelTypeSelectionPanel((VoxelType type) -> {
|
||||
Globals.clientSelectedVoxelType = type;
|
||||
}));
|
||||
}));
|
||||
|
||||
Globals.signalSystem.post(SignalType.YOGA_APPLY,terrainEditingSidePanelWindow);
|
||||
|
||||
return terrainEditingSidePanelWindow;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates the level editor side panel top view
|
||||
* @return
|
||||
*/
|
||||
public static Div createVoxelTypeSelectionPanel(Consumer<VoxelType> onSelectType){
|
||||
//setup window
|
||||
Div rVal = Div.createDiv();
|
||||
rVal.setAlignContent(YogaAlignment.Center);
|
||||
rVal.setAlignItems(YogaAlignment.Center);
|
||||
rVal.setJustifyContent(YogaJustification.Center);
|
||||
rVal.setFlexDirection(YogaFlexDirection.Column);
|
||||
|
||||
//scrollable that contains all the voxel types
|
||||
VirtualScrollable scrollable = new VirtualScrollable(VOXEL_SCROLLABLE_WIDTH, VOXEL_SCROLLABLE_HEIGHT);
|
||||
scrollable.setFlexDirection(YogaFlexDirection.Column);
|
||||
scrollable.setAlignItems(YogaAlignment.Start);
|
||||
|
||||
//search input
|
||||
TextInput searchInput = TextInput.createTextInput();
|
||||
searchInput.setWidth(TEXT_INPUT_WIDTH);
|
||||
searchInput.setMinWidth(TEXT_INPUT_WIDTH);
|
||||
searchInput.setMinHeight(20);
|
||||
searchInput.setOnPress(new KeyboardEventCallback() {public boolean execute(KeyboardEvent event){
|
||||
boolean rVal = searchInput.defaultKeyHandling(event);
|
||||
fillInVoxelSelectors(scrollable, searchInput.getText(), onSelectType);
|
||||
return rVal;
|
||||
}});
|
||||
rVal.addChild(searchInput);
|
||||
|
||||
|
||||
//attach scrollable after search input for organzation purposes
|
||||
rVal.addChild(scrollable);
|
||||
|
||||
//final step
|
||||
fillInVoxelSelectors(scrollable, searchInput.getText(), onSelectType);
|
||||
|
||||
return rVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills in the voxels to display based on the contents of the search string
|
||||
* @param scrollable the scrollable to drop selection buttons in to
|
||||
* @param searchString the string to search based on
|
||||
*/
|
||||
static void fillInVoxelSelectors(VirtualScrollable scrollable, String searchString, Consumer<VoxelType> onSelectType){
|
||||
scrollable.clearChildren();
|
||||
VoxelData voxelData = Globals.gameConfigCurrent.getVoxelData();
|
||||
List<VoxelType> matchingVoxels = voxelData.getTypes().stream().filter((type)->type.getName().toLowerCase().contains(searchString.toLowerCase())).toList();
|
||||
Div currentRow = null;
|
||||
int incrementer = 0;
|
||||
//generate voxel buttons
|
||||
for(VoxelType type : matchingVoxels){
|
||||
if(incrementer % 4 == 0){
|
||||
currentRow = Div.createRow();
|
||||
currentRow.setJustifyContent(YogaJustification.Evenly);
|
||||
scrollable.addChild(currentRow);
|
||||
}
|
||||
Div containerDiv = Div.createDiv();
|
||||
containerDiv.setMinWidthPercent(25.0f);
|
||||
currentRow.addChild(containerDiv);
|
||||
|
||||
Button newButton = new Button();
|
||||
newButton.setAlignItems(YogaAlignment.Center);
|
||||
//dimensions
|
||||
newButton.setMinWidth(VOXEL_BUTTON_WIDTH);
|
||||
newButton.setMinHeight(VOXEL_BUTTON_HEIGHT);
|
||||
//margin
|
||||
newButton.setMarginBottom(MARGIN_EACH_SIDE);
|
||||
newButton.setMarginLeft(MARGIN_EACH_SIDE);
|
||||
newButton.setMarginRight(MARGIN_EACH_SIDE);
|
||||
newButton.setMarginTop(MARGIN_EACH_SIDE);
|
||||
//label
|
||||
Label voxelLabel = Label.createLabel(type.getName());
|
||||
//icon/model
|
||||
ImagePanel texturePanel = ImagePanel.createImagePanel(type.getTexture());
|
||||
if(type.getTexture() != null){
|
||||
Globals.assetManager.addTexturePathtoQueue(type.getTexture());
|
||||
}
|
||||
texturePanel.setWidth(VOXEL_BUTTON_TEXTURE_DIM);
|
||||
texturePanel.setHeight(VOXEL_BUTTON_TEXTURE_DIM);
|
||||
texturePanel.setMarginBottom(MARGIN_EACH_SIDE);
|
||||
texturePanel.setMarginLeft(MARGIN_EACH_SIDE);
|
||||
texturePanel.setMarginRight(MARGIN_EACH_SIDE);
|
||||
texturePanel.setMarginTop(MARGIN_EACH_SIDE);
|
||||
newButton.addChild(texturePanel);
|
||||
texturePanel.setAlignSelf(YogaAlignment.Center);
|
||||
//button handling
|
||||
newButton.addChild(voxelLabel);
|
||||
newButton.setOnClick(new ClickEventCallback() {public boolean execute(ClickEvent event){
|
||||
//set voxel type to this type
|
||||
onSelectType.accept(type);
|
||||
Globals.clientSelectedVoxelType = type;
|
||||
return false;
|
||||
}});
|
||||
containerDiv.addChild(newButton);
|
||||
incrementer++;
|
||||
}
|
||||
for(int i = incrementer; i % 4 != 0; i++){
|
||||
Div spacerDiv = Div.createDiv();
|
||||
spacerDiv.setMinWidthPercent(25.0f);
|
||||
currentRow.addChild(spacerDiv);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the entity type selection window
|
||||
* @return
|
||||
|
||||
@ -0,0 +1,72 @@
|
||||
package electrosphere.client.ui.menu.mainmenu;
|
||||
|
||||
import electrosphere.client.ui.components.CharacterCustomizer;
|
||||
import electrosphere.client.ui.menu.WindowUtils;
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.entity.types.creature.CreatureTemplate;
|
||||
import electrosphere.renderer.ui.elements.Button;
|
||||
import electrosphere.renderer.ui.elements.FormElement;
|
||||
import electrosphere.renderer.ui.elements.StringCarousel;
|
||||
import electrosphere.renderer.ui.elementtypes.Element;
|
||||
import electrosphere.renderer.ui.events.ValueChangeEvent;
|
||||
|
||||
/**
|
||||
* The menu for character creation
|
||||
*/
|
||||
public class MenuCharacterCreation {
|
||||
|
||||
/**
|
||||
* The currently selected race
|
||||
*/
|
||||
static String selectedRace = "";
|
||||
|
||||
/**
|
||||
* Creates the menu for selecting/creating a character
|
||||
* @return The menu element
|
||||
*/
|
||||
public static Element createCharacterSelectionWindow(){
|
||||
FormElement rVal = new FormElement();
|
||||
|
||||
//button (create)
|
||||
rVal.addChild(Button.createButton("Create Character", () -> {
|
||||
WindowUtils.replaceMainMenuContents(MenuCharacterCreation.createRaceSelectionMenu());
|
||||
}));
|
||||
|
||||
return rVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the menu for selecting a character's race
|
||||
* @return The menu element
|
||||
*/
|
||||
public static Element createRaceSelectionMenu(){
|
||||
FormElement rVal = new FormElement();
|
||||
|
||||
//select race
|
||||
rVal.addChild(StringCarousel.create(Globals.gameConfigCurrent.getCreatureTypeLoader().getPlayableRaces(), (ValueChangeEvent event) -> {
|
||||
selectedRace = event.getAsString();
|
||||
}));
|
||||
|
||||
selectedRace = Globals.gameConfigCurrent.getCreatureTypeLoader().getPlayableRaces().get(0);
|
||||
|
||||
//button (create)
|
||||
rVal.addChild(Button.createButton("Confirm", () -> {
|
||||
WindowUtils.replaceMainMenuContents(MenuCharacterCreation.createCharacterCustomizationMenu());
|
||||
}));
|
||||
|
||||
return rVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the menu for customizing the character's appearance
|
||||
* @return The menu element
|
||||
*/
|
||||
public static Element createCharacterCustomizationMenu(){
|
||||
FormElement rVal = new FormElement();
|
||||
|
||||
rVal.addChild(CharacterCustomizer.createCharacterCustomizerPanel(selectedRace, (CreatureTemplate template) -> {}));
|
||||
|
||||
return rVal;
|
||||
}
|
||||
|
||||
}
|
||||
@ -3,8 +3,8 @@ package electrosphere.client.ui.menu.mainmenu;
|
||||
import java.util.List;
|
||||
|
||||
import electrosphere.client.ui.components.InputMacros;
|
||||
import electrosphere.client.ui.components.VoxelSelectionPanel;
|
||||
import electrosphere.client.ui.menu.WindowUtils;
|
||||
import electrosphere.client.ui.menu.ingame.MenuGeneratorsTerrainEditing;
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.engine.assetmanager.AssetDataStrings;
|
||||
import electrosphere.engine.loadingthreads.LoadingThread;
|
||||
@ -194,7 +194,7 @@ public class MenuGeneratorsLevelEditor {
|
||||
}, DEFAULT_GRID_SIZE / (float)GriddedDataCellManager.MAX_GRID_SIZE)
|
||||
);
|
||||
sceneFile.getRealmDescriptor().setGriddedRealmSize(DEFAULT_GRID_SIZE);
|
||||
griddedRealmControls.addChild(MenuGeneratorsTerrainEditing.createVoxelTypeSelectionPanel((VoxelType type) -> {
|
||||
griddedRealmControls.addChild(VoxelSelectionPanel.createVoxelTypeSelectionPanel((VoxelType type) -> {
|
||||
sceneFile.getRealmDescriptor().setBaseVoxel(type.getId());
|
||||
}));
|
||||
}
|
||||
@ -205,11 +205,13 @@ public class MenuGeneratorsLevelEditor {
|
||||
//
|
||||
//Create level button
|
||||
//
|
||||
rVal.addChild(Button.createButton("Create Level", () -> {
|
||||
Button createButton = Button.createButton("Create Level", () -> {
|
||||
//launch level editor
|
||||
LoadingThread loadingThread = new LoadingThread(LoadingThreadType.LEVEL_EDITOR, inFlightLevel);
|
||||
Globals.threadManager.start(loadingThread);
|
||||
}).setOnClickAudio(AssetDataStrings.UI_TONE_BUTTON_TITLE));
|
||||
}).setOnClickAudio(AssetDataStrings.UI_TONE_BUTTON_TITLE);
|
||||
createButton.setAlignSelf(YogaAlignment.Center);
|
||||
rVal.addChild(createButton);
|
||||
|
||||
|
||||
return rVal;
|
||||
|
||||
@ -4,6 +4,7 @@ import electrosphere.client.ui.components.CharacterCustomizer;
|
||||
import electrosphere.client.ui.menu.MenuGenerators;
|
||||
import electrosphere.client.ui.menu.WindowUtils;
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.entity.types.creature.CreatureTemplate;
|
||||
import electrosphere.renderer.ui.elements.Button;
|
||||
import electrosphere.renderer.ui.elements.FormElement;
|
||||
import electrosphere.renderer.ui.elements.Label;
|
||||
@ -52,7 +53,7 @@ public class MenuGeneratorsMultiplayer {
|
||||
createButton.addChild(createLabel);
|
||||
rVal.addChild(createButton);
|
||||
createButton.setOnClick(new ClickableElement.ClickEventCallback(){public boolean execute(ClickEvent event){
|
||||
WindowUtils.replaceMainMenuContents(CharacterCustomizer.createCharacterCustomizerPanel(selectedRace));
|
||||
WindowUtils.replaceMainMenuContents(CharacterCustomizer.createCharacterCustomizerPanel(selectedRace, (CreatureTemplate template) -> {}));
|
||||
return false;
|
||||
}});
|
||||
|
||||
|
||||
@ -13,8 +13,8 @@ import electrosphere.client.ui.components.EquipmentInventoryPanel;
|
||||
import electrosphere.client.ui.components.InputMacros;
|
||||
import electrosphere.client.ui.components.NaturalInventoryPanel;
|
||||
import electrosphere.client.ui.components.SpawnSelectionPanel;
|
||||
import electrosphere.client.ui.components.VoxelSelectionPanel;
|
||||
import electrosphere.client.ui.menu.WindowUtils;
|
||||
import electrosphere.client.ui.menu.ingame.MenuGeneratorsTerrainEditing;
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.engine.assetmanager.AssetDataStrings;
|
||||
import electrosphere.entity.Entity;
|
||||
@ -22,6 +22,7 @@ import electrosphere.entity.EntityCreationUtils;
|
||||
import electrosphere.entity.state.inventory.InventoryUtils;
|
||||
import electrosphere.entity.state.inventory.RelationalInventoryState;
|
||||
import electrosphere.entity.state.inventory.UnrelationalInventoryState;
|
||||
import electrosphere.entity.types.creature.CreatureTemplate;
|
||||
import electrosphere.game.data.common.CommonEntityType;
|
||||
import electrosphere.game.data.creature.type.equip.EquipPoint;
|
||||
import electrosphere.game.data.voxel.VoxelType;
|
||||
@ -123,7 +124,7 @@ public class MenuGeneratorsUITesting {
|
||||
formEl.addChild(Button.createButton("test", () -> {}));
|
||||
} break;
|
||||
case "CharacterCustomizer": {
|
||||
formEl.addChild(CharacterCustomizer.createCharacterCustomizerPanel("human"));
|
||||
formEl.addChild(CharacterCustomizer.createCharacterCustomizerPanel("human", (CreatureTemplate template) ->{}));
|
||||
} break;
|
||||
case "NaturalInventoryPanel": {
|
||||
Entity ent = EntityCreationUtils.TEST_createEntity();
|
||||
@ -144,7 +145,7 @@ public class MenuGeneratorsUITesting {
|
||||
formEl.addChild(EquipmentInventoryPanel.createEquipmentInventoryPanel(ent));
|
||||
} break;
|
||||
case "VoxelPicker": {
|
||||
formEl.addChild(MenuGeneratorsTerrainEditing.createVoxelTypeSelectionPanel((VoxelType voxelType) -> {
|
||||
formEl.addChild(VoxelSelectionPanel.createVoxelTypeSelectionPanel((VoxelType voxelType) -> {
|
||||
System.out.println(voxelType.getName());
|
||||
}));
|
||||
} break;
|
||||
|
||||
@ -300,6 +300,13 @@ public class Main {
|
||||
///
|
||||
Globals.scriptEngine.handleAllSignals();
|
||||
|
||||
///
|
||||
/// S C R I P T E N G I N E
|
||||
///
|
||||
if(Globals.RUN_SCRIPTS && Globals.scriptEngine != null){
|
||||
Globals.scriptEngine.scanScriptDir();
|
||||
}
|
||||
|
||||
|
||||
///
|
||||
///
|
||||
|
||||
@ -14,7 +14,7 @@ import electrosphere.client.terrain.cells.ClientDrawCellManager;
|
||||
import electrosphere.client.ui.menu.MenuGenerators;
|
||||
import electrosphere.client.ui.menu.WindowStrings;
|
||||
import electrosphere.client.ui.menu.WindowUtils;
|
||||
import electrosphere.client.ui.menu.mainmenu.MenuGeneratorsMultiplayer;
|
||||
import electrosphere.client.ui.menu.mainmenu.MenuCharacterCreation;
|
||||
import electrosphere.controls.ControlHandler;
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.engine.assetmanager.AssetDataStrings;
|
||||
@ -45,6 +45,10 @@ public class ClientLoading {
|
||||
static final int DRAW_CELL_EXPECTED_MINIMUM_FRAMES_TO_INIT = 10;
|
||||
|
||||
|
||||
/**
|
||||
* Loads the race data from the server
|
||||
* @param params no params
|
||||
*/
|
||||
protected static void loadCharacterServer(Object[] params){
|
||||
WindowUtils.recursiveSetVisible(Globals.elementService.getWindow(WindowStrings.WINDOW_MENU_MAIN), false);
|
||||
WindowUtils.replaceMainMenuContents(MenuGenerators.createEmptyMainMenu());
|
||||
@ -63,7 +67,7 @@ public class ClientLoading {
|
||||
//once we have them, bring up the character creation interface
|
||||
//init character creation window
|
||||
//eventually should replace with at ui to select an already created character or create a new one
|
||||
WindowUtils.replaceMainMenuContents(MenuGeneratorsMultiplayer.createMultiplayerCharacterCreationWindow());
|
||||
WindowUtils.replaceMainMenuContents(MenuCharacterCreation.createCharacterSelectionWindow());
|
||||
//make loading dialog disappear
|
||||
WindowUtils.recursiveSetVisible(Globals.elementService.getWindow(WindowStrings.WINDOW_LOADING), false);
|
||||
//make character creation window visible
|
||||
|
||||
@ -125,8 +125,8 @@ public class SceneLoader {
|
||||
//load scripts
|
||||
if(!isLevelEditor && file.getInitScriptPath() != null){
|
||||
Realm finalRealm = realm;
|
||||
Globals.scriptEngine.executeSynchronously(() -> {
|
||||
int sceneInstanceId = Globals.scriptEngine.initScene(file.getInitScriptPath());
|
||||
Globals.scriptEngine.getScriptContext().executeSynchronously(() -> {
|
||||
int sceneInstanceId = Globals.scriptEngine.getScriptContext().initScene(file.getInitScriptPath());
|
||||
finalRealm.setSceneInstanceId(sceneInstanceId);
|
||||
});
|
||||
}
|
||||
|
||||
@ -22,9 +22,17 @@ import electrosphere.renderer.ui.events.FocusEvent;
|
||||
import electrosphere.renderer.ui.events.HoverEvent;
|
||||
import electrosphere.renderer.ui.events.MouseEvent;
|
||||
|
||||
/**
|
||||
* A button element
|
||||
*/
|
||||
public class Button extends StandardContainerElement implements DrawableElement, FocusableElement, ClickableElement, HoverableElement {
|
||||
|
||||
static Vector3f color = new Vector3f(1.0f);
|
||||
static Vector3f COLOR_DEFAULT = new Vector3f(1.0f);
|
||||
|
||||
/**
|
||||
* The color of the backing element
|
||||
*/
|
||||
Vector3f color = new Vector3f(COLOR_DEFAULT);
|
||||
|
||||
Vector3f boxPosition = new Vector3f();
|
||||
Vector3f boxDimensions = new Vector3f();
|
||||
@ -302,5 +310,13 @@ public class Button extends StandardContainerElement implements DrawableElement,
|
||||
this.audioPathOnClick = audioPath;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the background color of this element
|
||||
* @param color The color
|
||||
*/
|
||||
public void setColor(Vector3f color){
|
||||
this.color.set(color);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -5,18 +5,12 @@ import electrosphere.renderer.model.Material;
|
||||
import electrosphere.renderer.texture.Texture;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Utilities for loading fonts
|
||||
@ -140,15 +134,15 @@ public class FontUtils {
|
||||
// AffineTransformOp operation = new AffineTransformOp(transform, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
|
||||
// image = operation.filter(image, null);
|
||||
|
||||
try {
|
||||
File imgFile = new File("./.testcache/testimg.png");
|
||||
imgFile.delete();
|
||||
imgFile.getParentFile().mkdirs();
|
||||
ImageIO.write(image, "png", Files.newOutputStream(imgFile.toPath()));
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
// try {
|
||||
// File imgFile = new File("./.testcache/testimg.png");
|
||||
// imgFile.delete();
|
||||
// imgFile.getParentFile().mkdirs();
|
||||
// ImageIO.write(image, "png", Files.newOutputStream(imgFile.toPath()));
|
||||
// } catch (IOException e) {
|
||||
// // TODO Auto-generated catch block
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
|
||||
|
||||
//create material with new font image
|
||||
|
||||
409
src/main/java/electrosphere/script/ScriptContext.java
Normal file
409
src/main/java/electrosphere/script/ScriptContext.java
Normal file
@ -0,0 +1,409 @@
|
||||
package electrosphere.script;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.graalvm.polyglot.Context;
|
||||
import org.graalvm.polyglot.Engine;
|
||||
import org.graalvm.polyglot.HostAccess;
|
||||
import org.graalvm.polyglot.PolyglotException;
|
||||
import org.graalvm.polyglot.Source;
|
||||
import org.graalvm.polyglot.Source.Builder;
|
||||
import org.graalvm.polyglot.Value;
|
||||
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.logger.LoggerInterface;
|
||||
import electrosphere.util.FileUtils;
|
||||
|
||||
/**
|
||||
* A context for executing scripts
|
||||
*/
|
||||
public class ScriptContext {
|
||||
|
||||
//the default namespaces for
|
||||
public static String SCRIPT_NAMESPACE_ENGINE = "engine"; //namespace for the engine functions exposed to the script engine
|
||||
public static String SCRIPT_NAMESPACE_SCRIPT = "script"; //namespace for the core typescript functionsw
|
||||
public static String SCRIPT_NAMESPACE_SCENE = "scene"; //namespace for the current scene
|
||||
|
||||
//the graal context
|
||||
Context context;
|
||||
|
||||
//used to build source objects
|
||||
Builder builder;
|
||||
|
||||
//the javascript object that stores values
|
||||
Value topLevelValue;
|
||||
|
||||
//the object that contains all host values accessible to javascript land
|
||||
Value hostObject;
|
||||
|
||||
//the engine object
|
||||
Value engineObject;
|
||||
|
||||
/**
|
||||
* The hook manager
|
||||
*/
|
||||
Value hookManager;
|
||||
|
||||
/**
|
||||
* The parent script engine
|
||||
*/
|
||||
ScriptEngine parent;
|
||||
|
||||
/**
|
||||
* Locks the script engine to enforce synchronization
|
||||
*/
|
||||
ReentrantLock lock = new ReentrantLock();
|
||||
|
||||
/**
|
||||
* Initializes the context
|
||||
* @param engine
|
||||
*/
|
||||
public void init(ScriptEngine scriptEngine){
|
||||
//register parent
|
||||
this.parent = scriptEngine;
|
||||
|
||||
|
||||
//create engine with flag to disable warning
|
||||
Engine engine = Engine.newBuilder()
|
||||
.option("engine.WarnInterpreterOnly", "false")
|
||||
.build();
|
||||
|
||||
//Create the rules for guest accessing the host environment
|
||||
HostAccess accessRules = HostAccess.newBuilder(HostAccess.EXPLICIT)
|
||||
.allowArrayAccess(true)
|
||||
.build();
|
||||
|
||||
//create context
|
||||
context = Context.newBuilder("js")
|
||||
.allowNativeAccess(false)
|
||||
.allowHostAccess(accessRules)
|
||||
.engine(engine)
|
||||
.build();
|
||||
//save the js bindings object
|
||||
topLevelValue = context.getBindings("js");
|
||||
|
||||
//put host members into environment
|
||||
this.putTopLevelValue("loggerScripts",LoggerInterface.loggerScripts);
|
||||
|
||||
//load all files required to start the engine
|
||||
for(String fileToLoad : ScriptEngine.filesToLoadOnInit){
|
||||
this.loadDependency(fileToLoad);
|
||||
}
|
||||
|
||||
//register engine files
|
||||
this.registerFile("/Scripts/engine/engine-init.ts");
|
||||
}
|
||||
|
||||
/**
|
||||
* Logic to run after initializing
|
||||
*/
|
||||
public void postInit(){
|
||||
//run script for engine init
|
||||
this.requireModule("/Scripts/engine/engine-init.ts");
|
||||
|
||||
//get the engine object
|
||||
engineObject = topLevelValue.getMember("REQUIRE_CACHE").getMember("/Scripts/engine/engine-init.js").getMember("exports").getMember("engine");
|
||||
hookManager = engineObject.getMember("hookManager");
|
||||
|
||||
//define host members
|
||||
this.defineHostMembers();
|
||||
|
||||
//init on script side
|
||||
this.invokeModuleFunction("/Scripts/engine/engine-init.ts","ENGINE_onInit");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Stores a variable at the top level of the js bindings
|
||||
* @param valueName The name of the variable (ie the name of the variable)
|
||||
* @param value The value that is stored at that variable
|
||||
*/
|
||||
public void putTopLevelValue(String valueName, Object value){
|
||||
topLevelValue.putMember(valueName, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a top level value from the script engine
|
||||
* @param valueName The name of the variable
|
||||
* @return The value of the variable
|
||||
*/
|
||||
public Value getTopLevelValue(String valueName){
|
||||
return topLevelValue.getMember(valueName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a top level member from the javascript context
|
||||
* @param valueName The name of the top level member
|
||||
* @return true if successfully removed, false otherwise
|
||||
*/
|
||||
public boolean removeTopLevelValue(String valueName){
|
||||
return topLevelValue.removeMember(valueName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a script from disk
|
||||
* @param path The path to the script file
|
||||
*/
|
||||
void loadDependency(String path){
|
||||
String content;
|
||||
Source source = null;
|
||||
try {
|
||||
content = FileUtils.getAssetFileAsString(path);
|
||||
source = Source.create("js",content);
|
||||
context.eval(source);
|
||||
} catch (IOException e) {
|
||||
LoggerInterface.loggerScripts.ERROR("FAILED TO LOAD SCRIPT", e);
|
||||
} catch (PolyglotException e){
|
||||
if(source != null){
|
||||
LoggerInterface.loggerScripts.WARNING("Source language: " + source.getLanguage());
|
||||
}
|
||||
LoggerInterface.loggerScripts.ERROR("Script error", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the content of a file
|
||||
* @param path The filepath of the script
|
||||
*/
|
||||
public void printScriptSource(String path){
|
||||
invokeMemberFunction("COMPILER", "printSource", path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the contents of a file in the virtual filepath
|
||||
* @param path The virtual filepath
|
||||
* @return The contents of that file if it exists, null otherwise
|
||||
*/
|
||||
public String getVirtualFileContent(String path){
|
||||
String rVal = null;
|
||||
Value compiler = this.topLevelValue.getMember("COMPILER");
|
||||
Value fileMap = compiler.getMember("fileMap");
|
||||
Value virtualFile = fileMap.getMember(path);
|
||||
rVal = virtualFile.getMember("content").asString();
|
||||
return rVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a file with the scripting engine to be compiled into the full binary
|
||||
* @param path The path to the script file
|
||||
*/
|
||||
protected boolean registerFile(String path){
|
||||
String content;
|
||||
try {
|
||||
content = FileUtils.getAssetFileAsString(path);
|
||||
Value dependentFilesValue = this.invokeMemberFunction("COMPILER", "registerFile", path, content);
|
||||
//
|
||||
//register dependent files if necessary
|
||||
long dependentFilesCount = dependentFilesValue.getArraySize();
|
||||
if(dependentFilesCount > 0){
|
||||
for(int i = 0; i < dependentFilesCount; i++){
|
||||
String dependentFilePath = dependentFilesValue.getArrayElement(i).asString();
|
||||
boolean shouldRegister = true;
|
||||
for(String ignorePath : ScriptEngine.registerIgnores){
|
||||
if(ignorePath.equals(dependentFilePath)){
|
||||
shouldRegister = false;
|
||||
}
|
||||
}
|
||||
if(shouldRegister){
|
||||
LoggerInterface.loggerScripts.INFO("[HOST - Script Engine] Should register file " + dependentFilePath);
|
||||
this.registerFile(dependentFilePath);
|
||||
} else {
|
||||
LoggerInterface.loggerScripts.DEBUG("[HOST - Script Engine] Skipping ignorepath file " + dependentFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LoggerInterface.loggerScripts.ERROR("FAILED TO LOAD SCRIPT", e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the project
|
||||
*/
|
||||
protected void compile(){
|
||||
//actually compile
|
||||
this.invokeMemberFunction("COMPILER", "run");
|
||||
//TODO:update local cache
|
||||
Value fileMap = this.topLevelValue.getMember("COMPILER").getMember("fileMap");
|
||||
for(String key : fileMap.getMemberKeys()){
|
||||
Value fileData = fileMap.getMember(key);
|
||||
String content = fileData.getMember("content").asString();
|
||||
String cacheFilePath = ScriptEngine.TS_SOURCE_CACHE_DIR + key;
|
||||
File toWriteFile = new File(cacheFilePath);
|
||||
|
||||
//make sure all containing folders exist
|
||||
try {
|
||||
Files.createDirectories(toWriteFile.getParentFile().toPath());
|
||||
} catch (IOException e) {
|
||||
LoggerInterface.loggerFileIO.ERROR(e);
|
||||
}
|
||||
|
||||
//write the actual file
|
||||
try {
|
||||
Files.writeString(toWriteFile.toPath(), content);
|
||||
} catch (IOException e) {
|
||||
LoggerInterface.loggerFileIO.ERROR(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recompiles the scripting engine
|
||||
*/
|
||||
protected void recompile(Runnable onCompletion){
|
||||
Thread recompileThread = new Thread(() -> {
|
||||
Globals.scriptEngine.getScriptContext().executeSynchronously(() -> {
|
||||
Globals.scriptEngine.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
|
||||
* @return The id assigned to the scene instance from the script-side
|
||||
*/
|
||||
public int initScene(String scenePath){
|
||||
//add files to virtual filesystem in script engine
|
||||
registerFile(scenePath);
|
||||
//load scene from javascript side
|
||||
Value sceneLoader = this.engineObject.getMember("sceneLoader");
|
||||
Value loadFunc = sceneLoader.getMember("loadScene");
|
||||
Value result = loadFunc.execute(scenePath);
|
||||
return result.asInt();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calls a function defined in the global scope with the arguments provided
|
||||
* @param functionName The function name
|
||||
* @param args The arguments
|
||||
*/
|
||||
public Value invokeFunction(String functionName, Object... args){
|
||||
LoggerInterface.loggerScripts.DEBUG("Host execute: " + functionName);
|
||||
Value function = topLevelValue.getMember(functionName);
|
||||
if(function != null){
|
||||
return function.execute(args);
|
||||
} else {
|
||||
LoggerInterface.loggerScripts.WARNING("Failed to invoke function " + functionName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a function on a child of the top level member
|
||||
* @param memberName The name of the child
|
||||
* @param functionName The name of the function
|
||||
* @param args The arguments for the function
|
||||
* @return The value from the function call
|
||||
*/
|
||||
public Value invokeMemberFunction(String memberName, String functionName, Object ... args){
|
||||
LoggerInterface.loggerScripts.DEBUG("Host execute: " + functionName);
|
||||
Value childMember = topLevelValue.getMember(memberName);
|
||||
Value function = childMember.getMember(functionName);
|
||||
if(function != null){
|
||||
return function.execute(args);
|
||||
} else {
|
||||
LoggerInterface.loggerScripts.WARNING("Failed to invoke function " + functionName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes a function defined in a file
|
||||
* @param filePath The file the function is defined in
|
||||
* @param functionName The function's name
|
||||
* @param args The args to pass into the function
|
||||
*/
|
||||
public void invokeModuleFunction(String filePath, String functionName, Object ... args){
|
||||
Value filePathRaw = invokeFunction("FILE_RESOLUTION_getFilePath",filePath);
|
||||
Value requireCache = topLevelValue.getMember("REQUIRE_CACHE");
|
||||
Value module = requireCache.getMember(filePathRaw.asString());
|
||||
Value exports = module.getMember("exports");
|
||||
Value function = exports.getMember(functionName);
|
||||
if(function != null && function.canExecute()){
|
||||
function.execute(args);
|
||||
} else {
|
||||
LoggerInterface.loggerScripts.WARNING("Failed to invoke function " + functionName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requires a module into the global space
|
||||
* @param filePath The filepath of the module
|
||||
*/
|
||||
public void requireModule(String filePath){
|
||||
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
|
||||
*/
|
||||
protected void defineHostMembers(){
|
||||
hostObject = topLevelValue.getMember("HOST_ACCESS");
|
||||
//give guest access to static classes
|
||||
Value classes = engineObject.getMember("classes");
|
||||
for(Object[] currentClass : ScriptEngine.staticClasses){
|
||||
classes.putMember((String)currentClass[0], currentClass[1]);
|
||||
}
|
||||
//give access to script engine instance
|
||||
hostObject.putMember("scriptEngine", this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires a signal on a given scene
|
||||
* @param signal The signal name
|
||||
* @param sceneInstanceId The script-side instanceid of the scene
|
||||
* @param args The arguments to accompany the signal invocation
|
||||
*/
|
||||
public void fireSignal(String signal, int sceneInstanceId, Object ... args){
|
||||
Value fireSignal = this.hookManager.getMember("fireSignal");
|
||||
fireSignal.execute(sceneInstanceId,signal,args);
|
||||
}
|
||||
|
||||
}
|
||||
@ -2,16 +2,23 @@ package electrosphere.script;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.StandardWatchEventKinds;
|
||||
import java.nio.file.WatchEvent;
|
||||
import java.nio.file.WatchKey;
|
||||
import java.nio.file.WatchService;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.graalvm.polyglot.Context;
|
||||
import org.graalvm.polyglot.Engine;
|
||||
import org.graalvm.polyglot.HostAccess;
|
||||
import org.graalvm.polyglot.PolyglotException;
|
||||
import org.graalvm.polyglot.Source;
|
||||
import org.graalvm.polyglot.Source.Builder;
|
||||
import org.graalvm.polyglot.Value;
|
||||
|
||||
import electrosphere.client.script.ScriptClientVoxelUtils;
|
||||
@ -29,55 +36,60 @@ import electrosphere.util.FileUtils;
|
||||
import electrosphere.util.math.SpatialMathUtils;
|
||||
|
||||
/**
|
||||
* Interface for executing scripts in the game engine
|
||||
* Handles the actual file loading of script files
|
||||
*/
|
||||
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
|
||||
public static String SCRIPT_NAMESPACE_SCRIPT = "script"; //namespace for the core typescript functionsw
|
||||
public static String SCRIPT_NAMESPACE_SCENE = "scene"; //namespace for the current scene
|
||||
/**
|
||||
* The directory with all script source files
|
||||
*/
|
||||
public static final String TS_SOURCE_DIR = "./assets/Scripts";
|
||||
|
||||
/**
|
||||
* The typescript cache dir
|
||||
*/
|
||||
public static final String TS_CACHE_DIR = "./.cache/tscache";
|
||||
|
||||
/**
|
||||
* Directory that should contain all ts source dirs
|
||||
*/
|
||||
public static final String TS_SOURCE_CACHE_DIR = TS_CACHE_DIR + "/src";
|
||||
|
||||
/**
|
||||
* The id for firing signals globally
|
||||
*/
|
||||
public static final int GLOBAL_SCENE = -1;
|
||||
|
||||
//the graal context
|
||||
Context context;
|
||||
|
||||
//used to build source objects
|
||||
Builder builder;
|
||||
|
||||
//the map of script filepaths to parsed, in-memory scripts
|
||||
Map<String,Source> sourceMap;
|
||||
|
||||
//the javascript object that stores values
|
||||
Value topLevelValue;
|
||||
|
||||
//the object that contains all host values accessible to javascript land
|
||||
Value hostObject;
|
||||
|
||||
//the engine object
|
||||
Value engineObject;
|
||||
/**
|
||||
* Stores all loaded files' md5 checksums
|
||||
*/
|
||||
Map<String,String> fileChecksumMap = new HashMap<String,String>();
|
||||
|
||||
/**
|
||||
* The hook manager
|
||||
* The script context
|
||||
*/
|
||||
Value hookManager;
|
||||
ScriptContext scriptContext = new ScriptContext();
|
||||
|
||||
/**
|
||||
* The file system object
|
||||
*/
|
||||
FileSystem fs;
|
||||
|
||||
/**
|
||||
* The watch service
|
||||
*/
|
||||
WatchService watchService;
|
||||
|
||||
/**
|
||||
* Tracks the initialization status of the script engine
|
||||
*/
|
||||
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[]{
|
||||
public static final String[] filesToLoadOnInit = new String[]{
|
||||
//polyfills
|
||||
"Scripts/compiler/require_polyfill.js",
|
||||
|
||||
@ -93,13 +105,13 @@ public class ScriptEngine extends SignalServiceImpl {
|
||||
/**
|
||||
* List of files that are ignored when registering new files
|
||||
*/
|
||||
static final String[] registerIgnores = new String[]{
|
||||
public static final String[] registerIgnores = new String[]{
|
||||
"/Scripts/compiler/host_access.ts",
|
||||
};
|
||||
|
||||
//The classes that will be provided to the scripting engine
|
||||
//https://stackoverflow.com/a/65942034
|
||||
static final Object[][] staticClasses = new Object[][]{
|
||||
public static final Object[][] staticClasses = new Object[][]{
|
||||
{"mathUtils",SpatialMathUtils.class},
|
||||
{"simulation",Main.class},
|
||||
{"tutorialUtils",TutorialMenus.class},
|
||||
@ -111,7 +123,7 @@ public class ScriptEngine extends SignalServiceImpl {
|
||||
};
|
||||
|
||||
//singletons from the host that are provided to the javascript context
|
||||
static final Object[][] hostSingletops = new Object[][]{
|
||||
public static final Object[][] hostSingletops = new Object[][]{
|
||||
{"timekeeper",Globals.timekeeper},
|
||||
{"currentPlayer",Globals.clientPlayer},
|
||||
{"loggerScripts",LoggerInterface.loggerScripts},
|
||||
@ -123,6 +135,29 @@ public class ScriptEngine extends SignalServiceImpl {
|
||||
*/
|
||||
public ScriptEngine(){
|
||||
super("ScriptEngine");
|
||||
sourceMap = new HashMap<String,Source>();
|
||||
this.fs = FileSystems.getDefault();
|
||||
try {
|
||||
this.watchService = fs.newWatchService();
|
||||
} catch (IOException e) {
|
||||
LoggerInterface.loggerFileIO.ERROR(e);
|
||||
}
|
||||
//register all source directories
|
||||
try {
|
||||
Files.walkFileTree(new File(TS_SOURCE_DIR).toPath(), new SimpleFileVisitor<Path>(){
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attr) throws IOException {
|
||||
dir.register(
|
||||
watchService,
|
||||
new WatchEvent.Kind[]{StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE}
|
||||
);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -130,365 +165,113 @@ public class ScriptEngine extends SignalServiceImpl {
|
||||
*/
|
||||
public void initScripts(){
|
||||
//init datastructures
|
||||
sourceMap = new HashMap<String,Source>();
|
||||
initialized = false;
|
||||
|
||||
//create engine with flag to disable warning
|
||||
Engine engine = Engine.newBuilder()
|
||||
.option("engine.WarnInterpreterOnly", "false")
|
||||
.build();
|
||||
//init script context
|
||||
scriptContext.init(this);
|
||||
|
||||
//Create the rules for guest accessing the host environment
|
||||
HostAccess accessRules = HostAccess.newBuilder(HostAccess.EXPLICIT)
|
||||
.allowArrayAccess(true)
|
||||
.build();
|
||||
|
||||
//create context
|
||||
context = Context.newBuilder("js")
|
||||
.allowNativeAccess(false)
|
||||
.allowHostAccess(accessRules)
|
||||
.engine(engine)
|
||||
.build();
|
||||
//save the js bindings object
|
||||
topLevelValue = context.getBindings("js");
|
||||
|
||||
//put host members into environment
|
||||
putTopLevelValue("loggerScripts",LoggerInterface.loggerScripts);
|
||||
|
||||
//load all files required to start the engine
|
||||
for(String fileToLoad : filesToLoadOnInit){
|
||||
loadDependency(fileToLoad);
|
||||
}
|
||||
|
||||
//register engine files
|
||||
registerFile("/Scripts/engine/engine-init.ts");
|
||||
//read files from cache
|
||||
boolean readCache = this.initCache();
|
||||
|
||||
//compile
|
||||
compile();
|
||||
if(!readCache){
|
||||
scriptContext.compile();
|
||||
}
|
||||
|
||||
//run script for engine init
|
||||
requireModule("/Scripts/engine/engine-init.ts");
|
||||
//post init logic
|
||||
scriptContext.postInit();
|
||||
|
||||
//get the engine object
|
||||
engineObject = topLevelValue.getMember("REQUIRE_CACHE").getMember("/Scripts/engine/engine-init.js").getMember("exports").getMember("engine");
|
||||
hookManager = engineObject.getMember("hookManager");
|
||||
|
||||
//define host members
|
||||
defineHostMembers();
|
||||
|
||||
//init on script side
|
||||
invokeModuleFunction("/Scripts/engine/engine-init.ts","ENGINE_onInit");
|
||||
|
||||
|
||||
//call the engine initialization function
|
||||
// invokeFunction("ENGINE_onInit");
|
||||
|
||||
|
||||
//read scripts into source map
|
||||
// readScriptsDirectory("/src/main/sql", FileUtils.getAssetFile("/src/main/sql"));
|
||||
//create bindings
|
||||
// try {
|
||||
// String content = FileUtils.getAssetFileAsString("/Scripts/test.js");
|
||||
// Source source = Source.create("js", content);
|
||||
// context.eval(source);
|
||||
// System.out.println("Evaluated");
|
||||
// } catch (IOException e) {
|
||||
// // TODO Auto-generated catch block
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a variable at the top level of the js bindings
|
||||
* @param valueName The name of the variable (ie the name of the variable)
|
||||
* @param value The value that is stored at that variable
|
||||
* Scans the scripts directory for updates
|
||||
*/
|
||||
public void putTopLevelValue(String valueName, Object value){
|
||||
topLevelValue.putMember(valueName, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a top level value from the script engine
|
||||
* @param valueName The name of the variable
|
||||
* @return The value of the variable
|
||||
*/
|
||||
public Value getTopLevelValue(String valueName){
|
||||
return topLevelValue.getMember(valueName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a top level member from the javascript context
|
||||
* @param valueName The name of the top level member
|
||||
* @return true if successfully removed, false otherwise
|
||||
*/
|
||||
public boolean removeTopLevelValue(String valueName){
|
||||
return topLevelValue.removeMember(valueName);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
/**
|
||||
* Reads the scripts directory
|
||||
* @param path The
|
||||
* @param directory
|
||||
*/
|
||||
void registerScriptDirectory(String path, File directory){
|
||||
if(directory.exists() && directory.isDirectory()){
|
||||
File[] children = directory.listFiles();
|
||||
for(File childFile : children){
|
||||
String qualifiedName = path + "/" + childFile.getName();
|
||||
if(childFile.isDirectory()){
|
||||
registerScriptDirectory(qualifiedName,childFile);
|
||||
} else {
|
||||
//add to source map
|
||||
registerFile(qualifiedName);
|
||||
// String content = FileUtils.readFileToString(childFile);
|
||||
// sourceMap.put(qualifiedName,Source.create("js",content));
|
||||
public void scanScriptDir(){
|
||||
WatchKey key = null;
|
||||
while((key = watchService.poll()) != null){
|
||||
List<WatchEvent<?>> events = key.pollEvents();
|
||||
for(WatchEvent<?> event : events){
|
||||
if(event.kind() == StandardWatchEventKinds.ENTRY_MODIFY){
|
||||
if(event.context() instanceof Path){
|
||||
// Path filePath = (Path)event.context();
|
||||
// System.out.println(filePath);
|
||||
}
|
||||
} else if(event.kind() == StandardWatchEventKinds.ENTRY_CREATE){
|
||||
throw new Error("Cannot handle create events yet");
|
||||
} else if(event.kind() == StandardWatchEventKinds.ENTRY_DELETE){
|
||||
throw new Error("Cannot handle delete events yet");
|
||||
}
|
||||
}
|
||||
key.reset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a script from disk
|
||||
* @param path The path to the script file
|
||||
* Gets the script context of the engine
|
||||
* @return The script context
|
||||
*/
|
||||
void loadDependency(String path){
|
||||
String content;
|
||||
Source source = null;
|
||||
try {
|
||||
content = FileUtils.getAssetFileAsString(path);
|
||||
source = Source.create("js",content);
|
||||
sourceMap.put(path,source);
|
||||
context.eval(source);
|
||||
} catch (IOException e) {
|
||||
LoggerInterface.loggerScripts.ERROR("FAILED TO LOAD SCRIPT", e);
|
||||
} catch (PolyglotException e){
|
||||
if(source != null){
|
||||
LoggerInterface.loggerScripts.WARNING("Source language: " + source.getLanguage());
|
||||
public ScriptContext getScriptContext(){
|
||||
return this.scriptContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure the cache folder exists
|
||||
* @return true if files were read from cache, false otherwise
|
||||
*/
|
||||
private boolean initCache(){
|
||||
File tsCache = new File(ScriptEngine.TS_SOURCE_CACHE_DIR);
|
||||
if(!tsCache.exists()){
|
||||
try {
|
||||
Files.createDirectories(tsCache.toPath());
|
||||
} catch (IOException e) {
|
||||
LoggerInterface.loggerFileIO.ERROR(e);
|
||||
}
|
||||
LoggerInterface.loggerScripts.ERROR("Script error", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the content of a file
|
||||
* @param path The filepath of the script
|
||||
*/
|
||||
public void printScriptSource(String path){
|
||||
invokeMemberFunction("COMPILER", "printSource", path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the contents of a file in the virtual filepath
|
||||
* @param path The virtual filepath
|
||||
* @return The contents of that file if it exists, null otherwise
|
||||
*/
|
||||
public String getVirtualFileContent(String path){
|
||||
String rVal = null;
|
||||
Value compiler = this.topLevelValue.getMember("COMPILER");
|
||||
Value fileMap = compiler.getMember("fileMap");
|
||||
Value virtualFile = fileMap.getMember(path);
|
||||
rVal = virtualFile.getMember("content").asString();
|
||||
return rVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a file with the scripting engine to be compiled into the full binary
|
||||
* @param path The path to the script file
|
||||
*/
|
||||
private boolean registerFile(String path){
|
||||
String content;
|
||||
try {
|
||||
content = FileUtils.getAssetFileAsString(path);
|
||||
sourceMap.put(path,Source.create("js",content));
|
||||
Value dependentFilesValue = invokeMemberFunction("COMPILER", "registerFile",path,content);
|
||||
//
|
||||
//register dependent files if necessary
|
||||
long dependentFilesCount = dependentFilesValue.getArraySize();
|
||||
if(dependentFilesCount > 0){
|
||||
for(int i = 0; i < dependentFilesCount; i++){
|
||||
String dependentFilePath = dependentFilesValue.getArrayElement(i).asString();
|
||||
boolean shouldRegister = true;
|
||||
for(String ignorePath : registerIgnores){
|
||||
if(ignorePath.equals(dependentFilePath)){
|
||||
shouldRegister = false;
|
||||
}
|
||||
}
|
||||
if(shouldRegister){
|
||||
LoggerInterface.loggerScripts.INFO("[HOST - Script Engine] Should register file " + dependentFilePath);
|
||||
registerFile(dependentFilePath);
|
||||
} else {
|
||||
LoggerInterface.loggerScripts.DEBUG("[HOST - Script Engine] Skipping ignorepath file " + dependentFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LoggerInterface.loggerScripts.ERROR("FAILED TO LOAD SCRIPT", e);
|
||||
return false;
|
||||
} else {
|
||||
Value fileMap = this.scriptContext.getTopLevelValue("COMPILER").getMember("fileMap");
|
||||
this.recursivelyRegisterCachedFiles(tsCache,fileMap,tsCache);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the project
|
||||
* Register files recursively from the current file
|
||||
* @param tsCache The ts cache file
|
||||
* @param fileMap The file map on script side
|
||||
* @param currentDirectory The current directory
|
||||
*/
|
||||
private void compile(){
|
||||
invokeMemberFunction("COMPILER", "run");
|
||||
}
|
||||
private void recursivelyRegisterCachedFiles(File tsCache, Value fileMap, File currentDirectory){
|
||||
for(File file : currentDirectory.listFiles()){
|
||||
if(file.isDirectory()){
|
||||
this.recursivelyRegisterCachedFiles(tsCache, fileMap, file);
|
||||
} else if(file.getPath().endsWith(".ts") || file.getPath().endsWith(".js")){
|
||||
try {
|
||||
String relativePath = FileUtils.relativize(file, tsCache);
|
||||
String normalizedPath = "/" + relativePath;
|
||||
|
||||
/**
|
||||
* Recompiles the scripting engine
|
||||
*/
|
||||
private void recompile(Runnable onCompletion){
|
||||
Thread recompileThread = new Thread(() -> {
|
||||
Globals.scriptEngine.executeSynchronously(() -> {
|
||||
this.initScripts();
|
||||
});
|
||||
if(onCompletion != null){
|
||||
onCompletion.run();
|
||||
if(!this.fileChecksumMap.containsKey(normalizedPath)){
|
||||
|
||||
//read file
|
||||
String fileContent = Files.readString(file.toPath());
|
||||
|
||||
//store checksum
|
||||
try {
|
||||
this.fileChecksumMap.put(normalizedPath,FileUtils.getChecksum(fileContent));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
//store on script side
|
||||
LoggerInterface.loggerScripts.DEBUG("Preload: " + normalizedPath);
|
||||
this.scriptContext.getTopLevelValue("COMPILER").invokeMember("preloadFile", normalizedPath, fileContent);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LoggerInterface.loggerFileIO.ERROR(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
recompileThread.setName("Recompile Script Engine");
|
||||
recompileThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a scene script
|
||||
* @param scenePath The scene's init script path
|
||||
* @return The id assigned to the scene instance from the script-side
|
||||
*/
|
||||
public int initScene(String scenePath){
|
||||
//add files to virtual filesystem in script engine
|
||||
registerFile(scenePath);
|
||||
//load scene from javascript side
|
||||
Value sceneLoader = this.engineObject.getMember("sceneLoader");
|
||||
Value loadFunc = sceneLoader.getMember("loadScene");
|
||||
Value result = loadFunc.execute(scenePath);
|
||||
return result.asInt();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calls a function defined in the global scope with the arguments provided
|
||||
* @param functionName The function name
|
||||
* @param args The arguments
|
||||
*/
|
||||
public Value invokeFunction(String functionName, Object... args){
|
||||
LoggerInterface.loggerScripts.DEBUG("Host execute: " + functionName);
|
||||
Value function = topLevelValue.getMember(functionName);
|
||||
if(function != null){
|
||||
return function.execute(args);
|
||||
} else {
|
||||
LoggerInterface.loggerScripts.WARNING("Failed to invoke function " + functionName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a function on a child of the top level member
|
||||
* @param memberName The name of the child
|
||||
* @param functionName The name of the function
|
||||
* @param args The arguments for the function
|
||||
* @return The value from the function call
|
||||
*/
|
||||
public Value invokeMemberFunction(String memberName, String functionName, Object ... args){
|
||||
LoggerInterface.loggerScripts.DEBUG("Host execute: " + functionName);
|
||||
Value childMember = topLevelValue.getMember(memberName);
|
||||
Value function = childMember.getMember(functionName);
|
||||
if(function != null){
|
||||
return function.execute(args);
|
||||
} else {
|
||||
LoggerInterface.loggerScripts.WARNING("Failed to invoke function " + functionName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes a function defined in a file
|
||||
* @param filePath The file the function is defined in
|
||||
* @param functionName The function's name
|
||||
* @param args The args to pass into the function
|
||||
*/
|
||||
public void invokeModuleFunction(String filePath, String functionName, Object ... args){
|
||||
Value filePathRaw = invokeFunction("FILE_RESOLUTION_getFilePath",filePath);
|
||||
Value requireCache = topLevelValue.getMember("REQUIRE_CACHE");
|
||||
Value module = requireCache.getMember(filePathRaw.asString());
|
||||
Value exports = module.getMember("exports");
|
||||
Value function = exports.getMember(functionName);
|
||||
if(function != null && function.canExecute()){
|
||||
function.execute(args);
|
||||
} else {
|
||||
LoggerInterface.loggerScripts.WARNING("Failed to invoke function " + functionName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requires a module into the global space
|
||||
* @param filePath The filepath of the module
|
||||
*/
|
||||
public void requireModule(String filePath){
|
||||
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
|
||||
*/
|
||||
private void defineHostMembers(){
|
||||
hostObject = topLevelValue.getMember("HOST_ACCESS");
|
||||
//give guest access to static classes
|
||||
Value classes = engineObject.getMember("classes");
|
||||
for(Object[] currentClass : staticClasses){
|
||||
classes.putMember((String)currentClass[0], currentClass[1]);
|
||||
}
|
||||
//give access to script engine instance
|
||||
hostObject.putMember("scriptEngine", this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires a signal on a given scene
|
||||
* @param signal The signal name
|
||||
* @param sceneInstanceId The script-side instanceid of the scene
|
||||
* @param args The arguments to accompany the signal invocation
|
||||
*/
|
||||
public void fireSignal(String signal, int sceneInstanceId, Object ... args){
|
||||
Value fireSignal = this.hookManager.getMember("fireSignal");
|
||||
fireSignal.execute(sceneInstanceId,signal,args);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -506,9 +289,9 @@ public class ScriptEngine extends SignalServiceImpl {
|
||||
switch(signal.getType()){
|
||||
case SCRIPT_RECOMPILE: {
|
||||
if(signal.getData() != null && signal.getData() instanceof Runnable){
|
||||
this.recompile((Runnable)signal.getData());
|
||||
scriptContext.recompile((Runnable)signal.getData());
|
||||
} else {
|
||||
this.recompile(null);
|
||||
scriptContext.recompile(null);
|
||||
}
|
||||
rVal = true;
|
||||
} break;
|
||||
|
||||
@ -276,11 +276,11 @@ public class Realm {
|
||||
*/
|
||||
public void fireSignal(String signalName, Object ... args){
|
||||
if(Globals.scriptEngine != null && Globals.scriptEngine.isInitialized()){
|
||||
Globals.scriptEngine.executeSynchronously(() -> {
|
||||
Globals.scriptEngine.getScriptContext().executeSynchronously(() -> {
|
||||
if(this.sceneInstanceId != NO_SCENE_INSTANCE){
|
||||
Globals.scriptEngine.fireSignal(signalName, sceneInstanceId, args);
|
||||
Globals.scriptEngine.getScriptContext().fireSignal(signalName, sceneInstanceId, args);
|
||||
} else {
|
||||
Globals.scriptEngine.fireSignal(signalName, ScriptEngine.GLOBAL_SCENE, args);
|
||||
Globals.scriptEngine.getScriptContext().fireSignal(signalName, ScriptEngine.GLOBAL_SCENE, args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -147,11 +147,11 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
|
||||
VoxelGenerator voxelGenerator = this.tagVoxelMap.get("hills");
|
||||
|
||||
if(this.useJavascript){
|
||||
Globals.scriptEngine.executeSynchronously(() -> {
|
||||
Globals.scriptEngine.getScriptContext().executeSynchronously(() -> {
|
||||
int firstType = -2;
|
||||
boolean homogenous = true;
|
||||
GeneratedVoxel voxel = new GeneratedVoxel();
|
||||
Value getVoxelFunc = Globals.scriptEngine.invokeEngineMember("chunkGeneratorManager", "getVoxelFunction", SCRIPT_GEN_TEST_TAG);
|
||||
Value getVoxelFunc = Globals.scriptEngine.getScriptContext().invokeEngineMember("chunkGeneratorManager", "getVoxelFunction", SCRIPT_GEN_TEST_TAG);
|
||||
for(int x = 0; x < ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE; x++){
|
||||
Globals.profiler.beginAggregateCpuSample("TestGenerationChunkGenerator - Generate slice");
|
||||
for(int y = 0; y < ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE; y++){
|
||||
|
||||
@ -12,13 +12,18 @@ import electrosphere.util.annotation.AnnotationExclusionStrategy;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -421,8 +426,47 @@ public class FileUtils {
|
||||
LoggerInterface.loggerRenderer.ERROR(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a file's path relative to a given directory
|
||||
* @param file The file
|
||||
* @param directory The directory
|
||||
* @return The relative path
|
||||
*/
|
||||
public static String relativize(File file, File directory){
|
||||
return directory.toURI().relativize(file.toURI()).getPath();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Computes the checksum of an object
|
||||
* @param object The object
|
||||
* @return The checksum
|
||||
* @throws IOException Thrown on io errors reading the file
|
||||
* @throws NoSuchAlgorithmException Thrown if MD5 isn't supported
|
||||
*/
|
||||
public static String getChecksum(Serializable object) throws IOException, NoSuchAlgorithmException {
|
||||
ByteArrayOutputStream baos = null;
|
||||
ObjectOutputStream oos = null;
|
||||
try {
|
||||
baos = new ByteArrayOutputStream();
|
||||
oos = new ObjectOutputStream(baos);
|
||||
oos.writeObject(object);
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
byte[] bytes = md.digest(baos.toByteArray());
|
||||
StringBuffer builder = new StringBuffer();
|
||||
for(byte byteCurr : bytes){
|
||||
builder.append(String.format("%02x",byteCurr));
|
||||
}
|
||||
return builder.toString();
|
||||
} finally {
|
||||
oos.close();
|
||||
baos.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user