This commit is contained in:
MaidOpi
2026-05-10 22:29:56 +08:00
commit 0428927f5f
54 changed files with 15200 additions and 0 deletions
+174
View File
@@ -0,0 +1,174 @@
#include "leanclr_hot_reload_host.h"
#include "leanclr_runtime_bridge.h"
#include <godot_cpp/classes/engine.hpp>
#include <godot_cpp/classes/file_access.hpp>
#include <godot_cpp/classes/input_event.hpp>
#include <godot_cpp/classes/os.hpp>
#include <godot_cpp/classes/scene_tree.hpp>
#include <godot_cpp/variant/packed_string_array.hpp>
#include <godot_cpp/variant/node_path.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
namespace godot
{
namespace
{
const char* RELOAD_MARKER_PATH = "res://leanclr/live_reload.txt";
const char* RELOAD_TYPE_NAME = "Game.HotReloadSmoke";
bool is_editor_scene_context()
{
Engine* engine = Engine::get_singleton();
if (engine != nullptr && (engine->is_editor_hint()))
{
return true;
}
PackedStringArray args = OS::get_singleton()->get_cmdline_args();
for (int32_t i = 0; i < args.size(); ++i)
{
if (args[i] == String("--editor") || args[i] == String("-e"))
{
return true;
}
}
return false;
}
}
void LeanCLRHotReloadHost::_bind_methods()
{
ClassDB::bind_method(D_METHOD("forward_input", "event"), &LeanCLRHotReloadHost::forward_input);
}
LeanCLRHotReloadHost::~LeanCLRHotReloadHost()
{
LeanCLRRuntimeBridge::release_script_object(managed_object);
managed_object = nullptr;
}
void LeanCLRHotReloadHost::_notification(int p_what)
{
if (p_what == NOTIFICATION_READY)
{
if (is_editor_scene_context() || !is_inside_tree() || (get_tree() != nullptr && get_tree()->get_edited_scene_root() != nullptr))
{
set_process(false);
return;
}
set_process(true);
check_reload_marker();
}
else if (p_what == NOTIFICATION_PROCESS)
{
if (is_editor_scene_context() || !is_inside_tree() || (get_tree() != nullptr && get_tree()->get_edited_scene_root() != nullptr))
{
return;
}
const double delta = get_process_delta_time();
if (managed_object != nullptr)
{
LeanCLRRuntimeBridge::invoke_script_process(managed_object, delta);
}
elapsed += delta;
if (elapsed >= 0.25)
{
elapsed = 0.0;
check_reload_marker();
}
}
}
void LeanCLRHotReloadHost::forward_input(const Ref<InputEvent>& p_event)
{
if (managed_object == nullptr || !p_event.is_valid() || is_editor_scene_context() || !is_inside_tree() ||
(get_tree() != nullptr && get_tree()->get_edited_scene_root() != nullptr))
{
return;
}
LeanCLRRuntimeBridge::invoke_script_method(managed_object, "_Input", p_event.ptr());
}
void LeanCLRHotReloadHost::check_reload_marker()
{
String assembly_name = "Game";
if (FileAccess::file_exists(RELOAD_MARKER_PATH))
{
assembly_name = FileAccess::get_file_as_string(RELOAD_MARKER_PATH).strip_edges();
if (assembly_name.is_empty())
{
assembly_name = "Game";
}
}
if (assembly_name == loaded_assembly_name)
{
return;
}
reload_managed_object(assembly_name);
}
void LeanCLRHotReloadHost::reload_managed_object(const String& p_assembly_name)
{
if (p_assembly_name == String("Game"))
{
LeanCLRRuntimeBridge::release_script_object(managed_object);
managed_object = nullptr;
loaded_assembly_name = p_assembly_name;
UtilityFunctions::print("LeanCLR live reload: using attached script assembly = ", loaded_assembly_name);
return;
}
Object* owner = this;
Node* parent = get_parent();
if (parent != nullptr)
{
Node* script_owner = parent->get_node_or_null(NodePath("FlappyScript"));
if (script_owner != nullptr)
{
owner = script_owner;
}
}
void* previous_object = managed_object != nullptr ? managed_object : LeanCLRRuntimeBridge::get_script_object_for_owner(owner);
Variant custom_state;
const bool has_custom_state = previous_object != nullptr &&
LeanCLRRuntimeBridge::has_script_method(previous_object, "CaptureHotReloadState", 0) &&
LeanCLRRuntimeBridge::invoke_script_method(previous_object, "CaptureHotReloadState", nullptr, 0, &custom_state);
void* next_object = LeanCLRRuntimeBridge::create_script_object(p_assembly_name, RELOAD_TYPE_NAME, owner);
if (next_object == nullptr)
{
UtilityFunctions::printerr("LeanCLR live reload: failed to load ", p_assembly_name, ": ", LeanCLRRuntimeBridge::get_last_error());
return;
}
const int32_t migrated_fields = LeanCLRRuntimeBridge::migrate_script_state(previous_object, next_object);
if (has_custom_state && LeanCLRRuntimeBridge::has_script_method(next_object, "RestoreHotReloadState", 1))
{
LeanCLRRuntimeBridge::invoke_script_method(next_object, "RestoreHotReloadState", custom_state);
}
if (managed_object != nullptr)
{
LeanCLRRuntimeBridge::release_script_object(managed_object);
}
managed_object = next_object;
loaded_assembly_name = p_assembly_name;
UtilityFunctions::print("LeanCLR live reload: loaded assembly = ", loaded_assembly_name);
UtilityFunctions::print("LeanCLR live reload: migrated fields = ", migrated_fields);
LeanCLRRuntimeBridge::invoke_script_ready(managed_object);
LeanCLRRuntimeBridge::invoke_script_method(managed_object, "OnHotReloaded");
}
} // namespace godot
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/classes/input_event.hpp>
#include <godot_cpp/variant/string.hpp>
namespace godot
{
class LeanCLRHotReloadHost : public Node
{
GDCLASS(LeanCLRHotReloadHost, Node)
public:
~LeanCLRHotReloadHost();
void forward_input(const Ref<InputEvent>& p_event);
protected:
static void _bind_methods();
void _notification(int p_what);
private:
void check_reload_marker();
void reload_managed_object(const String& p_assembly_name);
void* managed_object = nullptr;
String loaded_assembly_name;
double elapsed = 0.0;
};
} // namespace godot
+40
View File
@@ -0,0 +1,40 @@
#include "leanclr_main_node.h"
#include "leanclr_runtime_bridge.h"
namespace godot
{
void LeanCLRMain::_bind_methods()
{
}
LeanCLRMain::~LeanCLRMain()
{
LeanCLRRuntimeBridge::release_script_object(managed_object);
managed_object = nullptr;
}
void LeanCLRMain::_notification(int p_what)
{
if (p_what != NOTIFICATION_READY || ready_invoked)
{
return;
}
ready_invoked = true;
ensure_managed_object();
LeanCLRRuntimeBridge::invoke_script_ready(managed_object);
}
void LeanCLRMain::ensure_managed_object()
{
if (managed_object != nullptr)
{
return;
}
managed_object = LeanCLRRuntimeBridge::create_script_object("Game", "Game.ClassDbMain", this);
}
} // namespace godot
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include <godot_cpp/classes/node.hpp>
namespace godot
{
class LeanCLRMain : public Node
{
GDCLASS(LeanCLRMain, Node)
public:
LeanCLRMain() = default;
~LeanCLRMain();
protected:
static void _bind_methods();
void _notification(int p_what);
private:
void ensure_managed_object();
void* managed_object = nullptr;
bool ready_invoked = false;
};
} // namespace godot
File diff suppressed because it is too large Load Diff
+59
View File
@@ -0,0 +1,59 @@
#pragma once
#include <godot_cpp/variant/string.hpp>
#include <vector>
namespace godot
{
class Object;
class InputEvent;
class Variant;
struct LeanCLRScriptPropertyInfo
{
String name;
int32_t type = 0;
int32_t hint = 0;
String hint_string;
int32_t usage = 6;
};
class LeanCLRRuntimeBridge
{
public:
static void set_assembly_directory(const String& p_directory);
static String get_assembly_directory();
static bool initialize();
static void shutdown();
static bool is_initialized();
static bool load_assembly(const String& p_assembly_name);
static bool invoke_static_entry(const String& p_assembly_name, const String& p_entry_point);
static void* create_script_object(const String& p_assembly_name, const String& p_type_name, Object* p_owner = nullptr);
static void release_script_object(void* p_managed_object);
static bool has_script_method(void* p_managed_object, const String& p_method, int32_t p_argument_count);
static bool invoke_script_method(void* p_managed_object, const String& p_method);
static bool invoke_script_method(void* p_managed_object, const String& p_method, float p_argument);
static bool invoke_script_method(void* p_managed_object, const String& p_method, double p_argument);
static bool invoke_script_method(void* p_managed_object, const String& p_method, const Variant& p_argument);
static bool invoke_script_method(void* p_managed_object, const String& p_method, const Variant* p_arguments, int32_t p_argument_count);
static bool invoke_script_method(void* p_managed_object, const String& p_method, const Variant* p_arguments, int32_t p_argument_count, Variant* r_return);
static bool invoke_script_method(void* p_managed_object, const String& p_method, InputEvent* p_argument);
static bool get_script_property(void* p_managed_object, const String& p_property, Variant* r_value);
static bool set_script_property(void* p_managed_object, const String& p_property, const Variant& p_value);
static bool get_script_property_list(void* p_managed_object, std::vector<LeanCLRScriptPropertyInfo>& r_properties);
static int32_t get_script_property_type(void* p_managed_object, const String& p_property, bool* r_is_valid);
static bool invoke_script_ready(void* p_managed_object);
static bool invoke_script_process(void* p_managed_object, double p_delta);
static void register_script_object(Object* p_owner, void* p_managed_object);
static void unregister_script_object(Object* p_owner, void* p_managed_object);
static void* get_script_object_for_owner(Object* p_owner);
static int32_t migrate_script_state(void* p_source_object, void* p_target_object);
static String get_last_error();
};
} // namespace godot
+773
View File
@@ -0,0 +1,773 @@
#include "leanclr_script.h"
#include "leanclr_runtime_bridge.h"
#include "leanclr_script_language.h"
#include <godot_cpp/classes/engine.hpp>
#include <godot_cpp/classes/script_language.hpp>
#include <godot_cpp/classes/scene_tree.hpp>
#include <godot_cpp/classes/input_event.hpp>
#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/classes/os.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/core/gdextension_interface_loader.hpp>
#include <godot_cpp/variant/packed_string_array.hpp>
#include <godot_cpp/variant/string_name.hpp>
#include <godot_cpp/variant/variant.hpp>
#include <godot_cpp/variant/string.hpp>
#include <gdextension_interface.h>
#include <vector>
namespace godot
{
namespace
{
struct LeanCLRScriptInstance
{
Object* owner = nullptr;
Ref<LeanCLRScript> script;
void* managed_object = nullptr;
bool ready_invoked = false;
};
LeanCLRScriptInstance* as_instance(GDExtensionScriptInstanceDataPtr p_instance)
{
return static_cast<LeanCLRScriptInstance*>(p_instance);
}
String pascal_virtual_name(const StringName& p_godot_method)
{
const String source = String(p_godot_method);
if (!source.begins_with("_"))
{
return String();
}
String result = "_";
PackedStringArray parts = source.substr(1).split("_");
for (int32_t i = 0; i < parts.size(); ++i)
{
String part = parts[i];
if (part.is_empty())
{
continue;
}
if (part == "2d")
{
result += "2D";
}
else if (part == "3d")
{
result += "3D";
}
else if (part == "gui")
{
result += "Gui";
}
else if (part == "rid")
{
result += "Rid";
}
else
{
result += part.substr(0, 1).to_upper() + part.substr(1);
}
}
return result;
}
String managed_virtual_name(const StringName& p_godot_method)
{
return pascal_virtual_name(p_godot_method);
}
bool is_editor_scene_context(Object* p_owner = nullptr)
{
Node* owner_node = Object::cast_to<Node>(p_owner);
if (owner_node != nullptr && owner_node->is_inside_tree() && owner_node->get_tree() != nullptr && owner_node->get_tree()->get_edited_scene_root() != nullptr)
{
return true;
}
Engine* engine = Engine::get_singleton();
if (engine != nullptr && (engine->is_editor_hint()))
{
return true;
}
PackedStringArray args = OS::get_singleton()->get_cmdline_args();
for (int32_t i = 0; i < args.size(); ++i)
{
if (args[i] == String("--editor") || args[i] == String("-e"))
{
return true;
}
}
return false;
}
int32_t managed_virtual_argument_count(const StringName& p_godot_method)
{
if (p_godot_method == StringName("_process") || p_godot_method == StringName("_physics_process") || p_godot_method == StringName("_input") ||
p_godot_method == StringName("_gui_input") || p_godot_method == StringName("_shortcut_input") || p_godot_method == StringName("_unhandled_input") ||
p_godot_method == StringName("_unhandled_key_input") || p_godot_method == StringName("_set_path_cache") ||
p_godot_method == StringName("_make_visible") || p_godot_method == StringName("_edit") || p_godot_method == StringName("_handles") ||
p_godot_method == StringName("_set_state") || p_godot_method == StringName("_get_unsaved_status") || p_godot_method == StringName("_set_window_layout") ||
p_godot_method == StringName("_get_window_layout") || p_godot_method == StringName("_forward_canvas_gui_input") ||
p_godot_method == StringName("_forward_canvas_draw_over_viewport") || p_godot_method == StringName("_forward_canvas_force_draw_over_viewport") ||
p_godot_method == StringName("_forward_3d_draw_over_viewport") || p_godot_method == StringName("_forward_3d_force_draw_over_viewport") ||
p_godot_method == StringName("_run_scene") || p_godot_method == StringName("_has_point") || p_godot_method == StringName("_get_tooltip") ||
p_godot_method == StringName("_get_drag_data") || p_godot_method == StringName("_make_custom_tooltip") ||
p_godot_method == StringName("_get_accessibility_container_name"))
{
return 1;
}
if (p_godot_method == StringName("_forward_3d_gui_input") || p_godot_method == StringName("_structured_text_parser") ||
p_godot_method == StringName("_can_drop_data") || p_godot_method == StringName("_drop_data"))
{
return 2;
}
return 0;
}
GDExtensionBool script_instance_set(GDExtensionScriptInstanceDataPtr p_instance, GDExtensionConstStringNamePtr p_name,
GDExtensionConstVariantPtr p_value)
{
LeanCLRScriptInstance* instance = as_instance(p_instance);
const StringName& name = *reinterpret_cast<const StringName*>(p_name);
const Variant& value = *reinterpret_cast<const Variant*>(p_value);
return LeanCLRRuntimeBridge::set_script_property(instance->managed_object, String(name), value);
}
GDExtensionBool script_instance_get(GDExtensionScriptInstanceDataPtr p_instance, GDExtensionConstStringNamePtr p_name, GDExtensionVariantPtr r_ret)
{
LeanCLRScriptInstance* instance = as_instance(p_instance);
const StringName& name = *reinterpret_cast<const StringName*>(p_name);
Variant value;
if (!LeanCLRRuntimeBridge::get_script_property(instance->managed_object, String(name), &value))
{
*reinterpret_cast<Variant*>(r_ret) = Variant();
return false;
}
*reinterpret_cast<Variant*>(r_ret) = value;
return true;
}
const GDExtensionPropertyInfo* script_instance_get_property_list(GDExtensionScriptInstanceDataPtr p_instance, uint32_t* r_count)
{
LeanCLRScriptInstance* instance = as_instance(p_instance);
std::vector<LeanCLRScriptPropertyInfo> properties;
if (!LeanCLRRuntimeBridge::get_script_property_list(instance->managed_object, properties) || properties.empty())
{
*r_count = 0;
return nullptr;
}
GDExtensionPropertyInfo* list = new GDExtensionPropertyInfo[properties.size()];
for (size_t i = 0; i < properties.size(); ++i)
{
list[i].type = static_cast<GDExtensionVariantType>(properties[i].type);
list[i].name = reinterpret_cast<GDExtensionStringNamePtr>(new StringName(properties[i].name));
list[i].class_name = reinterpret_cast<GDExtensionStringNamePtr>(new StringName());
list[i].hint = properties[i].hint;
list[i].hint_string = reinterpret_cast<GDExtensionStringPtr>(new String(properties[i].hint_string));
list[i].usage = properties[i].usage;
}
*r_count = static_cast<uint32_t>(properties.size());
return list;
}
void script_instance_free_property_list(GDExtensionScriptInstanceDataPtr p_instance, const GDExtensionPropertyInfo* p_list, uint32_t p_count)
{
(void)p_instance;
for (uint32_t i = 0; i < p_count; ++i)
{
delete reinterpret_cast<StringName*>(p_list[i].name);
delete reinterpret_cast<StringName*>(p_list[i].class_name);
delete reinterpret_cast<String*>(p_list[i].hint_string);
}
delete[] p_list;
}
GDExtensionObjectPtr script_instance_get_owner(GDExtensionScriptInstanceDataPtr p_instance)
{
LeanCLRScriptInstance* instance = as_instance(p_instance);
return instance->owner != nullptr ? instance->owner->_owner : nullptr;
}
void script_instance_get_property_state(GDExtensionScriptInstanceDataPtr p_instance, GDExtensionScriptInstancePropertyStateAdd p_add_func,
void* p_userdata)
{
LeanCLRScriptInstance* instance = as_instance(p_instance);
std::vector<LeanCLRScriptPropertyInfo> properties;
LeanCLRRuntimeBridge::get_script_property_list(instance->managed_object, properties);
for (size_t i = 0; i < properties.size(); ++i)
{
StringName name(properties[i].name);
Variant value;
if (LeanCLRRuntimeBridge::get_script_property(instance->managed_object, properties[i].name, &value))
{
p_add_func(&name, &value, p_userdata);
}
}
}
const GDExtensionMethodInfo* script_instance_get_method_list(GDExtensionScriptInstanceDataPtr p_instance, uint32_t* r_count)
{
(void)p_instance;
*r_count = 0;
return nullptr;
}
void script_instance_free_method_list(GDExtensionScriptInstanceDataPtr p_instance, const GDExtensionMethodInfo* p_list, uint32_t p_count)
{
(void)p_instance;
(void)p_list;
(void)p_count;
}
GDExtensionVariantType script_instance_get_property_type(GDExtensionScriptInstanceDataPtr p_instance, GDExtensionConstStringNamePtr p_name,
GDExtensionBool* r_is_valid)
{
LeanCLRScriptInstance* instance = as_instance(p_instance);
const StringName& name = *reinterpret_cast<const StringName*>(p_name);
bool is_valid = false;
int32_t type = LeanCLRRuntimeBridge::get_script_property_type(instance->managed_object, String(name), &is_valid);
*r_is_valid = is_valid;
return static_cast<GDExtensionVariantType>(type);
}
GDExtensionBool script_instance_has_method(GDExtensionScriptInstanceDataPtr p_instance, GDExtensionConstStringNamePtr p_name)
{
LeanCLRScriptInstance* instance = as_instance(p_instance);
const StringName& name = *reinterpret_cast<const StringName*>(p_name);
String managed_name = managed_virtual_name(name);
if (!managed_name.is_empty())
{
return LeanCLRRuntimeBridge::has_script_method(instance->managed_object, managed_name, managed_virtual_argument_count(name));
}
return LeanCLRRuntimeBridge::has_script_method(instance->managed_object, String(name), 0) ||
LeanCLRRuntimeBridge::has_script_method(instance->managed_object, String(name), 1);
}
void script_instance_invoke_ready(LeanCLRScriptInstance* p_instance)
{
if (p_instance->ready_invoked)
{
return;
}
p_instance->ready_invoked = true;
LeanCLRRuntimeBridge::invoke_script_ready(p_instance->managed_object);
}
GDExtensionInt script_instance_get_method_argument_count(GDExtensionScriptInstanceDataPtr p_instance, GDExtensionConstStringNamePtr p_name,
GDExtensionBool* r_is_valid)
{
*r_is_valid = script_instance_has_method(p_instance, p_name);
const StringName& name = *reinterpret_cast<const StringName*>(p_name);
String managed_name = managed_virtual_name(name);
if (!managed_name.is_empty())
{
return managed_virtual_argument_count(name);
}
return LeanCLRRuntimeBridge::has_script_method(as_instance(p_instance)->managed_object, String(name), 1) ? 1 : 0;
}
void script_instance_call(GDExtensionScriptInstanceDataPtr p_instance, GDExtensionConstStringNamePtr p_method,
const GDExtensionConstVariantPtr* p_args, GDExtensionInt p_argument_count, GDExtensionVariantPtr r_return,
GDExtensionCallError* r_error)
{
const StringName& method = *reinterpret_cast<const StringName*>(p_method);
LeanCLRScriptInstance* instance = as_instance(p_instance);
if (is_editor_scene_context(instance->owner) && !instance->script->_is_tool() && String(method).begins_with("_"))
{
*reinterpret_cast<Variant*>(r_return) = Variant();
r_error->error = GDEXTENSION_CALL_ERROR_INVALID_METHOD;
return;
}
String managed_name = managed_virtual_name(method);
if (managed_name.is_empty())
{
const String method_name = String(method);
if (LeanCLRRuntimeBridge::has_script_method(instance->managed_object, method_name, static_cast<int32_t>(p_argument_count)) ||
(p_argument_count > 1 && LeanCLRRuntimeBridge::has_script_method(instance->managed_object, method_name, 1)))
{
std::vector<Variant> arguments;
arguments.reserve(static_cast<size_t>(p_argument_count));
for (GDExtensionInt i = 0; i < p_argument_count; ++i)
{
arguments.push_back(*reinterpret_cast<const Variant*>(p_args[i]));
}
Variant return_value;
LeanCLRRuntimeBridge::invoke_script_method(instance->managed_object, method_name, arguments.data(), static_cast<int32_t>(arguments.size()), &return_value);
*reinterpret_cast<Variant*>(r_return) = return_value;
r_error->error = GDEXTENSION_CALL_OK;
return;
}
else
{
r_error->error = GDEXTENSION_CALL_ERROR_INVALID_METHOD;
}
}
else
{
if (method == StringName("_ready") && p_argument_count == 0)
{
script_instance_invoke_ready(instance);
r_error->error = GDEXTENSION_CALL_OK;
}
else
{
std::vector<Variant> arguments;
arguments.reserve(static_cast<size_t>(p_argument_count));
for (GDExtensionInt i = 0; i < p_argument_count; ++i)
{
arguments.push_back(*reinterpret_cast<const Variant*>(p_args[i]));
}
Variant return_value;
LeanCLRRuntimeBridge::invoke_script_method(instance->managed_object, managed_name, arguments.data(), static_cast<int32_t>(arguments.size()), &return_value);
*reinterpret_cast<Variant*>(r_return) = return_value;
r_error->error = GDEXTENSION_CALL_OK;
return;
}
}
*reinterpret_cast<Variant*>(r_return) = Variant();
}
void script_instance_notification(GDExtensionScriptInstanceDataPtr p_instance, int32_t p_what, GDExtensionBool p_reversed)
{
if (p_reversed)
{
return;
}
LeanCLRScriptInstance* instance = as_instance(p_instance);
if (is_editor_scene_context(instance->owner) && !instance->script->_is_tool())
{
return;
}
if (p_what == Node::NOTIFICATION_READY)
{
script_instance_invoke_ready(instance);
}
else if (p_what == Node::NOTIFICATION_EXIT_TREE)
{
LeanCLRRuntimeBridge::invoke_script_method(instance->managed_object, "_ExitTree");
}
}
void script_instance_to_string(GDExtensionScriptInstanceDataPtr p_instance, GDExtensionBool* r_is_valid, GDExtensionStringPtr r_out)
{
LeanCLRScriptInstance* instance = as_instance(p_instance);
*reinterpret_cast<String*>(r_out) = "<LeanCLRScriptInstance:" + instance->script->get_type_name() + ">";
*r_is_valid = true;
}
GDExtensionObjectPtr script_instance_get_script(GDExtensionScriptInstanceDataPtr p_instance)
{
LeanCLRScriptInstance* instance = as_instance(p_instance);
return instance->script.ptr() != nullptr ? instance->script->_owner : nullptr;
}
GDExtensionBool script_instance_is_placeholder(GDExtensionScriptInstanceDataPtr p_instance)
{
(void)p_instance;
return false;
}
GDExtensionScriptLanguagePtr script_instance_get_language(GDExtensionScriptInstanceDataPtr p_instance)
{
(void)p_instance;
ScriptLanguage* language = LeanCLRScriptLanguage::get_singleton();
return language != nullptr ? language->_owner : nullptr;
}
void script_instance_free(GDExtensionScriptInstanceDataPtr p_instance)
{
LeanCLRScriptInstance* instance = as_instance(p_instance);
LeanCLRRuntimeBridge::unregister_script_object(instance->owner, instance->managed_object);
LeanCLRRuntimeBridge::release_script_object(instance->managed_object);
delete instance;
}
const GDExtensionScriptInstanceInfo3& script_instance_info()
{
static const GDExtensionScriptInstanceInfo3 info = {
script_instance_set,
script_instance_get,
script_instance_get_property_list,
script_instance_free_property_list,
nullptr,
nullptr,
nullptr,
script_instance_get_owner,
script_instance_get_property_state,
script_instance_get_method_list,
script_instance_free_method_list,
script_instance_get_property_type,
nullptr,
script_instance_has_method,
script_instance_get_method_argument_count,
script_instance_call,
script_instance_notification,
script_instance_to_string,
nullptr,
nullptr,
script_instance_get_script,
script_instance_is_placeholder,
script_instance_set,
script_instance_get,
script_instance_get_language,
script_instance_free,
};
return info;
}
} // namespace
void LeanCLRScript::_bind_methods()
{
ClassDB::bind_method(D_METHOD("get_assembly_name"), &LeanCLRScript::get_assembly_name);
ClassDB::bind_method(D_METHOD("get_type_name"), &LeanCLRScript::get_type_name);
ClassDB::bind_method(D_METHOD("get_entry_point"), &LeanCLRScript::get_entry_point);
}
bool LeanCLRScript::_editor_can_reload_from_file()
{
return true;
}
bool LeanCLRScript::_can_instantiate() const
{
return valid;
}
Ref<Script> LeanCLRScript::_get_base_script() const
{
return Ref<Script>();
}
StringName LeanCLRScript::_get_global_name() const
{
return StringName(type_name);
}
bool LeanCLRScript::_inherits_script(const Ref<Script>& p_script) const
{
return p_script.ptr() == this;
}
StringName LeanCLRScript::_get_instance_base_type() const
{
return base_type;
}
void* LeanCLRScript::_instance_create(Object* p_for_object) const
{
if (!valid || p_for_object == nullptr)
{
return nullptr;
}
void* managed_object = LeanCLRRuntimeBridge::create_script_object(assembly_name, type_name, p_for_object);
if (managed_object == nullptr)
{
return nullptr;
}
LeanCLRScriptInstance* instance = new LeanCLRScriptInstance;
instance->owner = p_for_object;
instance->script = Ref<LeanCLRScript>(const_cast<LeanCLRScript*>(this));
instance->managed_object = managed_object;
LeanCLRRuntimeBridge::register_script_object(p_for_object, managed_object);
void* script_instance = gdextension_interface::script_instance_create3(&script_instance_info(), instance);
if (script_instance == nullptr)
{
LeanCLRRuntimeBridge::unregister_script_object(p_for_object, managed_object);
LeanCLRRuntimeBridge::release_script_object(managed_object);
delete instance;
}
return script_instance;
}
void* LeanCLRScript::_placeholder_instance_create(Object* p_for_object) const
{
(void)p_for_object;
return nullptr;
}
bool LeanCLRScript::_has_source_code() const
{
return !source_code.is_empty();
}
String LeanCLRScript::_get_source_code() const
{
return source_code;
}
void LeanCLRScript::_set_source_code(const String& p_code)
{
source_code = p_code;
parse_source();
}
Error LeanCLRScript::_reload(bool p_keep_state)
{
(void)p_keep_state;
parse_source();
return valid ? OK : ERR_PARSE_ERROR;
}
StringName LeanCLRScript::_get_doc_class_name() const
{
return StringName(type_name);
}
TypedArray<Dictionary> LeanCLRScript::_get_documentation() const
{
return TypedArray<Dictionary>();
}
String LeanCLRScript::_get_class_icon_path() const
{
return String();
}
bool LeanCLRScript::_has_method(const StringName& p_method) const
{
return p_method == StringName("_ready") || p_method == StringName("_process");
}
bool LeanCLRScript::_has_static_method(const StringName& p_method) const
{
(void)p_method;
return false;
}
Dictionary LeanCLRScript::_get_method_info(const StringName& p_method) const
{
Dictionary method;
method["name"] = p_method;
return method;
}
bool LeanCLRScript::_is_tool() const
{
return false;
}
bool LeanCLRScript::_is_valid() const
{
return valid;
}
bool LeanCLRScript::_is_abstract() const
{
return false;
}
ScriptLanguage* LeanCLRScript::_get_language() const
{
return LeanCLRScriptLanguage::get_singleton();
}
bool LeanCLRScript::_has_script_signal(const StringName& p_signal) const
{
(void)p_signal;
return false;
}
TypedArray<Dictionary> LeanCLRScript::_get_script_signal_list() const
{
return TypedArray<Dictionary>();
}
bool LeanCLRScript::_has_property_default_value(const StringName& p_property) const
{
(void)p_property;
return false;
}
Variant LeanCLRScript::_get_property_default_value(const StringName& p_property) const
{
(void)p_property;
return Variant();
}
void LeanCLRScript::_update_exports()
{
}
TypedArray<Dictionary> LeanCLRScript::_get_script_method_list() const
{
TypedArray<Dictionary> methods;
Dictionary method;
method["name"] = StringName("_ready");
methods.push_back(method);
Dictionary process_method;
process_method["name"] = StringName("_process");
methods.push_back(process_method);
return methods;
}
TypedArray<Dictionary> LeanCLRScript::_get_script_property_list() const
{
return TypedArray<Dictionary>();
}
int32_t LeanCLRScript::_get_member_line(const StringName& p_member) const
{
(void)p_member;
return -1;
}
Dictionary LeanCLRScript::_get_constants() const
{
return Dictionary();
}
TypedArray<StringName> LeanCLRScript::_get_members() const
{
return TypedArray<StringName>();
}
bool LeanCLRScript::_is_placeholder_fallback_enabled() const
{
return false;
}
Variant LeanCLRScript::_get_rpc_config() const
{
return Variant();
}
void LeanCLRScript::set_path_hint(const String& p_path)
{
path_hint = p_path;
}
String LeanCLRScript::get_assembly_name() const
{
return assembly_name;
}
String LeanCLRScript::get_type_name() const
{
return type_name;
}
String LeanCLRScript::get_entry_point() const
{
return entry_point;
}
void LeanCLRScript::parse_source()
{
assembly_name = String();
type_name = String();
entry_point = String();
base_type = "Node";
const String extension = path_hint.get_extension().to_lower();
if (extension == "cs")
{
assembly_name = "Game";
String namespace_name;
String class_name;
PackedStringArray lines = source_code.split("\n");
for (int64_t i = 0; i < lines.size(); ++i)
{
const String line = lines[i].strip_edges();
if (line.begins_with("// @assembly "))
{
assembly_name = line.substr(12).strip_edges();
}
else if (line.begins_with("// @type "))
{
type_name = line.substr(9).strip_edges();
}
else if (line.begins_with("// @base "))
{
base_type = StringName(line.substr(9).strip_edges());
}
else if (namespace_name.is_empty() && line.begins_with("namespace "))
{
namespace_name = line.substr(10).strip_edges().trim_suffix(";").trim_suffix("{").strip_edges();
}
else if (class_name.is_empty() && (line.find(" class ") >= 0 || line.begins_with("class ")))
{
PackedStringArray tokens = line.replace(":", " : ").split(" ", false);
for (int64_t token_index = 0; token_index + 1 < tokens.size(); ++token_index)
{
if (tokens[token_index] == "class")
{
class_name = tokens[token_index + 1].get_slice("<", 0).strip_edges();
break;
}
}
}
}
if (type_name.is_empty())
{
if (class_name.is_empty() && !path_hint.is_empty())
{
class_name = path_hint.get_file().get_basename();
}
type_name = namespace_name.is_empty() ? class_name : namespace_name + "." + class_name;
}
valid = !assembly_name.is_empty() && !type_name.is_empty();
return;
}
PackedStringArray lines = source_code.split("\n");
for (int64_t i = 0; i < lines.size(); ++i)
{
PackedStringArray pair = lines[i].strip_edges().split("=", false, 1);
if (pair.size() != 2)
{
continue;
}
const String key = pair[0].strip_edges().to_lower();
const String value = pair[1].strip_edges();
if (key == "assembly")
{
assembly_name = value;
}
else if (key == "type")
{
type_name = value;
}
else if (key == "base")
{
base_type = StringName(value);
}
else if (key == "entry")
{
entry_point = value;
}
}
valid = !assembly_name.is_empty() && !type_name.is_empty();
if (!valid && !path_hint.is_empty())
{
type_name = path_hint.get_file().get_basename();
}
}
} // namespace godot
+71
View File
@@ -0,0 +1,71 @@
#pragma once
#include <godot_cpp/classes/script_language.hpp>
#include <godot_cpp/classes/script_extension.hpp>
#include <godot_cpp/variant/dictionary.hpp>
#include <godot_cpp/variant/typed_array.hpp>
namespace godot
{
class LeanCLRScript : public ScriptExtension
{
GDCLASS(LeanCLRScript, ScriptExtension)
public:
bool _editor_can_reload_from_file() override;
bool _can_instantiate() const override;
Ref<Script> _get_base_script() const override;
StringName _get_global_name() const override;
bool _inherits_script(const Ref<Script>& p_script) const override;
StringName _get_instance_base_type() const override;
void* _instance_create(Object* p_for_object) const override;
void* _placeholder_instance_create(Object* p_for_object) const override;
bool _has_source_code() const override;
String _get_source_code() const override;
void _set_source_code(const String& p_code) override;
Error _reload(bool p_keep_state) override;
StringName _get_doc_class_name() const override;
TypedArray<Dictionary> _get_documentation() const override;
String _get_class_icon_path() const override;
bool _has_method(const StringName& p_method) const override;
bool _has_static_method(const StringName& p_method) const override;
Dictionary _get_method_info(const StringName& p_method) const override;
bool _is_tool() const override;
bool _is_valid() const override;
bool _is_abstract() const override;
ScriptLanguage* _get_language() const override;
bool _has_script_signal(const StringName& p_signal) const override;
TypedArray<Dictionary> _get_script_signal_list() const override;
bool _has_property_default_value(const StringName& p_property) const override;
Variant _get_property_default_value(const StringName& p_property) const override;
void _update_exports() override;
TypedArray<Dictionary> _get_script_method_list() const override;
TypedArray<Dictionary> _get_script_property_list() const override;
int32_t _get_member_line(const StringName& p_member) const override;
Dictionary _get_constants() const override;
TypedArray<StringName> _get_members() const override;
bool _is_placeholder_fallback_enabled() const override;
Variant _get_rpc_config() const override;
void set_path_hint(const String& p_path);
String get_assembly_name() const;
String get_type_name() const;
String get_entry_point() const;
protected:
static void _bind_methods();
private:
void parse_source();
String source_code;
String path_hint;
String assembly_name;
String type_name;
String entry_point;
StringName base_type = "Node";
bool valid = false;
};
} // namespace godot
+457
View File
@@ -0,0 +1,457 @@
#include "leanclr_script_language.h"
#include "leanclr_runtime_bridge.h"
#include "leanclr_script.h"
#include <godot_cpp/classes/engine.hpp>
#include <godot_cpp/classes/file_access.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/core/memory.hpp>
#include <godot_cpp/variant/array.hpp>
#include <godot_cpp/variant/packed_string_array.hpp>
namespace godot
{
LeanCLRScriptLanguage* LeanCLRScriptLanguage::singleton = nullptr;
void LeanCLRScriptLanguage::_bind_methods()
{
}
void LeanCLRScriptLanguage::init_singleton()
{
if (singleton != nullptr)
{
return;
}
singleton = memnew(LeanCLRScriptLanguage);
Engine::get_singleton()->register_script_language(singleton);
}
void LeanCLRScriptLanguage::deinit_singleton()
{
if (singleton == nullptr)
{
return;
}
Engine::get_singleton()->unregister_script_language(singleton);
memdelete(singleton);
singleton = nullptr;
}
LeanCLRScriptLanguage* LeanCLRScriptLanguage::get_singleton()
{
return singleton;
}
String LeanCLRScriptLanguage::_get_name() const
{
return "LeanCLR C#";
}
void LeanCLRScriptLanguage::_init()
{
}
String LeanCLRScriptLanguage::_get_type() const
{
return "LeanCLRScript";
}
String LeanCLRScriptLanguage::_get_extension() const
{
return "cs";
}
void LeanCLRScriptLanguage::_finish()
{
LeanCLRRuntimeBridge::shutdown();
}
PackedStringArray LeanCLRScriptLanguage::_get_reserved_words() const
{
PackedStringArray words;
words.push_back("assembly");
words.push_back("type");
words.push_back("base");
words.push_back("entry");
return words;
}
bool LeanCLRScriptLanguage::_is_control_flow_keyword(const String& p_keyword) const
{
(void)p_keyword;
return false;
}
PackedStringArray LeanCLRScriptLanguage::_get_comment_delimiters() const
{
PackedStringArray delimiters;
delimiters.push_back("#");
delimiters.push_back("//");
return delimiters;
}
PackedStringArray LeanCLRScriptLanguage::_get_doc_comment_delimiters() const
{
return PackedStringArray();
}
PackedStringArray LeanCLRScriptLanguage::_get_string_delimiters() const
{
PackedStringArray delimiters;
delimiters.push_back("\"");
return delimiters;
}
Ref<Script> LeanCLRScriptLanguage::_make_template(const String& p_template, const String& p_class_name, const String& p_base_class_name) const
{
(void)p_template;
Ref<LeanCLRScript> script;
script.instantiate();
script->_set_source_code("using Godot;\n\nnamespace Game;\n\npublic partial class " + p_class_name + " : " + p_base_class_name + "\n{\n}\n");
return script;
}
TypedArray<Dictionary> LeanCLRScriptLanguage::_get_built_in_templates(const StringName& p_object) const
{
(void)p_object;
return TypedArray<Dictionary>();
}
bool LeanCLRScriptLanguage::_is_using_templates()
{
return true;
}
Dictionary LeanCLRScriptLanguage::_validate(const String& p_script, const String& p_path, bool p_validate_functions, bool p_validate_errors,
bool p_validate_warnings, bool p_validate_safe_lines) const
{
(void)p_validate_functions;
(void)p_validate_warnings;
(void)p_validate_safe_lines;
const bool is_cs = p_path.get_extension().to_lower() == "cs";
const bool has_assembly = p_script.find("assembly=") >= 0;
const bool has_type = p_script.find("type=") >= 0;
Dictionary result;
result["valid"] = is_cs || (has_assembly && has_type);
if (p_validate_errors && !is_cs && !(has_assembly && has_type))
{
Array errors;
Dictionary error;
error["path"] = p_path;
error["line"] = 1;
error["column"] = 1;
error["message"] = "LeanCLR script requires assembly=<name> and type=<namespace.type>.";
errors.push_back(error);
result["errors"] = errors;
}
return result;
}
String LeanCLRScriptLanguage::_validate_path(const String& p_path) const
{
const String extension = p_path.get_extension().to_lower();
return extension == "cs" || extension == "lcs" ? String() : String("LeanCLR scripts must use .cs or .lcs extension.");
}
Object* LeanCLRScriptLanguage::_create_script() const
{
return memnew(LeanCLRScript);
}
bool LeanCLRScriptLanguage::_has_named_classes() const
{
return true;
}
bool LeanCLRScriptLanguage::_supports_builtin_mode() const
{
return false;
}
bool LeanCLRScriptLanguage::_supports_documentation() const
{
return false;
}
bool LeanCLRScriptLanguage::_can_inherit_from_file() const
{
return false;
}
int32_t LeanCLRScriptLanguage::_find_function(const String& p_function, const String& p_code) const
{
(void)p_function;
(void)p_code;
return -1;
}
String LeanCLRScriptLanguage::_make_function(const String& p_class_name, const String& p_function_name, const PackedStringArray& p_function_args) const
{
(void)p_class_name;
(void)p_function_name;
(void)p_function_args;
return String();
}
bool LeanCLRScriptLanguage::_can_make_function() const
{
return false;
}
Error LeanCLRScriptLanguage::_open_in_external_editor(const Ref<Script>& p_script, int32_t p_line, int32_t p_column)
{
(void)p_script;
(void)p_line;
(void)p_column;
return ERR_UNAVAILABLE;
}
bool LeanCLRScriptLanguage::_overrides_external_editor()
{
return false;
}
ScriptLanguage::ScriptNameCasing LeanCLRScriptLanguage::_preferred_file_name_casing() const
{
return SCRIPT_NAME_CASING_PASCAL_CASE;
}
Dictionary LeanCLRScriptLanguage::_complete_code(const String& p_code, const String& p_path, Object* p_owner) const
{
(void)p_code;
(void)p_path;
(void)p_owner;
Dictionary result;
result["result"] = ERR_UNAVAILABLE;
result["force"] = false;
result["call_hint"] = String();
return result;
}
Dictionary LeanCLRScriptLanguage::_lookup_code(const String& p_code, const String& p_symbol, const String& p_path, Object* p_owner) const
{
(void)p_code;
(void)p_symbol;
(void)p_path;
(void)p_owner;
Dictionary result;
result["result"] = ERR_UNAVAILABLE;
result["type"] = LOOKUP_RESULT_SCRIPT_LOCATION;
return result;
}
String LeanCLRScriptLanguage::_auto_indent_code(const String& p_code, int32_t p_from_line, int32_t p_to_line) const
{
(void)p_from_line;
(void)p_to_line;
return p_code;
}
void LeanCLRScriptLanguage::_thread_enter()
{
}
void LeanCLRScriptLanguage::_thread_exit()
{
}
String LeanCLRScriptLanguage::_debug_get_error() const
{
return LeanCLRRuntimeBridge::get_last_error();
}
int32_t LeanCLRScriptLanguage::_debug_get_stack_level_count() const
{
return 0;
}
int32_t LeanCLRScriptLanguage::_debug_get_stack_level_line(int32_t p_level) const
{
(void)p_level;
return -1;
}
String LeanCLRScriptLanguage::_debug_get_stack_level_function(int32_t p_level) const
{
(void)p_level;
return String();
}
String LeanCLRScriptLanguage::_debug_get_stack_level_source(int32_t p_level) const
{
(void)p_level;
return String();
}
Dictionary LeanCLRScriptLanguage::_debug_get_stack_level_locals(int32_t p_level, int32_t p_max_subitems, int32_t p_max_depth)
{
(void)p_level;
(void)p_max_subitems;
(void)p_max_depth;
return Dictionary();
}
Dictionary LeanCLRScriptLanguage::_debug_get_stack_level_members(int32_t p_level, int32_t p_max_subitems, int32_t p_max_depth)
{
(void)p_level;
(void)p_max_subitems;
(void)p_max_depth;
return Dictionary();
}
void* LeanCLRScriptLanguage::_debug_get_stack_level_instance(int32_t p_level)
{
(void)p_level;
return nullptr;
}
Dictionary LeanCLRScriptLanguage::_debug_get_globals(int32_t p_max_subitems, int32_t p_max_depth)
{
(void)p_max_subitems;
(void)p_max_depth;
return Dictionary();
}
String LeanCLRScriptLanguage::_debug_parse_stack_level_expression(int32_t p_level, const String& p_expression, int32_t p_max_subitems, int32_t p_max_depth)
{
(void)p_level;
(void)p_expression;
(void)p_max_subitems;
(void)p_max_depth;
return String();
}
TypedArray<Dictionary> LeanCLRScriptLanguage::_debug_get_current_stack_info()
{
return TypedArray<Dictionary>();
}
void LeanCLRScriptLanguage::_reload_all_scripts()
{
}
void LeanCLRScriptLanguage::_reload_scripts(const Array& p_scripts, bool p_soft_reload)
{
(void)p_scripts;
(void)p_soft_reload;
}
void LeanCLRScriptLanguage::_reload_tool_script(const Ref<Script>& p_script, bool p_soft_reload)
{
(void)p_script;
(void)p_soft_reload;
}
PackedStringArray LeanCLRScriptLanguage::_get_recognized_extensions() const
{
PackedStringArray extensions;
extensions.push_back("cs");
extensions.push_back("lcs");
return extensions;
}
TypedArray<Dictionary> LeanCLRScriptLanguage::_get_public_functions() const
{
return TypedArray<Dictionary>();
}
Dictionary LeanCLRScriptLanguage::_get_public_constants() const
{
return Dictionary();
}
TypedArray<Dictionary> LeanCLRScriptLanguage::_get_public_annotations() const
{
return TypedArray<Dictionary>();
}
void LeanCLRScriptLanguage::_profiling_start()
{
}
void LeanCLRScriptLanguage::_profiling_stop()
{
}
void LeanCLRScriptLanguage::_profiling_set_save_native_calls(bool p_enable)
{
(void)p_enable;
}
int32_t LeanCLRScriptLanguage::_profiling_get_accumulated_data(ScriptLanguageExtensionProfilingInfo* p_info_array, int32_t p_info_max)
{
(void)p_info_array;
(void)p_info_max;
return 0;
}
int32_t LeanCLRScriptLanguage::_profiling_get_frame_data(ScriptLanguageExtensionProfilingInfo* p_info_array, int32_t p_info_max)
{
(void)p_info_array;
(void)p_info_max;
return 0;
}
void LeanCLRScriptLanguage::_frame()
{
}
bool LeanCLRScriptLanguage::_handles_global_class_type(const String& p_type) const
{
return p_type == "LeanCLRScript";
}
Dictionary LeanCLRScriptLanguage::_get_global_class_name(const String& p_path) const
{
Dictionary result;
const String extension = p_path.get_extension().to_lower();
if (extension != "cs" && extension != "lcs")
{
return result;
}
const String source = FileAccess::get_file_as_string(p_path);
Ref<LeanCLRScript> script;
script.instantiate();
script->set_path_hint(p_path);
script->_set_source_code(source);
if (script->_is_valid())
{
result["name"] = script->get_type_name();
result["base_type"] = script->_get_instance_base_type();
return result;
}
PackedStringArray lines = source.split("\n");
for (int64_t i = 0; i < lines.size(); ++i)
{
PackedStringArray pair = lines[i].strip_edges().split("=", false, 1);
if (pair.size() != 2)
{
continue;
}
const String key = pair[0].strip_edges().to_lower();
const String value = pair[1].strip_edges();
if (key == "type")
{
result["name"] = value;
}
else if (key == "base")
{
result["base_type"] = value;
}
}
return result;
}
} // namespace godot
+83
View File
@@ -0,0 +1,83 @@
#pragma once
#include <godot_cpp/classes/script_language_extension.hpp>
namespace godot
{
class LeanCLRScriptLanguage : public ScriptLanguageExtension
{
GDCLASS(LeanCLRScriptLanguage, ScriptLanguageExtension)
public:
static void init_singleton();
static void deinit_singleton();
static LeanCLRScriptLanguage* get_singleton();
String _get_name() const override;
void _init() override;
String _get_type() const override;
String _get_extension() const override;
void _finish() override;
PackedStringArray _get_reserved_words() const override;
bool _is_control_flow_keyword(const String& p_keyword) const override;
PackedStringArray _get_comment_delimiters() const override;
PackedStringArray _get_doc_comment_delimiters() const override;
PackedStringArray _get_string_delimiters() const override;
Ref<Script> _make_template(const String& p_template, const String& p_class_name, const String& p_base_class_name) const override;
TypedArray<Dictionary> _get_built_in_templates(const StringName& p_object) const override;
bool _is_using_templates() override;
Dictionary _validate(const String& p_script, const String& p_path, bool p_validate_functions, bool p_validate_errors,
bool p_validate_warnings, bool p_validate_safe_lines) const override;
String _validate_path(const String& p_path) const override;
Object* _create_script() const override;
bool _has_named_classes() const override;
bool _supports_builtin_mode() const override;
bool _supports_documentation() const override;
bool _can_inherit_from_file() const override;
int32_t _find_function(const String& p_function, const String& p_code) const override;
String _make_function(const String& p_class_name, const String& p_function_name, const PackedStringArray& p_function_args) const override;
bool _can_make_function() const override;
Error _open_in_external_editor(const Ref<Script>& p_script, int32_t p_line, int32_t p_column) override;
bool _overrides_external_editor() override;
ScriptLanguage::ScriptNameCasing _preferred_file_name_casing() const override;
Dictionary _complete_code(const String& p_code, const String& p_path, Object* p_owner) const override;
Dictionary _lookup_code(const String& p_code, const String& p_symbol, const String& p_path, Object* p_owner) const override;
String _auto_indent_code(const String& p_code, int32_t p_from_line, int32_t p_to_line) const override;
void _thread_enter() override;
void _thread_exit() override;
String _debug_get_error() const override;
int32_t _debug_get_stack_level_count() const override;
int32_t _debug_get_stack_level_line(int32_t p_level) const override;
String _debug_get_stack_level_function(int32_t p_level) const override;
String _debug_get_stack_level_source(int32_t p_level) const override;
Dictionary _debug_get_stack_level_locals(int32_t p_level, int32_t p_max_subitems, int32_t p_max_depth) override;
Dictionary _debug_get_stack_level_members(int32_t p_level, int32_t p_max_subitems, int32_t p_max_depth) override;
void* _debug_get_stack_level_instance(int32_t p_level) override;
Dictionary _debug_get_globals(int32_t p_max_subitems, int32_t p_max_depth) override;
String _debug_parse_stack_level_expression(int32_t p_level, const String& p_expression, int32_t p_max_subitems, int32_t p_max_depth) override;
TypedArray<Dictionary> _debug_get_current_stack_info() override;
void _reload_all_scripts() override;
void _reload_scripts(const Array& p_scripts, bool p_soft_reload) override;
void _reload_tool_script(const Ref<Script>& p_script, bool p_soft_reload) override;
PackedStringArray _get_recognized_extensions() const override;
TypedArray<Dictionary> _get_public_functions() const override;
Dictionary _get_public_constants() const override;
TypedArray<Dictionary> _get_public_annotations() const override;
void _profiling_start() override;
void _profiling_stop() override;
void _profiling_set_save_native_calls(bool p_enable) override;
int32_t _profiling_get_accumulated_data(ScriptLanguageExtensionProfilingInfo* p_info_array, int32_t p_info_max) override;
int32_t _profiling_get_frame_data(ScriptLanguageExtensionProfilingInfo* p_info_array, int32_t p_info_max) override;
void _frame() override;
bool _handles_global_class_type(const String& p_type) const override;
Dictionary _get_global_class_name(const String& p_path) const override;
protected:
static void _bind_methods();
private:
static LeanCLRScriptLanguage* singleton;
};
} // namespace godot
+54
View File
@@ -0,0 +1,54 @@
#include "leanclr_script_loader.h"
#include "leanclr_script.h"
#include <godot_cpp/classes/file_access.hpp>
#include <godot_cpp/core/class_db.hpp>
namespace godot
{
void LeanCLRScriptLoader::_bind_methods()
{
}
PackedStringArray LeanCLRScriptLoader::_get_recognized_extensions() const
{
PackedStringArray extensions;
extensions.push_back("cs");
extensions.push_back("lcs");
return extensions;
}
bool LeanCLRScriptLoader::_handles_type(const StringName& p_type) const
{
return p_type == StringName("Script") || p_type == StringName("LeanCLRScript") || p_type == StringName();
}
String LeanCLRScriptLoader::_get_resource_type(const String& p_path) const
{
const String extension = p_path.get_extension().to_lower();
return extension == "cs" || extension == "lcs" ? String("LeanCLRScript") : String();
}
Variant LeanCLRScriptLoader::_load(const String& p_path, const String& p_original_path, bool p_use_sub_threads, int32_t p_cache_mode) const
{
(void)p_original_path;
(void)p_use_sub_threads;
(void)p_cache_mode;
Ref<LeanCLRScript> script;
script.instantiate();
script->set_path_hint(p_path);
script->_set_source_code(FileAccess::get_file_as_string(p_path));
const Error reload_error = script->_reload(false);
if (reload_error != OK)
{
return reload_error;
}
return script;
}
} // namespace godot
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include <godot_cpp/classes/resource_format_loader.hpp>
namespace godot
{
class LeanCLRScriptLoader : public ResourceFormatLoader
{
GDCLASS(LeanCLRScriptLoader, ResourceFormatLoader)
public:
PackedStringArray _get_recognized_extensions() const override;
bool _handles_type(const StringName& p_type) const override;
String _get_resource_type(const String& p_path) const override;
Variant _load(const String& p_path, const String& p_original_path, bool p_use_sub_threads, int32_t p_cache_mode) const override;
protected:
static void _bind_methods();
};
} // namespace godot
+79
View File
@@ -0,0 +1,79 @@
#include "leanclr_script_saver.h"
#include "leanclr_script.h"
#include <godot_cpp/classes/file_access.hpp>
#include <godot_cpp/classes/resource.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
namespace godot
{
void LeanCLRScriptSaver::_bind_methods()
{
}
Error LeanCLRScriptSaver::_save(const Ref<Resource>& p_resource, const String& p_path, uint32_t p_flags)
{
(void)p_flags;
LeanCLRScript* script = Object::cast_to<LeanCLRScript>(p_resource.ptr());
if (script == nullptr || p_path.is_empty())
{
return ERR_INVALID_PARAMETER;
}
String source_code = script->_get_source_code();
if (source_code.is_empty() && FileAccess::file_exists(p_path))
{
source_code = FileAccess::get_file_as_string(p_path);
}
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE);
if (!file.is_valid())
{
const Error error = FileAccess::get_open_error();
UtilityFunctions::printerr("LeanCLR script saver: failed to open ", p_path, " error = ", static_cast<int64_t>(error));
return error;
}
file->store_string(source_code);
file->flush();
file->close();
return OK;
}
Error LeanCLRScriptSaver::_set_uid(const String& p_path, int64_t p_uid)
{
(void)p_path;
(void)p_uid;
return OK;
}
bool LeanCLRScriptSaver::_recognize(const Ref<Resource>& p_resource) const
{
return Object::cast_to<LeanCLRScript>(p_resource.ptr()) != nullptr;
}
PackedStringArray LeanCLRScriptSaver::_get_recognized_extensions(const Ref<Resource>& p_resource) const
{
PackedStringArray extensions;
if (_recognize(p_resource))
{
extensions.push_back("cs");
extensions.push_back("lcs");
}
return extensions;
}
bool LeanCLRScriptSaver::_recognize_path(const Ref<Resource>& p_resource, const String& p_path) const
{
if (!_recognize(p_resource))
{
return false;
}
const String extension = p_path.get_extension().to_lower();
return extension == "cs" || extension == "lcs";
}
} // namespace godot
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include <godot_cpp/classes/resource_format_saver.hpp>
namespace godot
{
class LeanCLRScriptSaver : public ResourceFormatSaver
{
GDCLASS(LeanCLRScriptSaver, ResourceFormatSaver)
public:
Error _save(const Ref<Resource>& p_resource, const String& p_path, uint32_t p_flags) override;
Error _set_uid(const String& p_path, int64_t p_uid) override;
bool _recognize(const Ref<Resource>& p_resource) const override;
PackedStringArray _get_recognized_extensions(const Ref<Resource>& p_resource) const override;
bool _recognize_path(const Ref<Resource>& p_resource, const String& p_path) const override;
protected:
static void _bind_methods();
};
} // namespace godot
+96
View File
@@ -0,0 +1,96 @@
#include "register_types.h"
#include "leanclr_runtime_bridge.h"
#include "leanclr_main_node.h"
#include "leanclr_hot_reload_host.h"
#include "leanclr_script.h"
#include "leanclr_script_language.h"
#include "leanclr_script_loader.h"
#include "leanclr_script_saver.h"
#include <gdextension_interface.h>
#include <godot_cpp/classes/resource_loader.hpp>
#include <godot_cpp/classes/resource_saver.hpp>
#include <godot_cpp/core/defs.hpp>
#include <godot_cpp/core/memory.hpp>
#include <godot_cpp/godot.hpp>
namespace godot
{
namespace
{
Ref<LeanCLRScriptLoader>* script_loader = nullptr;
Ref<LeanCLRScriptSaver>* script_saver = nullptr;
} // namespace
void initialize_leanclr_godot_module(ModuleInitializationLevel p_level)
{
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE)
{
return;
}
GDREGISTER_CLASS(LeanCLRScript);
GDREGISTER_CLASS(LeanCLRScriptLanguage);
GDREGISTER_CLASS(LeanCLRScriptLoader);
GDREGISTER_CLASS(LeanCLRScriptSaver);
GDREGISTER_CLASS(LeanCLRMain);
GDREGISTER_CLASS(LeanCLRHotReloadHost);
LeanCLRScriptLanguage::init_singleton();
script_loader = memnew(Ref<LeanCLRScriptLoader>);
script_loader->instantiate();
ResourceLoader::get_singleton()->add_resource_format_loader(*script_loader, true);
script_saver = memnew(Ref<LeanCLRScriptSaver>);
script_saver->instantiate();
ResourceSaver::get_singleton()->add_resource_format_saver(*script_saver, true);
}
void uninitialize_leanclr_godot_module(ModuleInitializationLevel p_level)
{
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE)
{
return;
}
if (script_saver != nullptr && script_saver->is_valid())
{
ResourceSaver::get_singleton()->remove_resource_format_saver(*script_saver);
script_saver->unref();
memdelete(script_saver);
script_saver = nullptr;
}
if (script_loader != nullptr && script_loader->is_valid())
{
ResourceLoader::get_singleton()->remove_resource_format_loader(*script_loader);
script_loader->unref();
memdelete(script_loader);
script_loader = nullptr;
}
LeanCLRScriptLanguage::deinit_singleton();
LeanCLRRuntimeBridge::shutdown();
}
} // namespace godot
extern "C"
{
GDExtensionBool GDE_EXPORT leanclr_godot_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address,
GDExtensionClassLibraryPtr p_library,
GDExtensionInitialization* r_initialization)
{
godot::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization);
init_obj.register_initializer(godot::initialize_leanclr_godot_module);
init_obj.register_terminator(godot::uninitialize_leanclr_godot_module);
init_obj.set_minimum_library_initialization_level(godot::MODULE_INITIALIZATION_LEVEL_SCENE);
return init_obj.init();
}
}
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include <godot_cpp/core/class_db.hpp>
namespace godot
{
void initialize_leanclr_godot_module(ModuleInitializationLevel p_level);
void uninitialize_leanclr_godot_module(ModuleInitializationLevel p_level);
} // namespace godot