Преглед на файлове

[HTML5] EditorRunNative works with GDNative.

This "breaks" our loading bar logic (libraries are not counted).
Fixing it is non trivial and probably deserves investigating a different
strategy.
Fabio Alessandrelli преди 4 години
родител
ревизия
611c4998e8

+ 2 - 0
misc/dist/html/full-size.html

@@ -145,6 +145,7 @@ $GODOT_HEAD_INCLUDE
 			const EXECUTABLE_NAME = '$GODOT_BASENAME';
 			const MAIN_PACK = '$GODOT_BASENAME.pck';
 			const EXTRA_ARGS = JSON.parse('$GODOT_ARGS');
+			const GDNATIVE_LIBS = [$GODOT_GDNATIVE_LIBS];
 			const INDETERMINATE_STATUS_STEP_MS = 100;
 			const FULL_WINDOW = $GODOT_FULL_WINDOW;
 
@@ -263,6 +264,7 @@ $GODOT_HEAD_INCLUDE
 			} else {
 				setStatusMode('indeterminate');
 				engine.setCanvas(canvas);
+				engine.setGDNativeLibraries(GDNATIVE_LIBS);
 				engine.startGame(EXECUTABLE_NAME, MAIN_PACK, EXTRA_ARGS).then(() => {
 					setStatusMode('hidden');
 					initializing = false;

+ 45 - 15
platform/javascript/export/export.cpp

@@ -85,36 +85,44 @@ public:
 		// Wrong protocol
 		ERR_FAIL_COND_MSG(req[0] != "GET" || req[2] != "HTTP/1.1", "Invalid method or HTTP version.");
 
-		String filepath = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmp_js_export");
+		const String cache_path = EditorSettings::get_singleton()->get_cache_dir();
 		const String basereq = "/tmp_js_export";
-		String ctype = "";
+		String filepath;
+		String ctype;
 		if (req[1] == basereq + ".html") {
-			filepath += ".html";
+			filepath = cache_path.plus_file(req[1].get_file());
 			ctype = "text/html";
 		} else if (req[1] == basereq + ".js") {
-			filepath += ".js";
+			filepath = cache_path.plus_file(req[1].get_file());
 			ctype = "application/javascript";
 		} else if (req[1] == basereq + ".audio.worklet.js") {
-			filepath += ".audio.worklet.js";
+			filepath = cache_path.plus_file(req[1].get_file());
 			ctype = "application/javascript";
 		} else if (req[1] == basereq + ".worker.js") {
-			filepath += ".worker.js";
+			filepath = cache_path.plus_file(req[1].get_file());
 			ctype = "application/javascript";
 		} else if (req[1] == basereq + ".pck") {
-			filepath += ".pck";
+			filepath = cache_path.plus_file(req[1].get_file());
 			ctype = "application/octet-stream";
 		} else if (req[1] == basereq + ".png" || req[1] == "/favicon.png") {
 			// Also allow serving the generated favicon for a smoother loading experience.
 			if (req[1] == "/favicon.png") {
 				filepath = EditorSettings::get_singleton()->get_cache_dir().plus_file("favicon.png");
 			} else {
-				filepath += ".png";
+				filepath = basereq + ".png";
 			}
 			ctype = "image/png";
+		} else if (req[1] == basereq + ".side.wasm") {
+			filepath = cache_path.plus_file(req[1].get_file());
+			ctype = "application/wasm";
 		} else if (req[1] == basereq + ".wasm") {
-			filepath += ".wasm";
+			filepath = cache_path.plus_file(req[1].get_file());
 			ctype = "application/wasm";
-		} else {
+		} else if (req[1].ends_with(".wasm")) {
+			filepath = cache_path.plus_file(req[1].get_file()); // TODO dangerous?
+			ctype = "application/wasm";
+		}
+		if (filepath.empty() || !FileAccess::exists(filepath)) {
 			String s = "HTTP/1.1 404 Not Found\r\n";
 			s += "Connection: Close\r\n";
 			s += "\r\n";
@@ -205,7 +213,7 @@ class EditorExportPlatformJavaScript : public EditorExportPlatform {
 	Ref<ImageTexture> stop_icon;
 	int menu_options;
 
-	void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags);
+	void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects);
 
 private:
 	Ref<EditorHTTPServer> server;
@@ -250,7 +258,7 @@ public:
 	~EditorExportPlatformJavaScript();
 };
 
-void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags) {
+void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects) {
 	String str_template = String::utf8(reinterpret_cast<const char *>(p_html.ptr()), p_html.size());
 	String str_export;
 	Vector<String> lines = str_template.split("\n");
@@ -258,6 +266,10 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re
 	String flags_json;
 	gen_export_flags(flags, p_flags);
 	flags_json = JSON::print(flags);
+	String libs;
+	for (int i = 0; i < p_shared_objects.size(); i++) {
+		libs += "\"" + p_shared_objects[i].path.get_file() + "\",";
+	}
 
 	for (int i = 0; i < lines.size(); i++) {
 		String current_line = lines[i];
@@ -265,6 +277,7 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re
 		current_line = current_line.replace("$GODOT_PROJECT_NAME", ProjectSettings::get_singleton()->get_setting("application/config/name"));
 		current_line = current_line.replace("$GODOT_HEAD_INCLUDE", p_preset->get("html/head_include"));
 		current_line = current_line.replace("$GODOT_FULL_WINDOW", p_preset->get("html/full_window_size") ? "true" : "false");
+		current_line = current_line.replace("$GODOT_GDNATIVE_LIBS", libs);
 		current_line = current_line.replace("$GODOT_DEBUG_ENABLED", p_debug ? "true" : "false");
 		current_line = current_line.replace("$GODOT_ARGS", flags_json);
 		str_export += current_line + "\n";
@@ -393,12 +406,24 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
 		return ERR_FILE_NOT_FOUND;
 	}
 
+	Vector<SharedObject> shared_objects;
 	String pck_path = p_path.get_basename() + ".pck";
-	Error error = save_pack(p_preset, pck_path);
+	Error error = save_pack(p_preset, pck_path, &shared_objects);
 	if (error != OK) {
 		EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + pck_path);
 		return error;
 	}
+	DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+	for (int i = 0; i < shared_objects.size(); i++) {
+		String dst = p_path.get_base_dir().plus_file(shared_objects[i].path.get_file());
+		error = da->copy(shared_objects[i].path, dst);
+		if (error != OK) {
+			EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + shared_objects[i].path.get_file());
+			memdelete(da);
+			return error;
+		}
+	}
+	memdelete(da);
 
 	FileAccess *src_f = nullptr;
 	zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
