Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add C# resource export. #63126

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 86 additions & 3 deletions modules/mono/csharp_script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
#ifdef TOOLS_ENABLED
#include "core/os/keyboard.h"
#include "editor/bindings_generator.h"
#include "editor/editor_file_system.h"
#include "editor/editor_internal_calls.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
Expand Down Expand Up @@ -548,6 +549,37 @@ String CSharpLanguage::_get_indentation() const {
return "\t";
}

bool CSharpLanguage::handles_global_class_type(const String &p_type) const {
return p_type == "CSharpScript";
}

String CSharpLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const {
Ref<CSharpScript> script = ResourceLoader::load(p_path, "CSharpScript");
if (script.is_valid()) {
String name = script->get_script_class_name();
if (name.length()) {
if (r_base_type) {
StringName base_name = script->get_script_class_base();
if (base_name != StringName()) {
*r_base_type = base_name;
} else {
*r_base_type = script->get_instance_base_type();
}
}
if (r_icon_path) {
*r_icon_path = script->get_script_class_icon_path();
}
return name;
}
}

if (r_base_type)
*r_base_type = String();
if (r_icon_path)
*r_icon_path = String();
return String();
}

String CSharpLanguage::debug_get_error() const {
return _debug_error;
}
Expand Down Expand Up @@ -1076,6 +1108,9 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
to_reload_state.push_back(script);
}

#ifdef TOOLS_ENABLED
EditorFileSystem *efs = EditorFileSystem::get_singleton();
#endif
for (Ref<CSharpScript> &script : to_reload_state) {
for (const ObjectID &obj_id : script->pending_reload_instances) {
Object *obj = ObjectDB::get_instance(obj_id);
Expand Down Expand Up @@ -1137,6 +1172,9 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
}
}

#ifdef TOOLS_ENABLED
efs->update_file(script->get_path());
#endif
script->pending_reload_instances.clear();
}

Expand Down Expand Up @@ -2790,7 +2828,9 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect
}

#ifdef TOOLS_ENABLED
int hint_res = _try_get_member_export_hint(p_member, type, variant_type, /* allow_generics: */ true, hint, hint_string);
PropertyHint given_hint = PropertyHint(CACHED_FIELD(ExportAttribute, hint)->get_int_value(attr));
String given_hint_string = CACHED_FIELD(ExportAttribute, hintString)->get_string_value(attr);
int hint_res = _try_get_member_export_hint(p_member, type, variant_type, given_hint, given_hint_string, /* allow_generics: */ true, hint, hint_string);

ERR_FAIL_COND_V_MSG(hint_res == -1, false,
"Error while trying to determine information about the exported member: '" +
Expand Down Expand Up @@ -2818,7 +2858,7 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect
}

