Skip to content

Commit

Permalink
Make GDKotlin states even more granular to prepare for reloading.
Browse files Browse the repository at this point in the history
  • Loading branch information
CedNaru committed Apr 27, 2024
1 parent 9377746 commit a0dd415
Show file tree
Hide file tree
Showing 4 changed files with 180 additions and 149 deletions.
4 changes: 2 additions & 2 deletions register_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ void initialize_kotlin_jvm_module(ModuleInitializationLevel p_level) {
#endif

if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) {
GDKotlin::get_instance().init();
GDKotlin::get_instance().initialize_up_to(GDKotlin::State::CORE_LIBRARY_INITIALIZED);

GDREGISTER_ABSTRACT_CLASS(JvmScript);
GDREGISTER_CLASS(GdjScript);
Expand Down Expand Up @@ -89,5 +89,5 @@ void uninitialize_kotlin_jvm_module(ModuleInitializationLevel p_level) {
ScriptServer::unregister_language(jvm_language);
memdelete(jvm_language);

GDKotlin::get_instance().finish();
GDKotlin::get_instance().finalize_down_to(GDKotlin::State::NOT_STARTED);
}
306 changes: 164 additions & 142 deletions src/gd_kotlin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,6 @@
#include <core/io/resource_loader.h>
#include <main/main.h>

#define CHECK_AND_SET_STATE(cond, new_state) \
if (cond) { \
state = State::new_state; \
} else { \
return; \
}

GDKotlin& GDKotlin::get_instance() {
static GDKotlin instance;
return instance;
Expand All @@ -26,6 +19,109 @@ GDKotlin::State GDKotlin::get_state() {
return state;
}

const JvmUserConfiguration& GDKotlin::get_configuration() {
return user_configuration;
}

#ifdef DYNAMIC_JVM
bool GDKotlin::load_dynamic_lib() {
String path_to_jvm_lib;
switch (user_configuration.vm_type) {
case jni::JvmType::JVM:
if (String embedded_jvm = get_path_to_embedded_jvm(); FileAccess::exists(embedded_jvm)) {
path_to_jvm_lib = embedded_jvm;
}
#ifdef TOOLS_ENABLED
else if (String environment_jvm = get_path_to_environment_jvm();
!environment_jvm.is_empty() && FileAccess::exists(environment_jvm)) {
LOG_WARNING(vformat("Godot-JVM: You really should embed a JRE in your project with jlink! See the "
"documentation if you don't know how to do that"));
path_to_jvm_lib = environment_jvm;
} else {
#ifdef MACOS_ENABLED
JVM_ERR_FAIL_V_MSG(
false,
"The environment variable JAVA_HOME is not found and there is no embedded JRE. If you "
"launched the editor through a double click on Godot.app, also make sure that JAVA_HOME "
"is set through launchctl: `launchctl setenv JAVA_HOME </path/to/jdk>`"
);
#else
JVM_ERR_FAIL_V_MSG(false, "The environment variable JAVA_HOME is not found and there is no embedded JRE.");
#endif
}
#else
else {
JVM_ERR_FAIL_V_MSG(false, vformat("No embedded JRE found at: %s!", get_path_to_embedded_jvm()));
}
#endif

break;
case jni::JvmType::GRAAL_NATIVE_IMAGE:
if (String native_jvm = get_path_to_native_image(); FileAccess::exists(native_jvm)) {
path_to_jvm_lib = native_jvm;
} else {
JVM_ERR_FAIL_V_MSG(
false,
"Cannot find Graal VM user code native image! /n This usually happens when you "
"define that your project should be executed using graalvm but did not "
"successfully "
"compile your project and thus usercode.(sh, dll, dylib) cannot be found."
);
}
break;
default:
// Sanity check. Should never happen
JVM_CRASH_NOW_MSG("Tried to load a VM that's neither the JVM nor Graal Native Image");
}

if (OS::get_singleton()->open_dynamic_library(path_to_jvm_lib, jvm_dynamic_library_handle) != OK) {
JVM_ERR_FAIL_V_MSG(false, vformat("Failed to load the jvm dynamic library from path %s!", path_to_jvm_lib));
}
return true;
}

#ifdef TOOLS_ENABLED
String GDKotlin::get_path_to_embedded_jvm() {
String godot_path {String(RES_DIRECTORY).path_join(EMBEDDED_JRE_DIRECTORY).path_join(RELATIVE_JVM_LIB_PATH)};
return ProjectSettings::get_singleton()->globalize_path(godot_path);
}

String GDKotlin::get_path_to_native_image() {
String godot_path {String(BUILD_DIRECTORY) + String(GRAAL_NATIVE_IMAGE_FILE)};
return ProjectSettings::get_singleton()->globalize_path(godot_path);
}

String GDKotlin::get_path_to_environment_jvm() {
String javaHome {OS::get_singleton()->get_environment("JAVA_HOME")};
if (javaHome.is_empty()) { return javaHome; }
return javaHome.path_join(RELATIVE_JVM_LIB_PATH);
}

#else

String GDKotlin::get_path_to_embedded_jvm() {
return OS::get_singleton()
->get_executable_path()
.get_base_dir()
#if defined(MACOS_ENABLED)
.path_join("../PlugIns/")
#endif
.path_join(EMBEDDED_JRE_DIRECTORY)
.path_join(RELATIVE_JVM_LIB_PATH);
}

String GDKotlin::get_path_to_native_image() {
return ProjectSettings::get_singleton()->globalize_path(copy_new_file_to_user_dir(GRAAL_NATIVE_IMAGE_FILE));
}
#endif

void GDKotlin::unload_dynamic_lib() {
if (OS::get_singleton()->close_dynamic_library(jvm_dynamic_library_handle) != OK) {
JVM_ERR_FAIL_MSG("Failed to close the jvm dynamic library!");
}
}
#endif

void GDKotlin::fetch_user_configuration() {
bool invalid_file_content = false;
bool configuration_file_exist = FileAccess::exists(JVM_CONFIGURATION_PATH);
Expand Down Expand Up @@ -155,7 +251,7 @@ bool GDKotlin::initialize_core_library() {
return true;
}

void GDKotlin::load_user_code() {
bool GDKotlin::load_user_code() {
#ifdef TOOLS_ENABLED
String project_path {ProjectSettings::get_singleton()->globalize_path(RES_DIRECTORY)};
#else
Expand All @@ -166,12 +262,15 @@ void GDKotlin::load_user_code() {
if (user_configuration.vm_type == jni::JvmType::GRAAL_NATIVE_IMAGE) {
state = State::JVM_SCRIPTS_INITIALIZED;
bootstrap->init(env, project_path, "", jni::JObject(nullptr));
return true;
} else {
#ifdef TOOLS_ENABLED
String user_code_path {String(BUILD_DIRECTORY) + String(USER_CODE_FILE)};
#else
String user_code_path {copy_new_file_to_user_dir(USER_CODE_FILE)};
#endif
if (!FileAccess::exists(user_code_path)) { return false; }

LOG_VERBOSE(vformat("Loading usercode file at: %s", user_code_path));
// TODO: Rework this part when cpp reloading done, can't check what's happening in the Kotlin code from here.
ClassLoader* user_class_loader = ClassLoader::create_instance(
Expand All @@ -186,162 +285,84 @@ void GDKotlin::load_user_code() {
user_class_loader->get_wrapped()
);
delete user_class_loader;
CHECK_AND_SET_STATE(FileAccess::exists(user_code_path), JVM_SCRIPTS_INITIALIZED)
return true;
}
}

void GDKotlin::init() {
fetch_user_configuration();
set_jvm_options();

#ifdef DYNAMIC_JVM
CHECK_AND_SET_STATE(load_dynamic_lib(), JVM_LIBRARY_LOADED)
CHECK_AND_SET_STATE(JvmManager::initialize_or_get_jvm(jvm_dynamic_library_handle, user_configuration, jvm_options), JVM_STARTED)
#else
CHECK_AND_SET_STATE(JvmManager::initialize_or_get_jvm(nullptr, user_configuration, jvm_options), JVM_STARTED)
#endif

CHECK_AND_SET_STATE(load_bootstrap(), BOOTSTRAP_LOADED)
CHECK_AND_SET_STATE(initialize_core_library(), CORE_LIBRARY_INITIALIZED)
void GDKotlin::unload_user_code() {
jni::Env env {jni::Jvm::current_env()};
bootstrap->finish(env);
JvmScriptManager::get_instance().clear();
TypeManager::get_instance().clear();
}

void GDKotlin::finish() {
if (state >= State::JVM_SCRIPTS_INITIALIZED) {
jni::Env env {jni::Jvm::current_env()};
bootstrap->finish(env);
JvmScriptManager::get_instance().clear();
TypeManager::get_instance().clear();
}

if (state >= State::CORE_LIBRARY_INITIALIZED) {
jni::Env env {jni::Jvm::current_env()};
if (!user_configuration.disable_gc) {
MemoryManager::get_instance().close(env);
void GDKotlin::finalize_core_library() const {
jni::Env env {jni::Jvm::current_env()};
if (!user_configuration.disable_gc) {
MemoryManager::get_instance().close(env);

while (!MemoryManager::get_instance().is_closed(env)) {
OS::get_singleton()->delay_usec(600000);
}
LOG_VERBOSE("JVM GC thread was closed");
MemoryManager::get_instance().clean_up(env);
while (!MemoryManager::get_instance().is_closed(env)) {
OS::get_singleton()->delay_usec(600000);
}
JvmManager::destroy_jni_classes();
}

if (state >= State::BOOTSTRAP_LOADED) {
delete bootstrap;
bootstrap = nullptr;
delete bootstrap_class_loader;
bootstrap_class_loader = nullptr;
LOG_VERBOSE("JVM GC thread was closed");
MemoryManager::get_instance().clean_up(env);
}

if (state >= State::JVM_STARTED) { JvmManager::close_jvm(); }
#ifdef DYNAMIC_JVM
if (state >= State::JVM_LIBRARY_LOADED) { unload_dynamic_lib(); }
#endif

state = State::NOT_STARTED;
JvmManager::destroy_jni_classes();
}

const JvmUserConfiguration& GDKotlin::get_configuration() {
return user_configuration;
void GDKotlin::unload_boostrap() {
delete bootstrap;
bootstrap = nullptr;
delete bootstrap_class_loader;
bootstrap_class_loader = nullptr;
}

#ifdef DYNAMIC_JVM
bool GDKotlin::load_dynamic_lib() {
String path_to_jvm_lib;
switch (user_configuration.vm_type) {
case jni::JvmType::JVM:
if (String embedded_jvm = get_path_to_embedded_jvm(); FileAccess::exists(embedded_jvm)) {
path_to_jvm_lib = embedded_jvm;
}
#ifdef TOOLS_ENABLED
else if (String environment_jvm = get_path_to_environment_jvm();
!environment_jvm.is_empty() && FileAccess::exists(environment_jvm)) {
LOG_WARNING(vformat("Godot-JVM: You really should embed a JRE in your project with jlink! See the "
"documentation if you don't know how to do that"));
path_to_jvm_lib = environment_jvm;
} else {
#ifdef MACOS_ENABLED
JVM_ERR_FAIL_V_MSG(
false,
"The environment variable JAVA_HOME is not found and there is no embedded JRE. If you "
"launched the editor through a double click on Godot.app, also make sure that JAVA_HOME "
"is set through launchctl: `launchctl setenv JAVA_HOME </path/to/jdk>`"
);
#else
JVM_ERR_FAIL_V_MSG(false, "The environment variable JAVA_HOME is not found and there is no embedded JRE.");
#endif
}
#else
else {
JVM_ERR_FAIL_V_MSG(false, vformat("No embedded JRE found at: %s!", get_path_to_embedded_jvm()));
}
#endif

break;
case jni::JvmType::GRAAL_NATIVE_IMAGE:
if (String native_jvm = get_path_to_native_image(); FileAccess::exists(native_jvm)) {
path_to_jvm_lib = native_jvm;
} else {
JVM_ERR_FAIL_V_MSG(
false,
"Cannot find Graal VM user code native image! /n This usually happens when you "
"define that your project should be executed using graalvm but did not "
"successfully "
"compile your project and thus usercode.(sh, dll, dylib) cannot be found."
);
}
break;
default:
// Sanity check. Should never happen
JVM_CRASH_NOW_MSG("Tried to load a VM that's neither the JVM nor Graal Native Image");
}

if (OS::get_singleton()->open_dynamic_library(path_to_jvm_lib, jvm_dynamic_library_handle) != OK) {
JVM_ERR_FAIL_V_MSG(false, vformat("Failed to load the jvm dynamic library from path %s!", path_to_jvm_lib));
#define SET_LOADING_STATE(cond, new_state, target_state) \
if (state < State::new_state) { \
if (!cond) { return; } \
state = State::new_state; \
if (new_state == target_state) { return; } \
}
return true;
}

#ifdef TOOLS_ENABLED
String GDKotlin::get_path_to_embedded_jvm() {
String godot_path {String(RES_DIRECTORY).path_join(EMBEDDED_JRE_DIRECTORY).path_join(RELATIVE_JVM_LIB_PATH)};
return ProjectSettings::get_singleton()->globalize_path(godot_path);
}
void GDKotlin::initialize_up_to(State target_state) {
fetch_user_configuration();
set_jvm_options();

String GDKotlin::get_path_to_native_image() {
String godot_path {String(BUILD_DIRECTORY) + String(GRAAL_NATIVE_IMAGE_FILE)};
return ProjectSettings::get_singleton()->globalize_path(godot_path);
#ifdef DYNAMIC_JVM
SET_LOADING_STATE(load_dynamic_lib(), JVM_LIBRARY_LOADED, target_state)
SET_LOADING_STATE(JvmManager::initialize_or_get_jvm(jvm_dynamic_library_handle, user_configuration, jvm_options), JVM_STARTED, target_state)
#else
SET_LOADING_STATE(JvmManager::initialize_or_get_jvm(nullptr, user_configuration, jvm_options), JVM_STARTED, target_state)
#endif
SET_LOADING_STATE(load_bootstrap(), BOOTSTRAP_LOADED, target_state)
SET_LOADING_STATE(initialize_core_library(), CORE_LIBRARY_INITIALIZED, target_state)
SET_LOADING_STATE(load_user_code(), JVM_SCRIPTS_INITIALIZED, target_state)
}

String GDKotlin::get_path_to_environment_jvm() {
String javaHome {OS::get_singleton()->get_environment("JAVA_HOME")};
if (javaHome.is_empty()) { return javaHome; }
return javaHome.path_join(RELATIVE_JVM_LIB_PATH);
}
#define UNSET_LOADING_STATE(function, new_state, target_state) \
if (state > State::new_state) { \
function; \
state = State::new_state; \
if (new_state == target_state) { return; } \
}

void GDKotlin::finalize_down_to(State target_state) {
UNSET_LOADING_STATE(unload_user_code(), CORE_LIBRARY_INITIALIZED, target_state)
UNSET_LOADING_STATE(finalize_core_library(), BOOTSTRAP_LOADED, target_state)
UNSET_LOADING_STATE(unload_boostrap(), JVM_STARTED, target_state)
#ifdef DYNAMIC_JVM
UNSET_LOADING_STATE(JvmManager::close_jvm(), JVM_LIBRARY_LOADED, target_state)
UNSET_LOADING_STATE(unload_dynamic_lib(), NOT_STARTED, target_state)
#else

String GDKotlin::get_path_to_embedded_jvm() {
return OS::get_singleton()
->get_executable_path()
.get_base_dir()
#if defined(MACOS_ENABLED)
.path_join("../PlugIns/")
UNSET_LOADING_STATE(JvmManager::close_jvm(), NOT_STARTED, target_state)
#endif
.path_join(EMBEDDED_JRE_DIRECTORY)
.path_join(RELATIVE_JVM_LIB_PATH);
}

String GDKotlin::get_path_to_native_image() {
return ProjectSettings::get_singleton()->globalize_path(copy_new_file_to_user_dir(GRAAL_NATIVE_IMAGE_FILE));
}
#endif

void GDKotlin::unload_dynamic_lib() {
if (OS::get_singleton()->close_dynamic_library(jvm_dynamic_library_handle) != OK) {
JVM_ERR_FAIL_MSG("Failed to close the jvm dynamic library!");
}
#ifdef DYNAMIC_JVM
void GDKotlin::reload_user_code() {
finalize_down_to(JVM_STARTED);
initialize_up_to(JVM_SCRIPTS_INITIALIZED);
}
#endif

Expand Down Expand Up @@ -397,8 +418,9 @@ void GDKotlin::validate_state() {
if (state == State::CORE_LIBRARY_INITIALIZED || state == State::JVM_SCRIPTS_INITIALIZED) { return; }

if (invalid) {
finish();
finalize_down_to(NOT_STARTED);
OS::get_singleton()->alert(warning + cause + pre_hint + hint, "Kotlin/JVM module initialization error");
}
}

#endif
Loading

0 comments on commit a0dd415

Please sign in to comment.