init
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user