hooks implementation
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit
This commit is contained in:
parent
7ee1f99dd1
commit
893239c158
@ -1,4 +1,5 @@
|
||||
import { Scene } from "/Scripts/types/scene";
|
||||
import { Vector } from "/Scripts/types/spatial";
|
||||
|
||||
/**
|
||||
* The main scene interface
|
||||
@ -24,8 +25,18 @@ class TestScene1 extends Scene {
|
||||
*/
|
||||
{
|
||||
signal: "equipItem",
|
||||
callback: () => {
|
||||
console.log("Item equipped")
|
||||
callback: (entityId: number) => {
|
||||
console.log("Item equipped to entity " + entityId)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Move hook
|
||||
*/
|
||||
{
|
||||
signal: "entityGroundMove",
|
||||
callback: (entityId: number, newPos: Vector) => {
|
||||
console.log("Entity moved " + entityId + " to " + Vector.toString(newPos))
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -22,7 +22,8 @@ export const ENGINE_onInit = () => {
|
||||
|
||||
//load namespaces
|
||||
let client: NamespaceClient = Client
|
||||
engine.sceneLoader.hookManager = engine.hookManager
|
||||
engine.sceneLoader.engine = engine
|
||||
engine.hookManager.engine = engine
|
||||
|
||||
loggerScripts.INFO('Script Engine Initialized')
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { Engine } from "/Scripts/engine/engine-interface"
|
||||
import { Hook } from "/Scripts/types/hook"
|
||||
import { Scene } from "/Scripts/types/scene"
|
||||
|
||||
@ -49,6 +50,9 @@ export class HookManager {
|
||||
*/
|
||||
readonly trackedScenes: Array<Scene> = []
|
||||
|
||||
//The parent engine object
|
||||
engine: Engine
|
||||
|
||||
/**
|
||||
* Registers a hook
|
||||
* @param scene The scene introducing the hook
|
||||
@ -67,6 +71,10 @@ export class HookManager {
|
||||
const signalArray: Array<TrackedHook> = this.signalHookMap?.[hookSignal] ? this.signalHookMap?.[hookSignal] : []
|
||||
signalArray.push(trackedHook)
|
||||
this.signalHookMap[hookSignal] = signalArray
|
||||
console.log('register signal hook map')
|
||||
console.log(hookSignal)
|
||||
console.log(Object.keys(this.signalHookMap))
|
||||
console.log(this.signalHookMap[hookSignal])
|
||||
//
|
||||
//Scene related structures
|
||||
//
|
||||
@ -92,15 +100,37 @@ export class HookManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires a signal
|
||||
* Fires a signal scoped to a specific scene instance
|
||||
* @param instanceId The scene instance id to fire the signal within
|
||||
* @param signal The signal
|
||||
* @param value The value associated with the signal
|
||||
*/
|
||||
fireSignal(instanceId: number, signal: string, value: any){
|
||||
const hooks: Array<TrackedHook> = this.signalHookMap[signal]
|
||||
hooks.forEach(trackedHook => {
|
||||
trackedHook.callback(value)
|
||||
})
|
||||
fireSignal(instanceId: number, signal: string, ...value: any){
|
||||
//parse from host
|
||||
const argsRaw: any[] = value?.[0]
|
||||
|
||||
//try running global hooks for the signal
|
||||
const globalHooks: Array<TrackedHook> = this.signalHookMap[signal]
|
||||
if(!!globalHooks){
|
||||
globalHooks.forEach(trackedHook => {
|
||||
trackedHook.callback(...argsRaw)
|
||||
})
|
||||
} else {
|
||||
//There isn't a hook registered for this signal at the global level
|
||||
console.log("No global hooks for signal " + signal)
|
||||
}
|
||||
|
||||
//try firing at scene scope
|
||||
const scene: Scene = this.engine.sceneLoader.getScene(instanceId)
|
||||
const sceneHooks: Array<TrackedHook> = scene.signalHookMap[signal]
|
||||
if(!!sceneHooks){
|
||||
sceneHooks.forEach(trackeHook => {
|
||||
trackeHook.callback(...argsRaw)
|
||||
})
|
||||
} else {
|
||||
//There isn't a hook registered for this signal at the scene level
|
||||
console.log("No scene hooks for signal " + signal)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { engine } from "/Scripts/engine/engine-init";
|
||||
import { HookManager } from "/Scripts/engine/hooks/hook-manager";
|
||||
import { Engine } from "/Scripts/engine/engine-interface";
|
||||
import { Hook } from "/Scripts/types/hook";
|
||||
import { Scene } from "/Scripts/types/scene";
|
||||
|
||||
@ -13,9 +12,9 @@ export class SceneLoader {
|
||||
|
||||
|
||||
/**
|
||||
* The hook manager
|
||||
* The parent engine object
|
||||
*/
|
||||
hookManager: HookManager
|
||||
engine: Engine
|
||||
|
||||
/**
|
||||
* The list of loaded scenes
|
||||
@ -70,7 +69,7 @@ export class SceneLoader {
|
||||
|
||||
//load all hooks from the scene
|
||||
scene?.hooks?.forEach((hook: Hook) => {
|
||||
this.hookManager.registerHook(trackedScene,hook,isServerScene)
|
||||
this.engine.hookManager.registerHook(trackedScene,hook,isServerScene)
|
||||
})
|
||||
|
||||
return trackedScene.instanceId
|
||||
|
||||
@ -13,6 +13,6 @@ export interface Hook {
|
||||
/**
|
||||
* The function to call when the signal is fired
|
||||
*/
|
||||
readonly callback: Function,
|
||||
readonly callback: (...value: any) => void,
|
||||
|
||||
}
|
||||
|
||||
31
assets/Scripts/types/spatial.ts
Normal file
31
assets/Scripts/types/spatial.ts
Normal file
@ -0,0 +1,31 @@
|
||||
|
||||
/**
|
||||
* A 3D vector
|
||||
*/
|
||||
export class Vector {
|
||||
|
||||
/**
|
||||
* The x coordinate
|
||||
*/
|
||||
x: number
|
||||
|
||||
/**
|
||||
* The y coordinate
|
||||
*/
|
||||
y: number
|
||||
|
||||
/**
|
||||
* The z coordinate
|
||||
*/
|
||||
z: number
|
||||
|
||||
/**
|
||||
* Gets the Vector as a string
|
||||
* @param input The vector to convert to a string
|
||||
* @returns The string
|
||||
*/
|
||||
static toString(input: Vector): string {
|
||||
return Math.round(input.x * 100) / 100 + "," + Math.round(input.y * 100) / 100 + "," + Math.round(input.z * 100) / 100
|
||||
}
|
||||
|
||||
}
|
||||
@ -2,7 +2,7 @@
|
||||
Server commands client to load a given scene file
|
||||
+ there is a sword lying on the ground
|
||||
+ when you grab the sword, a tutorial popup appears to tell you how to use in
|
||||
fire a script on a hook
|
||||
fix grabbing an item
|
||||
hook for grabbing an item
|
||||
+ on clearing the tutorial, continue the game+ when the sword is equipped, create another popup to teach sword controls. it pauses the game
|
||||
hook on equipping an item
|
||||
|
||||
@ -424,6 +424,10 @@ Extracting pixels from framebuffers
|
||||
(07/07/2024)
|
||||
Work on testing
|
||||
|
||||
(07/16/2024)
|
||||
Scene Loading Refactor
|
||||
Hooking into engine from script-side
|
||||
|
||||
|
||||
# TODO
|
||||
|
||||
|
||||
@ -104,7 +104,8 @@ public class SceneLoader {
|
||||
}
|
||||
//load scripts
|
||||
if(!isLevelEditor && file.getInitScriptPath() != null){
|
||||
Globals.scriptEngine.initScene(file.getInitScriptPath());
|
||||
int sceneInstanceId = Globals.scriptEngine.initScene(file.getInitScriptPath());
|
||||
realm.setSceneInstanceId(sceneInstanceId);
|
||||
}
|
||||
//TODO: integrate scripts for client side of scenes
|
||||
// for(String scriptPath : file.getScriptPaths()){
|
||||
|
||||
@ -40,6 +40,7 @@ import electrosphere.server.datacell.Realm;
|
||||
import electrosphere.server.datacell.ServerDataCell;
|
||||
import electrosphere.server.datacell.utils.DataCellSearchUtils;
|
||||
import electrosphere.server.datacell.utils.ServerEntityTagUtils;
|
||||
import electrosphere.server.utils.ServerScriptUtils;
|
||||
|
||||
@SynchronizedBehaviorTree(name = "serverEquipState", isServer = true, correspondingTree="clientEquipState")
|
||||
/**
|
||||
@ -184,6 +185,9 @@ public class ServerEquipState implements BehaviorTree {
|
||||
NetworkMessage attachMessage = InventoryMessage.constructserverCommandEquipItemMessage(equipperId, equipPointId, inWorldItemId, itemType);
|
||||
//actually send the packet
|
||||
dataCell.broadcastNetworkMessage(attachMessage);
|
||||
|
||||
//Fire signal to script engine to equip
|
||||
ServerScriptUtils.fireSignalOnEntity(parent, "equipItem");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -28,8 +28,10 @@ import electrosphere.net.parser.net.message.EntityMessage;
|
||||
import electrosphere.net.synchronization.annotation.SyncedField;
|
||||
import electrosphere.net.synchronization.annotation.SynchronizedBehaviorTree;
|
||||
import electrosphere.renderer.anim.Animation;
|
||||
import electrosphere.script.utils.AccessTransforms;
|
||||
import electrosphere.server.datacell.utils.DataCellSearchUtils;
|
||||
import electrosphere.server.poseactor.PoseActor;
|
||||
import electrosphere.server.utils.ServerScriptUtils;
|
||||
import electrosphere.util.MathUtils;
|
||||
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
@ -344,6 +346,9 @@ public class ServerGroundMovementTree implements BehaviorTree {
|
||||
1
|
||||
)
|
||||
);
|
||||
|
||||
//tell script engine we moved
|
||||
ServerScriptUtils.fireSignalOnEntity(parent, "entityGroundMove", AccessTransforms.getVector(position));
|
||||
} break;
|
||||
case SLOWDOWN: {
|
||||
//run slowdown code
|
||||
|
||||
@ -7,6 +7,7 @@ import java.util.Map;
|
||||
|
||||
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;
|
||||
@ -45,6 +46,11 @@ public class ScriptEngine {
|
||||
//the engine object
|
||||
Value engineObject;
|
||||
|
||||
/**
|
||||
* The hook manager
|
||||
*/
|
||||
Value hookManager;
|
||||
|
||||
//The files that are loaded on init to bootstrap the script engine
|
||||
static final String[] filesToLoadOnInit = new String[]{
|
||||
//polyfills
|
||||
@ -85,11 +91,21 @@ public class ScriptEngine {
|
||||
public void init(){
|
||||
//init datastructures
|
||||
sourceMap = new HashMap<String,Source>();
|
||||
|
||||
//create engine with flag to disable warning
|
||||
Engine engine = Engine.newBuilder().option("engine.WarnInterpreterOnly", "false").build();
|
||||
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
|
||||
@ -116,6 +132,7 @@ public class ScriptEngine {
|
||||
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");
|
||||
invokeModuleFunction("/Scripts/engine/engine-init.ts","ENGINE_onInit");
|
||||
|
||||
|
||||
@ -218,6 +235,20 @@ public class ScriptEngine {
|
||||
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
|
||||
@ -353,6 +384,17 @@ public class ScriptEngine {
|
||||
//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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
31
src/main/java/electrosphere/script/access/Vector.java
Normal file
31
src/main/java/electrosphere/script/access/Vector.java
Normal file
@ -0,0 +1,31 @@
|
||||
package electrosphere.script.access;
|
||||
|
||||
import org.graalvm.polyglot.HostAccess.Export;
|
||||
|
||||
/**
|
||||
* A script's view of a 3d vector
|
||||
*/
|
||||
public class Vector {
|
||||
|
||||
@Export
|
||||
public double x;
|
||||
|
||||
@Export
|
||||
public double y;
|
||||
|
||||
@Export
|
||||
public double z;
|
||||
|
||||
/**
|
||||
* Creates a vector
|
||||
* @param x the x coordinate
|
||||
* @param y the y coordinate
|
||||
* @param z the z coordinate
|
||||
*/
|
||||
public Vector(double x, double y, double z){
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package electrosphere.script.utils;
|
||||
|
||||
/**
|
||||
* Used for transforming datastructures to and from the script-access forms
|
||||
* ie, converting a "Vector3d" into a "Vector" and vice versa
|
||||
*/
|
||||
public class AccessTransforms {
|
||||
|
||||
/**
|
||||
* Converts a JOML vector to an access vector
|
||||
* @param source The JOML vector
|
||||
* @return The access vector
|
||||
*/
|
||||
public static electrosphere.script.access.Vector getVector(org.joml.Vector3d source){
|
||||
return new electrosphere.script.access.Vector(source.x, source.y, source.z);
|
||||
}
|
||||
|
||||
}
|
||||
@ -6,6 +6,7 @@ import electrosphere.engine.Globals;
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.entity.Scene;
|
||||
import electrosphere.game.server.world.ServerWorldData;
|
||||
import electrosphere.logger.LoggerInterface;
|
||||
import electrosphere.net.parser.net.message.NetworkMessage;
|
||||
import electrosphere.server.content.ServerContentManager;
|
||||
import electrosphere.server.datacell.interfaces.DataCellManager;
|
||||
@ -18,6 +19,11 @@ import java.util.Set;
|
||||
* Manages data cells on the server side
|
||||
*/
|
||||
public class Realm {
|
||||
|
||||
/**
|
||||
* No scene was loaded from script engine alongside this realm
|
||||
*/
|
||||
public static final int NO_SCENE_INSTANCE = -1;
|
||||
|
||||
//The set containing all data cells loaded into this realm
|
||||
Set<ServerDataCell> loadedDataCells = new HashSet<ServerDataCell>();
|
||||
@ -46,6 +52,11 @@ public class Realm {
|
||||
* The content manager
|
||||
*/
|
||||
ServerContentManager serverContentManager;
|
||||
|
||||
/**
|
||||
* The instanceId of the scene that was loaded with this realm
|
||||
*/
|
||||
int sceneInstanceId = NO_SCENE_INSTANCE;
|
||||
|
||||
/**
|
||||
* Realm constructor
|
||||
@ -217,5 +228,26 @@ public class Realm {
|
||||
public ServerContentManager getServerContentManager(){
|
||||
return this.serverContentManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the script-engine side instance id for the scene that was loaded with this realm
|
||||
* @param sceneInstanceId The instance id
|
||||
*/
|
||||
public void setSceneInstanceId(int sceneInstanceId){
|
||||
this.sceneInstanceId = sceneInstanceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires a signal in this scene
|
||||
* @param signalName The name of the signal
|
||||
* @param args The arguments provided alongside the signal
|
||||
*/
|
||||
public void fireSignal(String signalName, Object ... args){
|
||||
if(this.sceneInstanceId != NO_SCENE_INSTANCE){
|
||||
Globals.scriptEngine.fireSignal(signalName, sceneInstanceId, args);
|
||||
} else {
|
||||
LoggerInterface.loggerScripts.ERROR(new UnsupportedOperationException("Firing a signal in a realm that does not have an associated scene"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
package electrosphere.server.utils;
|
||||
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.server.datacell.Realm;
|
||||
|
||||
/**
|
||||
* Utility functions for dealing with scripts from the server
|
||||
*/
|
||||
public class ServerScriptUtils {
|
||||
|
||||
/**
|
||||
* Fires a signal on an entity
|
||||
* @param entity The entity
|
||||
* @param signal The signal
|
||||
* @param args The args provided with the signal
|
||||
*/
|
||||
public static void fireSignalOnEntity(Entity entity, String signal, Object ... args){
|
||||
Realm entityRealm = Globals.realmManager.getEntityRealm(entity);
|
||||
|
||||
//TODO: see if we can optimize this if it becomes a problem
|
||||
Object finalArgs[] = new Object[args.length + 1];
|
||||
finalArgs[0] = entity.getId();
|
||||
for(int i = 0; i < args.length; i++){
|
||||
finalArgs[i+1] = args[i];
|
||||
}
|
||||
entityRealm.fireSignal(signal, finalArgs);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user