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

C#: Add global class support #72619

Merged
merged 1 commit into from
May 30, 2023
Merged
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
74 changes: 71 additions & 3 deletions modules/mono/csharp_script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@

#ifdef TOOLS_ENABLED
#include "core/os/keyboard.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 @@ -535,6 +536,48 @@ String CSharpLanguage::_get_indentation() const {
return "\t";
}

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

String CSharpLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const {
Ref<CSharpScript> scr = ResourceLoader::load(p_path, get_type());
if (!scr.is_valid() || !scr->valid || !scr->global_class) {
// Invalid script or the script is not a global class.
return String();
}

String name = scr->class_name;
if (unlikely(name.is_empty())) {
return String();
}

if (r_icon_path) {
if (scr->icon_path.is_empty() || scr->icon_path.is_absolute_path()) {
*r_icon_path = scr->icon_path.simplify_path();
} else if (scr->icon_path.is_relative_path()) {
*r_icon_path = p_path.get_base_dir().path_join(scr->icon_path).simplify_path();
}
}
if (r_base_type) {
bool found_global_base_script = false;
const CSharpScript *top = scr->base_script.ptr();
while (top != nullptr) {
if (top->global_class) {
*r_base_type = top->class_name;
found_global_base_script = true;
break;
}

top = top->base_script.ptr();
}
if (!found_global_base_script) {
*r_base_type = scr->get_instance_base_type();
}
}
return name;
}

String CSharpLanguage::debug_get_error() const {
return _debug_error;
}
Expand Down Expand Up @@ -2203,11 +2246,21 @@ void CSharpScript::reload_registered_script(Ref<CSharpScript> p_script) {
update_script_class_info(p_script);

p_script->_update_exports();

#if TOOLS_ENABLED
// If the EditorFileSystem singleton is available, update the file;
// otherwise, the file will be updated when the singleton becomes available.
EditorFileSystem *efs = EditorFileSystem::get_singleton();
if (efs) {
efs->update_file(p_script->get_path());
}
#endif
}

// Extract information about the script using the mono class.
void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
bool tool = false;
bool global_class = false;

// TODO: Use GDExtension godot_dictionary
Array methods_array;
Expand All @@ -2217,11 +2270,17 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
Dictionary signals_dict;
signals_dict.~Dictionary();

String class_name;
String icon_path;
Ref<CSharpScript> base_script;
GDMonoCache::managed_callbacks.ScriptManagerBridge_UpdateScriptClassInfo(
p_script.ptr(), &tool, &methods_array, &rpc_functions_dict, &signals_dict, &base_script);
p_script.ptr(), &class_name, &tool, &global_class, &icon_path,
&methods_array, &rpc_functions_dict, &signals_dict, &base_script);

p_script->class_name = class_name;
p_script->tool = tool;
p_script->global_class = global_class;
p_script->icon_path = icon_path;

p_script->rpc_config.clear();
p_script->rpc_config = rpc_functions_dict;
Expand Down Expand Up @@ -2519,6 +2578,15 @@ Error CSharpScript::reload(bool p_keep_state) {
update_script_class_info(this);

_update_exports();

#if TOOLS_ENABLED
// If the EditorFileSystem singleton is available, update the file;
// otherwise, the file will be updated when the singleton becomes available.
EditorFileSystem *efs = EditorFileSystem::get_singleton();
if (efs) {
efs->update_file(script_path);
}
#endif
}

return OK;
Expand Down Expand Up @@ -2605,11 +2673,11 @@ bool CSharpScript::inherits_script(const Ref<Script> &p_script) const {
}

Ref<Script> CSharpScript::get_base_script() const {
return base_script;
return base_script.is_valid() && !base_script->get_path().is_empty() ? base_script : nullptr;
}

StringName CSharpScript::get_global_name() const {
return StringName();
return global_class ? StringName(class_name) : StringName();
}

