hooks implementation
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit

This commit is contained in:
austin 2024-07-16 17:55:28 -04:00
parent 7ee1f99dd1
commit 893239c158
16 changed files with 257 additions and 18 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -13,6 +13,6 @@ export interface Hook {
/**
* The function to call when the signal is fired
*/
readonly callback: Function,
readonly callback: (...value: any) => void,
}

View 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
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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