#ifdef TOOLS_ENABLED
int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, ManagedType p_type, Variant::Type p_variant_type, bool p_allow_generics, PropertyHint &r_hint, String &r_hint_string) {
int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, ManagedType p_type, Variant::Type p_variant_type, PropertyHint p_given_hint, String p_given_hint_string, bool p_allow_generics, PropertyHint &r_hint, String &r_hint_string) {
if (p_variant_type == Variant::NIL) {
// System.Object (Variant)
return 1;
Expand Down Expand Up @@ -2886,6 +2926,16 @@ int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, Manage

r_hint = PROPERTY_HINT_RESOURCE_TYPE;
r_hint_string = String(NATIVE_GDMONOCLASS_NAME(field_native_class));

if (p_type.type_class->has_attribute(CACHED_CLASS(GlobalAttribute))) {
MonoObject *attr = p_type.type_class->get_attribute(CACHED_CLASS(GlobalAttribute));
StringName script_class_name = CACHED_FIELD(GlobalAttribute, name)->get_string_value(attr);
if (script_class_name != StringName()) {
r_hint_string = script_class_name;
}
} else if (!p_given_hint_string.is_empty()) {
r_hint_string = p_given_hint_string;
}
} else if (p_variant_type == Variant::OBJECT && CACHED_CLASS(Node)->is_assignable_from(p_type.type_class)) {
GDMonoClass *field_native_class = GDMonoUtils::get_class_native_base(p_type.type_class);
CRASH_COND(field_native_class == nullptr);
Expand Down Expand Up @@ -2918,7 +2968,7 @@ int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, Manage
}

if (!preset_hint) {
int hint_res = _try_get_member_export_hint(p_member, elem_type, elem_variant_type, /* allow_generics: */ false, elem_hint, elem_hint_string);
int hint_res = _try_get_member_export_hint(p_member, elem_type, elem_variant_type, p_given_hint, p_given_hint_string, /* allow_generics: */ false, elem_hint, elem_hint_string);

ERR_FAIL_COND_V_MSG(hint_res == -1, -1, "Error while trying to determine information about the array element type.");

Expand Down Expand Up @@ -3406,6 +3456,8 @@ Error CSharpScript::reload(bool p_keep_state) {
update_script_class_info(this);

_update_exports();

_update_global_script_class_settings();
}

return OK;
Expand Down Expand Up @@ -3564,6 +3616,37 @@ Error CSharpScript::load_source_code(const String &p_path) {
return OK;
}

void CSharpScript::_update_global_script_class_settings() {
// Evaluate script's use of engine "Script Class" system.
if (script_class->has_attribute(CACHED_CLASS(GlobalAttribute))) {
MonoObject *attr = script_class->get_attribute(CACHED_CLASS(GlobalAttribute));
script_class_name = CACHED_FIELD(GlobalAttribute, name)->get_string_value(attr);
script_class_icon_path = CACHED_FIELD(GlobalAttribute, iconPath)->get_string_value(attr);
if (script_class_name.is_empty()) {
script_class_name = script_class->get_name();
}
} else {
script_class_name = String();
script_class_icon_path = String();
}

GDMonoClass *parent = script_class->get_parent_class();
while (parent) {
if (parent->has_attribute(CACHED_CLASS(GlobalAttribute))) {
MonoObject *attr = parent->get_attribute(CACHED_CLASS(GlobalAttribute));
script_class_base = CACHED_FIELD(GlobalAttribute, name)->get_string_value(attr);
if (script_class_base.is_empty()) {
script_class_base = script_class->get_name();
}
break;
}
parent = parent->get_parent_class();
}
if (script_class_base.is_empty()) {
script_class_base = get_instance_base_type();
}
}

void CSharpScript::_update_name() {
String path = get_path();

Expand Down
16 changes: 15 additions & 1 deletion modules/mono/csharp_script.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ class CSharpScript : public Script {
String source;
StringName name;

// For engine "Script Class" support, not affiliated with `GDMonoClass *script_class` property.
String script_class_name;
String script_class_base;
String script_class_icon_path;

SelfList<CSharpScript> script_list = this;

HashMap<StringName, Vector<SignalParameter>> _signals;
Expand Down Expand Up @@ -167,7 +172,7 @@ class CSharpScript : public Script {

bool _get_member_export(IMonoClassMember *p_member, bool p_inspect_export, PropertyInfo &r_prop_info, bool &r_exported);
#ifdef TOOLS_ENABLED
static int _try_get_member_export_hint(IMonoClassMember *p_member, ManagedType p_type, Variant::Type p_variant_type, bool p_allow_generics, PropertyHint &r_hint, String &r_hint_string);
static int _try_get_member_export_hint(IMonoClassMember *p_member, ManagedType p_type, Variant::Type p_variant_type, PropertyHint p_given_hint, String p_given_hint_string, bool p_allow_generics, PropertyHint &r_hint, String &r_hint_string);
#endif

CSharpInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error);
Expand Down Expand Up @@ -242,6 +247,11 @@ class CSharpScript : public Script {

Error load_source_code(const String &p_path);

String get_script_class_name() const { return script_class_name; }
String get_script_class_base() const { return script_class_base; }
String get_script_class_icon_path() const { return script_class_icon_path; }
void _update_global_script_class_settings();

CSharpScript();
~CSharpScript();
};
Expand Down Expand Up @@ -480,6 +490,10 @@ class CSharpLanguage : public ScriptLanguage {
/* TODO? */ void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const override {}
/* TODO */ void add_global_constant(const StringName &p_variable, const Variant &p_value) override {}

/* SCRIPT CLASS FUNCTIONS */
virtual bool handles_global_class_type(const String &p_type) const;
virtual String get_global_class_name(const String &p_path, String *r_base_type = NULL, String *r_icon_path = NULL) const;

/* DEBUGGER FUNCTIONS */
String debug_get_error() const override;
int debug_get_stack_level_count() const override;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,16 @@ public ExportAttribute(PropertyHint hint = PropertyHint.None, string hintString
this.hint = hint;
this.hintString = hintString;
}

/// <summary>
/// Constructs a new ExportAttribute Instance. Allows properties to expose their data type as a non-C# Resource class in Godot.
/// </summary>
/// <param name="className">The name of a class by which to export the targeted property.</param>
public ExportAttribute(string className)
{
// Because `ScriptServer` is not exposed to the scripting API, there is no proper way of validating the passed in className.
this.hint = PropertyHint.ResourceType;
this.hintString = className;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;

namespace Godot
{
/// <summary>
/// Exposes the target class as a global script class to Godot Engine.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class GlobalAttribute : Attribute
{
private string name;
private string iconPath;

/// <summary>
/// Constructs a new GlobalAttribute Instance.
/// </summary>
/// <param name="name">The name under which to register the targeted class. Uses its typename by default.</param>
/// <param name="iconPath">An optional file path to a custom icon for representing this class in the Godot Editor.</param>
public GlobalAttribute(string name = "", string iconPath = "")
{
this.name = name ?? "";
this.iconPath = iconPath ?? "";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<Compile Include="Core\Attributes\AssemblyHasScriptsAttribute.cs" />
<Compile Include="Core\Attributes\DisableGodotGeneratorsAttribute.cs" />
<Compile Include="Core\Attributes\ExportAttribute.cs" />
<Compile Include="Core\Attributes\GlobalAttribute.cs" />
<Compile Include="Core\Attributes\GodotMethodAttribute.cs" />
<Compile Include="Core\Attributes\RPCAttribute.cs" />
<Compile Include="Core\Attributes\ScriptPathAttribute.cs" />
Expand Down
6 changes: 6 additions & 0 deletions modules/mono/mono_gd/gd_mono_cache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ void CachedData::clear_godot_api_cache() {
class_ExportAttribute = nullptr;
field_ExportAttribute_hint = nullptr;
field_ExportAttribute_hintString = nullptr;
class_GlobalAttribute = nullptr;
field_GlobalAttribute_name = nullptr;
field_GlobalAttribute_iconPath = nullptr;
class_SignalAttribute = nullptr;
class_ToolAttribute = nullptr;
class_RPCAttribute = nullptr;
Expand Down Expand Up @@ -275,6 +278,9 @@ void update_godot_api_cache() {
CACHE_CLASS_AND_CHECK(ExportAttribute, GODOT_API_CLASS(ExportAttribute));
CACHE_FIELD_AND_CHECK(ExportAttribute, hint, CACHED_CLASS(ExportAttribute)->get_field("hint"));
CACHE_FIELD_AND_CHECK(ExportAttribute, hintString, CACHED_CLASS(ExportAttribute)->get_field("hintString"));
CACHE_CLASS_AND_CHECK(GlobalAttribute, GODOT_API_CLASS(GlobalAttribute));
CACHE_FIELD_AND_CHECK(GlobalAttribute, name, CACHED_CLASS(GlobalAttribute)->get_field("name"));
CACHE_FIELD_AND_CHECK(GlobalAttribute, iconPath, CACHED_CLASS(GlobalAttribute)->get_field("iconPath"));
CACHE_CLASS_AND_CHECK(SignalAttribute, GODOT_API_CLASS(SignalAttribute));
CACHE_CLASS_AND_CHECK(ToolAttribute, GODOT_API_CLASS(ToolAttribute));
CACHE_CLASS_AND_CHECK(RPCAttribute, GODOT_API_CLASS(RPCAttribute));
Expand Down
3 changes: 3 additions & 0 deletions modules/mono/mono_gd/gd_mono_cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ struct CachedData {
GDMonoClass *class_ExportAttribute = nullptr;
GDMonoField *field_ExportAttribute_hint = nullptr;
GDMonoField *field_ExportAttribute_hintString = nullptr;
GDMonoClass *class_GlobalAttribute = nullptr;
GDMonoField *field_GlobalAttribute_name = nullptr;
GDMonoField *field_GlobalAttribute_iconPath = nullptr;
GDMonoClass *class_SignalAttribute = nullptr;
GDMonoClass *class_ToolAttribute = nullptr;
GDMonoClass *class_RPCAttribute = nullptr;
Expand Down