void CSharpScript::get_script_property_list(List<PropertyInfo> *r_list) const {
Expand Down
7 changes: 7 additions & 0 deletions modules/mono/csharp_script.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class CSharpScript : public Script {
friend class CSharpLanguage;

bool tool = false;
bool global_class = false;
bool valid = false;
bool reload_invalidated = false;

Expand All @@ -86,6 +87,8 @@ class CSharpScript : public Script {
#endif

String source;
String class_name;
String icon_path;

SelfList<CSharpScript> script_list = this;

Expand Down Expand Up @@ -442,6 +445,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 GLOBAL CLASS FUNCTIONS */
virtual bool handles_global_class_type(const String &p_type) const override;
virtual String get_global_class_name(const String &p_path, String *r_base_type = nullptr, String *r_icon_path = nullptr) const override;

/* 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 @@ -241,6 +241,9 @@ public static bool IsGodotMustBeVariantAttribute(this INamedTypeSymbol symbol)
public static bool IsGodotClassNameAttribute(this INamedTypeSymbol symbol)
=> symbol.ToString() == GodotClasses.GodotClassNameAttr;

public static bool IsGodotGlobalClassAttribute(this INamedTypeSymbol symbol)
=> symbol.ToString() == GodotClasses.GlobalClassAttr;

public static bool IsSystemFlagsAttribute(this INamedTypeSymbol symbol)
=> symbol.ToString() == GodotClasses.SystemFlagsAttr;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public static class GodotClasses
public const string SignalAttr = "Godot.SignalAttribute";
public const string MustBeVariantAttr = "Godot.MustBeVariantAttribute";
public const string GodotClassNameAttr = "Godot.GodotClassNameAttribute";
public const string GlobalClassAttr = "Godot.GlobalClassAttribute";
public const string SystemFlagsAttr = "System.FlagsAttribute";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -547,25 +547,32 @@ private static bool TryGetMemberExportHint(
{
if (memberNamedType.InheritsFrom("GodotSharp", "Godot.Resource"))
{
string nativeTypeName = memberNamedType.GetGodotScriptNativeClassName()!;

hint = PropertyHint.ResourceType;
hintString = nativeTypeName;
hintString = GetTypeName(memberNamedType);

return true;
}

if (memberNamedType.InheritsFrom("GodotSharp", "Godot.Node"))
{
string nativeTypeName = memberNamedType.GetGodotScriptNativeClassName()!;

hint = PropertyHint.NodeType;
hintString = nativeTypeName;
hintString = GetTypeName(memberNamedType);

return true;
}
}

static string GetTypeName(INamedTypeSymbol memberSymbol)
{
if (memberSymbol.GetAttributes()
.Any(a => a.AttributeClass?.IsGodotGlobalClassAttribute() ?? false))
{
return memberSymbol.Name;
}

return memberSymbol.GetGodotScriptNativeClassName()!;
}

static bool GetStringArrayEnumHint(VariantType elementVariantType,
AttributeData exportAttr, out string? hintString)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

#nullable enable

namespace Godot
{
/// <summary>
/// Exposes the target class as a global script class to Godot Engine.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public sealed class GlobalClassAttribute : Attribute { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;

namespace Godot
{
/// <summary>
/// Specifies a custom icon for representing this class in the Godot Editor.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public sealed class IconAttribute : Attribute
{
/// <summary>
/// File path to a custom icon for representing this class in the Godot Editor.
/// </summary>
public string Path { get; }

/// <summary>
/// Specify the custom icon that represents the class.
/// </summary>
/// <param name="path">File path to the custom icon.</param>
public IconAttribute(string path)
{
Path = path;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public unsafe struct ManagedCallbacks
public delegate* unmanaged<godot_string*, godot_ref*, void> ScriptManagerBridge_GetOrCreateScriptBridgeForPath;
public delegate* unmanaged<IntPtr, void> ScriptManagerBridge_RemoveScriptBridge;
public delegate* unmanaged<IntPtr, godot_bool> ScriptManagerBridge_TryReloadRegisteredScriptWithClass;
public delegate* unmanaged<IntPtr, godot_bool*, godot_array*, godot_dictionary*, godot_dictionary*, godot_ref*, void> ScriptManagerBridge_UpdateScriptClassInfo;
public delegate* unmanaged<IntPtr, godot_string*, godot_bool*, godot_bool*, godot_string*, godot_array*, godot_dictionary*, godot_dictionary*, godot_ref*, void> ScriptManagerBridge_UpdateScriptClassInfo;
public delegate* unmanaged<IntPtr, IntPtr*, godot_bool, godot_bool> ScriptManagerBridge_SwapGCHandleForType;
public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, godot_string*, void*, int, void>, void> ScriptManagerBridge_GetPropertyInfoList;
public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, void*, int, void>, void> ScriptManagerBridge_GetPropertyDefaultValues;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,13 @@ static void LookupScriptForClass(Type type)

_pathTypeBiMap.Add(scriptPathAttr.Path, type);

// This method may be called before initialization.
if (NativeFuncs.godotsharp_dotnet_module_is_initialized().ToBool() && Engine.IsEditorHint())
{
using godot_string scriptPath = Marshaling.ConvertStringToNative(scriptPathAttr.Path);
NativeFuncs.godotsharp_internal_editor_file_system_update_file(scriptPath);
}

if (AlcReloadCfg.IsAlcReloadingEnabled)
{
AddTypeForAlcReloading(type);
Expand Down Expand Up @@ -584,7 +591,8 @@ internal static godot_bool TryReloadRegisteredScriptWithClass(IntPtr scriptPtr)
}

[UnmanagedCallersOnly]
internal static unsafe void UpdateScriptClassInfo(IntPtr scriptPtr, godot_bool* outTool,
internal static unsafe void UpdateScriptClassInfo(IntPtr scriptPtr, godot_string* outClassName,
godot_bool* outTool, godot_bool* outGlobal, godot_string* outIconPath,
godot_array* outMethodsDest, godot_dictionary* outRpcFunctionsDest,
godot_dictionary* outEventSignalsDest, godot_ref* outBaseScript)
{
Expand All @@ -593,6 +601,8 @@ internal static unsafe void UpdateScriptClassInfo(IntPtr scriptPtr, godot_bool*
// Performance is not critical here as this will be replaced with source generators.
var scriptType = _scriptTypeBiMap.GetScriptType(scriptPtr);

*outClassName = Marshaling.ConvertStringToNative(scriptType.Name);

*outTool = scriptType.GetCustomAttributes(inherit: false)
.OfType<ToolAttribute>()
.Any().ToGodotBool();
Expand All @@ -607,6 +617,18 @@ internal static unsafe void UpdateScriptClassInfo(IntPtr scriptPtr, godot_bool*
if (!(*outTool).ToBool() && scriptType.Assembly.GetName().Name == "GodotTools")
*outTool = godot_bool.True;

var globalAttr = scriptType.GetCustomAttributes(inherit: false)
.OfType<GlobalClassAttribute>()
.FirstOrDefault();

*outGlobal = (globalAttr != null).ToGodotBool();

var iconAttr = scriptType.GetCustomAttributes(inherit: false)
.OfType<IconAttribute>()
.FirstOrDefault();

*outIconPath = Marshaling.ConvertStringToNative(iconAttr?.Path);

// Methods

// Performance is not critical here as this will be replaced with source generators.
Expand Down Expand Up @@ -693,7 +715,7 @@ internal static unsafe void UpdateScriptClassInfo(IntPtr scriptPtr, godot_bool*
}

*outRpcFunctionsDest = NativeFuncs.godotsharp_dictionary_new_copy(
(godot_dictionary)(rpcFunctions).NativeValue);
(godot_dictionary)rpcFunctions.NativeValue);

// Event signals

Expand Down Expand Up @@ -755,7 +777,10 @@ internal static unsafe void UpdateScriptClassInfo(IntPtr scriptPtr, godot_bool*
catch (Exception e)
{
ExceptionUtils.LogException(e);
*outClassName = default;
*outTool = godot_bool.False;
*outGlobal = godot_bool.False;
*outIconPath = default;
*outMethodsDest = NativeFuncs.godotsharp_array_new();
*outRpcFunctionsDest = NativeFuncs.godotsharp_dictionary_new();
*outEventSignalsDest = NativeFuncs.godotsharp_dictionary_new();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ private partial struct UnmanagedCallbacks

// Custom functions

internal static partial godot_bool godotsharp_dotnet_module_is_initialized();

public static partial IntPtr godotsharp_method_bind_get_method(in godot_string_name p_classname,
in godot_string_name p_methodname);

Expand All @@ -52,6 +54,8 @@ internal static partial Error godotsharp_stack_info_vector_resize(
internal static partial void godotsharp_stack_info_vector_destroy(
ref DebuggingUtils.godot_stack_info_vector p_stack_info_vector);

internal static partial void godotsharp_internal_editor_file_system_update_file(in godot_string p_script_path);

internal static partial void godotsharp_internal_script_debugger_send_error(in godot_string p_func,
in godot_string p_file, int p_line, in godot_string p_err, in godot_string p_descr,
godot_bool p_warning, in DebuggingUtils.godot_stack_info_vector p_stack_info_vector);
Expand Down
2 changes: 2 additions & 0 deletions modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@
<Compile Include="Core\Attributes\ExportCategoryAttribute.cs" />
<Compile Include="Core\Attributes\ExportGroupAttribute.cs" />
<Compile Include="Core\Attributes\ExportSubgroupAttribute.cs" />
<Compile Include="Core\Attributes\GlobalClassAttribute.cs" />
<Compile Include="Core\Attributes\GodotClassNameAttribute.cs" />
<Compile Include="Core\Attributes\IconAttribute.cs" />
<Compile Include="Core\Attributes\MustBeVariantAttribute.cs" />
<Compile Include="Core\Attributes\RpcAttribute.cs" />
<Compile Include="Core\Attributes\ScriptPathAttribute.cs" />
Expand Down
Loading