Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit
311 lines
9.9 KiB
Java
311 lines
9.9 KiB
Java
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 org.graalvm.polyglot.Source;
|
|
import org.graalvm.polyglot.Value;
|
|
|
|
import electrosphere.client.script.ScriptClientVoxelUtils;
|
|
import electrosphere.client.ui.menu.script.ScriptLevelEditorUtils;
|
|
import electrosphere.client.ui.menu.script.ScriptMenuUtils;
|
|
import electrosphere.client.ui.menu.tutorial.TutorialMenus;
|
|
import electrosphere.engine.Globals;
|
|
import electrosphere.engine.Main;
|
|
import electrosphere.engine.signal.Signal;
|
|
import electrosphere.engine.signal.Signal.SignalType;
|
|
import electrosphere.engine.signal.SignalServiceImpl;
|
|
import electrosphere.logger.LoggerInterface;
|
|
import electrosphere.script.translation.JSServerUtils;
|
|
import electrosphere.script.utils.ScriptMathInterface;
|
|
import electrosphere.util.FileUtils;
|
|
import electrosphere.util.math.SpatialMathUtils;
|
|
|
|
/**
|
|
* Handles the actual file loading of script files
|
|
*/
|
|
public class ScriptEngine extends SignalServiceImpl {
|
|
|
|
/**
|
|
* 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 map of script filepaths to parsed, in-memory scripts
|
|
Map<String,Source> sourceMap;
|
|
|
|
/**
|
|
* Stores all loaded files' md5 checksums
|
|
*/
|
|
Map<String,String> fileChecksumMap = new HashMap<String,String>();
|
|
|
|
/**
|
|
* The script context
|
|
*/
|
|
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;
|
|
|
|
//The files that are loaded on init to bootstrap the script engine
|
|
public static final String[] filesToLoadOnInit = new String[]{
|
|
//polyfills
|
|
"Scripts/compiler/require_polyfill.js",
|
|
|
|
//main typescript engine
|
|
"Scripts/compiler/typescript.js",
|
|
|
|
//compiler and utilities
|
|
"Scripts/compiler/file_resolution.js",
|
|
"Scripts/compiler/compiler.js",
|
|
"Scripts/compiler/host_access.js",
|
|
};
|
|
|
|
/**
|
|
* List of files that are ignored when registering new files
|
|
*/
|
|
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
|
|
public static final Object[][] staticClasses = new Object[][]{
|
|
{"mathUtils",SpatialMathUtils.class},
|
|
{"simulation",Main.class},
|
|
{"tutorialUtils",TutorialMenus.class},
|
|
{"serverUtils",JSServerUtils.class},
|
|
{"menuUtils",ScriptMenuUtils.class},
|
|
{"voxelUtils",ScriptClientVoxelUtils.class},
|
|
{"levelEditorUtils",ScriptLevelEditorUtils.class},
|
|
{"math",ScriptMathInterface.class},
|
|
};
|
|
|
|
//singletons from the host that are provided to the javascript context
|
|
public static final Object[][] hostSingletops = new Object[][]{
|
|
{"timekeeper",Globals.timekeeper},
|
|
{"currentPlayer",Globals.clientPlayer},
|
|
{"loggerScripts",LoggerInterface.loggerScripts},
|
|
};
|
|
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
public ScriptEngine(){
|
|
super(
|
|
"ScriptEngine",
|
|
new SignalType[]{
|
|
SignalType.SCRIPT_RECOMPILE,
|
|
}
|
|
);
|
|
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();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initializes the engine
|
|
*/
|
|
public void initScripts(){
|
|
//init datastructures
|
|
initialized = false;
|
|
|
|
//init script context
|
|
scriptContext.init(this);
|
|
|
|
//read files from cache
|
|
boolean readCache = this.initCache();
|
|
|
|
//compile
|
|
if(!readCache){
|
|
scriptContext.compile();
|
|
}
|
|
|
|
//post init logic
|
|
scriptContext.postInit();
|
|
|
|
initialized = true;
|
|
}
|
|
|
|
/**
|
|
* Scans the scripts directory for updates
|
|
*/
|
|
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();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the script context of the engine
|
|
* @return The script context
|
|
*/
|
|
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);
|
|
}
|
|
return false;
|
|
} else {
|
|
Value fileMap = this.scriptContext.getTopLevelValue("COMPILER").getMember("fileMap");
|
|
this.recursivelyRegisterCachedFiles(tsCache,fileMap,tsCache);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 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;
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the initialization status of the script engine
|
|
* @return true if initialized, false otherwise
|
|
*/
|
|
public boolean isInitialized(){
|
|
return this.initialized;
|
|
}
|
|
|
|
|
|
@Override
|
|
public boolean handle(Signal signal){
|
|
boolean rVal = false;
|
|
switch(signal.getType()){
|
|
case SCRIPT_RECOMPILE: {
|
|
if(signal.getData() != null && signal.getData() instanceof Runnable){
|
|
scriptContext.recompile((Runnable)signal.getData());
|
|
} else {
|
|
scriptContext.recompile(null);
|
|
}
|
|
rVal = true;
|
|
} break;
|
|
default: {
|
|
} break;
|
|
}
|
|
return rVal;
|
|
}
|
|
|
|
}
|