@@ -437,14 +462,18 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
 			if (!custom_html.empty()) {
 				continue;
 			}
-			_fix_html(data, p_preset, p_path.get_file().get_basename(), p_debug, p_flags);
+			_fix_html(data, p_preset, p_path.get_file().get_basename(), p_debug, p_flags, shared_objects);
 			file = p_path.get_file();
 
 		} else if (file == "godot.js") {
 			file = p_path.get_file().get_basename() + ".js";
+
 		} else if (file == "godot.worker.js") {
 			file = p_path.get_file().get_basename() + ".worker.js";
 
+		} else if (file == "godot.side.wasm") {
+			file = p_path.get_file().get_basename() + ".side.wasm";
+
 		} else if (file == "godot.audio.worklet.js") {
 			file = p_path.get_file().get_basename() + ".audio.worklet.js";
 
@@ -475,7 +504,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
 		buf.resize(f->get_len());
 		f->get_buffer(buf.ptrw(), buf.size());
 		memdelete(f);
-		_fix_html(buf, p_preset, p_path.get_file().get_basename(), p_debug, p_flags);
+		_fix_html(buf, p_preset, p_path.get_file().get_basename(), p_debug, p_flags, shared_objects);
 
 		f = FileAccess::open(p_path, FileAccess::WRITE);
 		if (!f) {
@@ -577,6 +606,7 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese
 		DirAccess::remove_file_or_error(basepath + ".audio.worklet.js");
 		DirAccess::remove_file_or_error(basepath + ".pck");
 		DirAccess::remove_file_or_error(basepath + ".png");
+		DirAccess::remove_file_or_error(basepath + ".side.wasm");
 		DirAccess::remove_file_or_error(basepath + ".wasm");
 		DirAccess::remove_file_or_error(EditorSettings::get_singleton()->get_cache_dir().plus_file("favicon.png"));
 		return err;

+ 1 - 1
platform/javascript/js/dynlink.pre.js

@@ -1 +1 @@
-Module['dynamicLibraries'] = [Module['thisProgram'] + '.side.wasm'];
+Module['dynamicLibraries'] = [Module['thisProgram'] + '.side.wasm'].concat(Module['dynamicLibraries'] ? Module['dynamicLibraries'] : []);

+ 7 - 0
platform/javascript/js/engine/engine.js

@@ -34,6 +34,7 @@ const Engine = (function () {
 		this.onExecute = null;
 		this.onExit = null;
 		this.persistentPaths = ['/userfs'];
+		this.gdnativeLibs = [];
 	}
 
 	Engine.prototype.init = /** @param {string=} basePath */ function (basePath) {
@@ -61,6 +62,7 @@ const Engine = (function () {
 			// Emscripten configuration.
 			config['thisProgram'] = me.executableName;
 			config['noExitRuntime'] = true;
+			config['dynamicLibraries'] = me.gdnativeLibs;
 			Godot(config).then(function (module) {
 				module['initFS'](me.persistentPaths).then(function (fs_err) {
 					me.rtenv = module;
@@ -249,6 +251,10 @@ const Engine = (function () {
 		this.persistentPaths = persistentPaths;
 	};
 
+	Engine.prototype.setGDNativeLibraries = function (gdnativeLibs) {
+		this.gdnativeLibs = gdnativeLibs;
+	};
+
 	Engine.prototype.requestQuit = function () {
 		if (this.rtenv) {
 			this.rtenv['request_quit']();
@@ -277,6 +283,7 @@ const Engine = (function () {
 	Engine.prototype['setOnExit'] = Engine.prototype.setOnExit;
 	Engine.prototype['copyToFS'] = Engine.prototype.copyToFS;
 	Engine.prototype['setPersistentPaths'] = Engine.prototype.setPersistentPaths;
+	Engine.prototype['setGDNativeLibraries'] = Engine.prototype.setGDNativeLibraries;
 	Engine.prototype['requestQuit'] = Engine.prototype.requestQuit;
 	return Engine;
 }());

+ 8 - 0
platform/javascript/os_javascript.cpp

@@ -43,6 +43,7 @@
 #include "modules/websocket/remote_debugger_peer_websocket.h"
 #endif
 
+#include <dlfcn.h>
 #include <emscripten.h>
 #include <stdlib.h>
 
@@ -187,6 +188,13 @@ bool OS_JavaScript::is_userfs_persistent() const {
 	return idb_available;
 }
 
+Error OS_JavaScript::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
+	String path = p_path.get_file();
+	p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
+	ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ". Error: " + dlerror());
+	return OK;
+}
+
 OS_JavaScript *OS_JavaScript::get_singleton() {
 	return static_cast<OS_JavaScript *>(OS::get_singleton());
 }

+ 1 - 0
platform/javascript/os_javascript.h

@@ -87,6 +87,7 @@ public:
 	String get_user_data_dir() const override;
 
 	bool is_userfs_persistent() const override;
+	Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) override;
 
 	void resume_audio();