#include "display_server_web.h"
#include "godot_js.h"
#include "os_web.h"
#include "core/config/engine.h"
#include "core/io/resource_loader.h"
#include "main/main.h"
#include "scene/main/scene_tree.h"
#include "scene/main/window.h"
#ifdef TOOLS_ENABLED
#include "editor/web_tools_editor_plugin.h"
#endif
#include <emscripten/emscripten.h>
#include <cstdlib>
static OS_Web *os = nullptr;
#ifndef PROXY_TO_PTHREAD_ENABLED
static uint64_t target_ticks = 0;
#endif
static bool main_started = false;
static bool shutdown_complete = false;
void exit_callback() {
if (!shutdown_complete) {
return;
}
if (main_started) {
Main::cleanup();
main_started = false;
}
int exit_code = OS_Web::get_singleton()->get_exit_code();
memdelete(os);
os = nullptr;
emscripten_force_exit(exit_code);
}
void cleanup_after_sync() {
shutdown_complete = true;
}
void main_loop_callback() {
#ifndef PROXY_TO_PTHREAD_ENABLED
uint64_t current_ticks = os->get_ticks_usec();
#endif
bool force_draw = DisplayServerWeb::get_singleton()->check_size_force_redraw();
if (force_draw) {
Main::force_redraw();
#ifndef PROXY_TO_PTHREAD_ENABLED
} else if (current_ticks < target_ticks) {
return;
#endif
}
#ifndef PROXY_TO_PTHREAD_ENABLED
int max_fps = Engine::get_singleton()->get_max_fps();
if (max_fps > 0) {
if (current_ticks - target_ticks > 1000000) {
target_ticks = current_ticks;
}
target_ticks += (uint64_t)(1000000 / max_fps);
}
#endif
if (os->main_loop_iterate()) {
emscripten_cancel_main_loop();
emscripten_set_main_loop(exit_callback, -1, false);
godot_js_os_finish_async(cleanup_after_sync);
}
}
void print_web_header() {
char *emscripten_version_char = godot_js_emscripten_get_version();
String emscripten_version = vformat("Emscripten %s", emscripten_version_char);
free(emscripten_version_char);
String thread_support = OS::get_singleton()->has_feature("threads")
? "multi-threaded"
: "single-threaded";
String extensions_support = OS::get_singleton()->has_feature("web_extensions")
? "GDExtension support"
: "no GDExtension support";
Vector<String> build_configuration = { emscripten_version, thread_support, extensions_support };
print_line(vformat("Build configuration: %s.", String(", ").join(build_configuration)));
}
extern EMSCRIPTEN_KEEPALIVE int godot_web_main(int argc, char *argv[]) {
os = new OS_Web();
#ifdef TOOLS_ENABLED
WebToolsEditorPlugin::initialize();
#endif
TEST_MAIN_OVERRIDE
Error err = Main::setup(argv[0], argc - 1, &argv[1]);
if (err != OK) {
emscripten_set_main_loop(exit_callback, -1, false);
godot_js_os_finish_async(cleanup_after_sync);
if (err == ERR_HELP) {
return EXIT_SUCCESS;
}
return EXIT_FAILURE;
}
print_web_header();
main_started = true;
ResourceLoader::set_abort_on_missing_resources(false);
int ret = Main::start();
os->set_exit_code(ret);
os->get_main_loop()->initialize();
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_project_manager_hint() && FileAccess::exists("/tmp/preload.zip")) {
PackedStringArray ps;
ps.push_back("/tmp/preload.zip");
SceneTree::get_singleton()->get_root()->emit_signal(SNAME("files_dropped"), ps);
}
#endif
emscripten_set_main_loop(main_loop_callback, -1, false);
main_loop_callback();
return os->get_exit_code();
}