Add ScriptServer and export all custom resources.

This commit is contained in:
willnationsdev 2020-12-27 22:24:31 -06:00
parent eae7a5384e
commit ad10a5961f
44 changed files with 1252 additions and 154 deletions

View file

@ -39,6 +39,7 @@
#include "core/os/keyboard.h"
#include "core/os/os.h"
#include "core/project_settings.h"
#include "core/script_language.h"
/**
* Time constants borrowed from loc_time.h
@ -2989,6 +2990,89 @@ _ClassDB::~_ClassDB() {
}
///////////////////////////////
bool _ScriptServer::_set(const StringName &p_name, const Variant &p_value) {
return false;
}
bool _ScriptServer::_get(const StringName &p_name, Variant &r_ret) const {
if (ScriptServer::is_global_class(p_name)) {
r_ret = ResourceLoader::load(ScriptServer::get_global_class_path(p_name), "Script");
return true;
}
return false;
}
void _ScriptServer::_get_property_list(List<PropertyInfo> *p_list) const {
ERR_FAIL_COND(!p_list);
List<StringName> names;
ScriptServer::get_global_class_list(&names);
for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
StringName n = E->get();
String class_name = String(n).get_file().get_extension();
p_list->push_back(PropertyInfo(Variant::OBJECT, class_name, PROPERTY_HINT_RESOURCE_TYPE, "Script", PROPERTY_USAGE_NETWORK, ResourceLoader::get_resource_type(ScriptServer::get_global_class_path(n))));
}
}
bool _ScriptServer::is_global_class(const StringName &p_class) const {
return ScriptServer::is_global_class(p_class);
}
String _ScriptServer::get_global_class_path(const String &p_class) const {
return ScriptServer::get_global_class_path(p_class);
}
StringName _ScriptServer::get_global_class_base(const String &p_class) const {
return ScriptServer::get_global_class_base(p_class);
}
StringName _ScriptServer::get_global_class_native_base(const String &p_class) const {
return ScriptServer::get_global_class_native_base(p_class);
}
StringName _ScriptServer::get_global_class_name(const String &p_path) const {
return ScriptServer::get_global_class_name(p_path);
}
Ref<Script> _ScriptServer::get_global_class_script(const StringName &p_class) const {
return ScriptServer::get_global_class_script(p_class);
}
Variant _ScriptServer::instantiate_global_class(const StringName &p_class) const {
return ScriptServer::instantiate_global_class(p_class);
}
Array _ScriptServer::get_global_class_list() const {
Array ret;
List<StringName> lst;
ScriptServer::get_global_class_list(&lst);
for (List<StringName>::Element *E = lst.front(); E; E = E->next()) {
ret.push_back(E->get());
}
return ret;
}
void _ScriptServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_global_class", "class"), &_ScriptServer::is_global_class);
ClassDB::bind_method(D_METHOD("get_global_class_path", "class"), &_ScriptServer::get_global_class_path);
ClassDB::bind_method(D_METHOD("get_global_class_base", "class"), &_ScriptServer::get_global_class_base);
ClassDB::bind_method(D_METHOD("get_global_class_native_base", "class"), &_ScriptServer::get_global_class_native_base);
ClassDB::bind_method(D_METHOD("get_global_class_name", "path"), &_ScriptServer::get_global_class_name);
ClassDB::bind_method(D_METHOD("get_global_class_script", "class"), &_ScriptServer::get_global_class_script);
ClassDB::bind_method(D_METHOD("instantiate_global_class", "class"), &_ScriptServer::instantiate_global_class);
ClassDB::bind_method(D_METHOD("get_global_class_list"), &_ScriptServer::get_global_class_list);
}
_ScriptServer::_ScriptServer() {
singleton = this;
}
_ScriptServer::~_ScriptServer() {
}
_ScriptServer *_ScriptServer::singleton = nullptr;
///////////////////////////////
void _Engine::set_iterations_per_second(int p_ips) {
Engine::get_singleton()->set_iterations_per_second(p_ips);
}

View file

@ -754,6 +754,33 @@ public:
~_ClassDB();
};
class _ScriptServer : public Object {
GDCLASS(_ScriptServer, Object);
protected:
static void _bind_methods();
static _ScriptServer *singleton;
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
public:
static _ScriptServer *get_singleton() { return singleton; }
bool is_global_class(const StringName &p_class) const;
String get_global_class_path(const String &p_class) const;
StringName get_global_class_base(const String &p_class) const;
StringName get_global_class_native_base(const String &p_class) const;
StringName get_global_class_name(const String &p_path) const;
Ref<Script> get_global_class_script(const StringName &p_class) const;
Variant instantiate_global_class(const StringName &p_class) const;
Array get_global_class_list() const;
_ScriptServer();
~_ScriptServer();
};
class _Engine : public Object {
GDCLASS(_Engine, Object);

View file

@ -101,8 +101,14 @@ void ResourceFormatLoader::get_recognized_extensions_for_type(const String &p_ty
}
void ResourceLoader::get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) {
StringName native = ScriptServer::is_global_class(p_type) ? ScriptServer::get_global_class_native_base(p_type) : StringName(p_type);
for (int i = 0; i < loader_count; i++) {
loader[i]->get_recognized_extensions_for_type(p_type, p_extensions);
Ref<ResourceFormatLoader> current = loader[i];
if (!current->get_script().is_null()) {
current->get_recognized_extensions_for_type(p_type, p_extensions);
} else {
current->get_recognized_extensions_for_type(native, p_extensions);
}
}
}

View file

@ -68,6 +68,7 @@
#include "core/packed_data_container.h"
#include "core/path_remap.h"
#include "core/project_settings.h"
#include "core/script_language.h"
#include "core/translation.h"
#include "core/undo_redo.h"
@ -86,6 +87,7 @@ static _Engine *_engine = nullptr;
static _ClassDB *_classdb = nullptr;
static _Marshalls *_marshalls = nullptr;
static _JSON *_json = nullptr;
static _ScriptServer *_script_server = nullptr;
static IP *ip = nullptr;
@ -222,6 +224,7 @@ void register_core_types() {
_classdb = memnew(_ClassDB);
_marshalls = memnew(_Marshalls);
_json = memnew(_JSON);
_script_server = memnew(_ScriptServer);
}
void register_core_settings() {
@ -250,6 +253,7 @@ void register_core_singletons() {
ClassDB::register_class<InputMap>();
ClassDB::register_class<_JSON>();
ClassDB::register_class<Expression>();
ClassDB::register_class<_ScriptServer>();
Engine::get_singleton()->add_singleton(Engine::Singleton("ProjectSettings", ProjectSettings::get_singleton()));
Engine::get_singleton()->add_singleton(Engine::Singleton("IP", IP::get_singleton()));
@ -264,6 +268,7 @@ void register_core_singletons() {
Engine::get_singleton()->add_singleton(Engine::Singleton("Input", Input::get_singleton()));
Engine::get_singleton()->add_singleton(Engine::Singleton("InputMap", InputMap::get_singleton()));
Engine::get_singleton()->add_singleton(Engine::Singleton("JSON", _JSON::get_singleton()));
Engine::get_singleton()->add_singleton(Engine::Singleton("ScriptServer", _ScriptServer::get_singleton()));
}
void unregister_core_types() {
@ -274,6 +279,7 @@ void unregister_core_types() {
memdelete(_classdb);
memdelete(_marshalls);
memdelete(_json);
memdelete(_script_server);
memdelete(_geometry);

View file

@ -31,6 +31,7 @@
#include "script_language.h"
#include "core/core_string_names.h"
#include "core/io/resource_loader.h"
#include "core/project_settings.h"
ScriptLanguage *ScriptServer::_languages[MAX_LANGUAGES];
@ -200,20 +201,28 @@ void ScriptServer::thread_exit() {
}
HashMap<StringName, ScriptServer::GlobalScriptClass> ScriptServer::global_classes;
HashMap<String, StringName> ScriptServer::global_class_paths;
void ScriptServer::global_classes_clear() {
global_classes.clear();
global_class_paths.clear();
}
void ScriptServer::add_global_class(const StringName &p_class, const StringName &p_base, const StringName &p_language, const String &p_path) {
ERR_FAIL_COND_MSG(p_class == p_base || (global_classes.has(p_base) && get_global_class_native_base(p_base) == p_class), "Cyclic inheritance in script class.");
ERR_FAIL_COND_MSG(p_class == StringName(), vformat("Attempted to register global script class at path '%s' without a class name.", p_path));
ERR_FAIL_COND_MSG(p_base == StringName(), vformat("Attempted to register global script class at path '%s' without a base name.", p_path));
ERR_FAIL_COND_MSG(p_language == StringName(), vformat("Attempted to register global script class at path '%s' without a language name.", p_path));
ERR_FAIL_COND_MSG(p_path.empty(), vformat("Attempted to register global script class named '%s' with an empty path.", p_class));
GlobalScriptClass g;
g.language = p_language;
g.path = p_path;
g.base = p_base;
global_classes[p_class] = g;
global_class_paths[p_path] = p_class;
}
void ScriptServer::remove_global_class(const StringName &p_class) {
global_class_paths.erase(global_classes[p_class].path);
global_classes.erase(p_class);
}
bool ScriptServer::is_global_class(const StringName &p_class) {
@ -223,23 +232,71 @@ StringName ScriptServer::get_global_class_language(const StringName &p_class) {
ERR_FAIL_COND_V(!global_classes.has(p_class), StringName());
return global_classes[p_class].language;
}
String ScriptServer::get_global_class_path(const String &p_class) {
String ScriptServer::get_global_class_path(const StringName &p_class) {
ERR_FAIL_COND_V(!global_classes.has(p_class), String());
return global_classes[p_class].path;
}
StringName ScriptServer::get_global_class_name(const String &p_path) {
if (global_class_paths.has(p_path)) {
return global_class_paths[p_path];
}
return StringName();
}
StringName ScriptServer::get_global_class_base(const String &p_class) {
ERR_FAIL_COND_V(!global_classes.has(p_class), String());
StringName ScriptServer::get_global_class_base(const StringName &p_class) {
ERR_FAIL_COND_V(!global_classes.has(p_class), StringName());
return global_classes[p_class].base;
}
StringName ScriptServer::get_global_class_native_base(const String &p_class) {
ERR_FAIL_COND_V(!global_classes.has(p_class), String());
StringName ScriptServer::get_global_class_native_base(const StringName &p_class) {
ERR_FAIL_COND_V(!global_classes.has(p_class), StringName());
String base = global_classes[p_class].base;
while (global_classes.has(base)) {
base = global_classes[base].base;
}
return base;
}
Ref<Script> ScriptServer::get_global_class_script(const StringName &p_class) {
ERR_FAIL_COND_V_MSG(!ScriptServer::is_global_class(p_class), Ref<Script>(), vformat("Class to load '%s' is not a script class.", p_class));
if (!ScriptServer::is_global_class(p_class)) {
return Ref<Script>();
}
String path = ScriptServer::get_global_class_path(p_class);
return ResourceLoader::load(path, "Script");
}
Variant ScriptServer::instantiate_global_class(const StringName &p_class) {
ERR_FAIL_COND_V_MSG(!global_classes.has(p_class), Variant(), vformat("Class to instantiate '%s' is not a script class.", p_class));
String native = get_global_class_native_base(p_class);
Object *o = ClassDB::instance(native);
ERR_FAIL_COND_V_MSG(!o, Variant(), vformat("Could not instantiate global script class '%s'. It extends native class '%s' which is not instantiable.", p_class, native));
REF ref;
Reference *r = Object::cast_to<Reference>(o);
if (r) {
ref = REF(r);
}
Variant ret;
if (ref.is_valid()) {
ret = ref;
} else {
ret = o;
}
Ref<Script> s = get_global_class_script(p_class);
ERR_FAIL_COND_V_MSG(s.is_null(), Variant(), vformat("Failed to load global script class '%s'.", p_class));
o->set_script(s.get_ref_ptr());
ScriptInstance *si = o->get_script_instance();
ERR_FAIL_COND_V_MSG(!si, Variant(), vformat("Failed to create script instance for global script class '%s'.", p_class));
return ret;
}
void ScriptServer::get_global_class_list(List<StringName> *r_global_classes) {
const StringName *K = nullptr;
List<StringName> classes;

View file

@ -37,6 +37,7 @@
#include "core/resource.h"
class ScriptLanguage;
class Script;
typedef void (*ScriptEditRequestFunction)(const String &p_path);
@ -59,6 +60,7 @@ class ScriptServer {
};
static HashMap<StringName, GlobalScriptClass> global_classes;
static HashMap<String, StringName> global_class_paths;
public:
static ScriptEditRequestFunction edit_request_func;
@ -81,9 +83,12 @@ public:
static void remove_global_class(const StringName &p_class);
static bool is_global_class(const StringName &p_class);
static StringName get_global_class_language(const StringName &p_class);
static String get_global_class_path(const String &p_class);
static StringName get_global_class_base(const String &p_class);
static StringName get_global_class_native_base(const String &p_class);
static String get_global_class_path(const StringName &p_class);
static StringName get_global_class_name(const String &p_path);
static StringName get_global_class_base(const StringName &p_class);
static StringName get_global_class_native_base(const StringName &p_class);
static Ref<Script> get_global_class_script(const StringName &p_class);
static Variant instantiate_global_class(const StringName &p_class);
static void get_global_class_list(List<StringName> *r_global_classes);
static void save_global_classes();
@ -283,6 +288,7 @@ public:
virtual String make_function(const String &p_class, const String &p_name, const PoolStringArray &p_args) const = 0;
virtual Error open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col) { return ERR_UNAVAILABLE; }
virtual bool overrides_external_editor() { return false; }
virtual bool has_delayed_script_class_metadata() const { return false; }
virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_force, String &r_call_hint) { return ERR_UNAVAILABLE; }

View file

@ -77,6 +77,9 @@
<member name="ResourceSaver" type="ResourceSaver" setter="" getter="">
The [ResourceSaver] singleton.
</member>
<member name="ScriptServer" type="ScriptServer" setter="" getter="">
The [ScriptServer] singleton.
</member>
<member name="TranslationServer" type="TranslationServer" setter="" getter="">
The [TranslationServer] singleton.
</member>

View file

@ -31,6 +31,15 @@
[b]Warning:[/b] Removing and freeing this node will render the editor useless and may cause a crash.
</description>
</method>
<method name="get_class_icon">
<return type="Texture" />
<argument index="0" name="class" type="String" />
<argument index="1" name="fallback" type="String" />
<description>
Returns the editor icon bound to a class name or the [code]fallback[/code] class's icon if not found. The [code]fallback[/code] defaults to "Object". If still not found, returns [code]null[/code].
[b]Node:[/b] This includes icons from custom types (see [method EditorPlugin.add_custom_type]) and global script classes from [ScriptServer].
</description>
</method>
<method name="get_current_path" qualifiers="const">
<return type="String" />
<description>
@ -78,6 +87,15 @@
[b]Warning:[/b] Removing and freeing this node will render a part of the editor useless and may cause a crash.
</description>
</method>
<method name="get_object_icon">
<return type="Texture" />
<argument index="0" name="object" type="Object" />
<argument index="1" name="fallback" type="String" />
<description>
Returns the editor icon bound to [Object] [code]object[/code] or the class [code]fallback[/code] if a type cannot be determined. If [code]object[/code] extends [Script], then return the editor icon bound to the scripted class, not the actual script. If still not found, return [code]null[/code].
[b]Note:[/b] if you need the editor icon for a script such as [GDScript], use [method get_class_icon].
</description>
</method>
<method name="get_open_scenes" qualifiers="const">
<return type="Array" />
<description>

View file

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="ScriptServer" inherits="Object" version="3.5">
<brief_description>
Global script class management singleton.
</brief_description>
<description>
ScriptServer manages all information related to global script classes in Godot projects, similar to [ClassDB] for engine classes. Scripts independently opt-in to become global classes. With it, you can check if a script has a global name or icon, what its base classes are, or even instantiate them directly.
[b]Note:[/b] This class shouldn't be instantiated directly. Instead, access the singleton through a global variable.
</description>
<tutorials>
<link>https://docs.godotengine.org/en/stable/getting_started/scripting/gdscript/gdscript_basics.html#classes</link>
</tutorials>
<methods>
<method name="get_global_class_base" qualifiers="const">
<return type="String" />
<argument index="0" name="class" type="String" />
<description>
Returns the class name that the script named [code]class[/code] directly extends. This may be an engine class or another global script class.
</description>
</method>
<method name="get_global_class_list" qualifiers="const">
<return type="Array" />
<description>
Returns the names of all global script class names known by the ScriptServer.
</description>
</method>
<method name="get_global_class_name" qualifiers="const">
<return type="String" />
<argument index="0" name="path" type="String" />
<description>
Returns the global class name bound to the [Script] at [code]path[/code].
</description>
</method>
<method name="get_global_class_native_base" qualifiers="const">
<return type="String" />
<argument index="0" name="class" type="String" />
<description>
Returns the native engine class that the script named [code]class[/code] eventually extends.
</description>
</method>
<method name="get_global_class_path" qualifiers="const">
<return type="String" />
<argument index="0" name="class" type="String" />
<description>
Returns the file path to the script resource named [code]class[/code].
</description>
</method>
<method name="get_global_class_script" qualifiers="const">
<return type="Script" />
<argument index="0" name="class" type="String" />
<description>
Returns the loaded [Script] named [code]class[/code].
</description>
</method>
<method name="instantiate_global_class" qualifiers="const">
<return type="Variant" />
<argument index="0" name="class" type="String" />
<description>
Returns a new instance of the scripted type defined by the script named [code]class[/code].
</description>
</method>
<method name="is_global_class" qualifiers="const">
<return type="bool" />
<argument index="0" name="class" type="String" />
<description>
Returns [code]true[/code] if the name [code]class[/code] is a global script class.
</description>
</method>
</methods>
<constants>
</constants>
</class>

View file

@ -147,17 +147,18 @@ void CreateDialog::add_type(const String &p_type, HashMap<String, TreeItem *> &p
return;
}
} else {
if (!search_loaded_scripts.has(p_type)) {
search_loaded_scripts[p_type] = ed.script_class_load_script(p_type);
}
if (!ScriptServer::is_global_class(p_type) || !ed.script_class_is_parent(p_type, base_type)) {
return;
}
if (!search_loaded_scripts.has(p_type)) {
search_loaded_scripts[p_type] = ScriptServer::get_global_class_script(p_type);
}
String script_path = ScriptServer::get_global_class_path(p_type);
if (script_path.find("res://addons/", 0) != -1) {
if (!EditorNode::get_singleton()->is_addon_plugin_enabled(script_path.get_slicec('/', 3))) {
String cfg_path = script_path.plus_file("plugin.cfg");
if (FileAccess::exists(cfg_path) && !EditorNode::get_singleton()->is_addon_plugin_enabled(script_path.get_slicec('/', 3))) {
return;
}
}
@ -186,7 +187,11 @@ void CreateDialog::add_type(const String &p_type, HashMap<String, TreeItem *> &p
item->set_text(0, p_type);
} else {
item->set_metadata(0, p_type);
item->set_text(0, p_type + " (" + ScriptServer::get_global_class_path(p_type).get_file() + ")");
String text = p_type;
if (!EDITOR_GET("interface/editors/create_dialog_hide_script_class_filepath")) {
text += " (" + ScriptServer::get_global_class_path(p_type).get_file() + ")";
}
item->set_text(0, text);
}
if (!can_instance) {
item->set_custom_color(0, get_color("disabled_font_color", "Editor"));
@ -348,7 +353,7 @@ void CreateDialog::_update_search() {
bool cpp_type2 = cpp_type;
if (!cpp_type && !search_loaded_scripts.has(type)) {
search_loaded_scripts[type] = ed.script_class_load_script(type);
search_loaded_scripts[type] = ScriptServer::get_global_class_script(type);
}
while (type2 != "" && (cpp_type2 ? ClassDB::is_parent_class(type2, base_type) : ed.script_class_is_parent(type2, base_type)) && type2 != base_type) {
@ -361,7 +366,7 @@ void CreateDialog::_update_search() {
cpp_type2 = cpp_type2 || ClassDB::class_exists(type2); // Built-in class can't inherit from custom type, so we can skip the check if it's already true.
if (!cpp_type2 && !search_loaded_scripts.has(type2)) {
search_loaded_scripts[type2] = ed.script_class_load_script(type2);
search_loaded_scripts[type2] = ScriptServer::get_global_class_script(type2);
}
}

View file

@ -865,12 +865,34 @@ void EditorData::get_plugin_window_layout(Ref<ConfigFile> p_layout) {
}
}
bool EditorData::script_class_is_parent(const String &p_class, const String &p_inherits) {
bool EditorData::class_equals_or_inherits(const StringName &p_class, const StringName &p_inherits) const {
if (p_class == p_inherits) {
return true;
}
if (ScriptServer::is_global_class(p_class)) {
return script_class_is_parent(p_class, p_inherits);
}
if (custom_types.has(p_inherits)) {
const Vector<EditorData::CustomType> &v = custom_types[p_inherits];
for (int i = 0; i < v.size(); i++) {
if (v[i].name == p_class) {
return true;
}
}
}
if (ClassDB::class_exists(p_class)) {
return ClassDB::is_parent_class(p_class, p_inherits);
}
return false;
}
bool EditorData::script_class_is_parent(const StringName &p_class, const StringName &p_inherits) const {
if (!ScriptServer::is_global_class(p_class)) {
return false;
}
String base = script_class_get_base(p_class);
Ref<Script> script = script_class_load_script(p_class);
Ref<Script> script = ScriptServer::get_global_class_script(p_class);
ERR_FAIL_COND_V_MSG(script.is_null(), false, vformat("Global script class '%s' failed to load."));
Ref<Script> base_script = script->get_base_script();
while (p_inherits != base) {
@ -887,25 +909,29 @@ bool EditorData::script_class_is_parent(const String &p_class, const String &p_i
return true;
}
StringName EditorData::script_class_get_base(const String &p_class) const {
Ref<Script> script = script_class_load_script(p_class);
StringName EditorData::script_class_get_base(const StringName &p_class) const {
if (!ScriptServer::is_global_class(p_class)) {
return StringName();
}
Ref<Script> script = ScriptServer::get_global_class_script(p_class);
if (script.is_null()) {
return StringName();
}
Ref<Script> base_script = script->get_base_script();
if (base_script.is_null()) {
return ScriptServer::get_global_class_base(p_class);
return ScriptServer::get_global_class_native_base(p_class);
}
return script->get_language()->get_global_class_name(base_script->get_path());
return ScriptServer::get_global_class_name(base_script->get_path());
}
Variant EditorData::script_class_instance(const String &p_class) {
Variant EditorData::script_class_instance(const StringName &p_class) const {
if (ScriptServer::is_global_class(p_class)) {
Variant obj = ClassDB::instance(ScriptServer::get_global_class_native_base(p_class));
if (obj) {
Ref<Script> script = script_class_load_script(p_class);
Ref<Script> script = ScriptServer::get_global_class_script(p_class);
if (script.is_valid()) {
((Object *)obj)->set_script(script.get_ref_ptr());
}
@ -915,25 +941,16 @@ Variant EditorData::script_class_instance(const String &p_class) {
return Variant();
}
Ref<Script> EditorData::script_class_load_script(const String &p_class) const {
if (!ScriptServer::is_global_class(p_class)) {
return Ref<Script>();
}
String path = ScriptServer::get_global_class_path(p_class);
return ResourceLoader::load(path, "Script");
}
void EditorData::script_class_set_icon_path(const String &p_class, const String &p_icon_path) {
void EditorData::script_class_set_icon_path(const StringName &p_class, const String &p_icon_path) {
_script_class_icon_paths[p_class] = p_icon_path;
}
String EditorData::script_class_get_icon_path(const String &p_class) const {
String EditorData::script_class_get_icon_path(const StringName &p_class) const {
if (!ScriptServer::is_global_class(p_class)) {
return String();
}
String current = p_class;
StringName current = p_class;
String ret = _script_class_icon_paths[current];
while (ret.empty()) {
current = script_class_get_base(current);
@ -946,12 +963,25 @@ String EditorData::script_class_get_icon_path(const String &p_class) const {
return ret;
}
StringName EditorData::script_class_get_name(const String &p_path) const {
return _script_class_file_to_path.has(p_path) ? _script_class_file_to_path[p_path] : StringName();
}
void EditorData::script_class_set_name(const String &p_path, const StringName &p_class) {
_script_class_file_to_path[p_path] = p_class;
Ref<Script> EditorData::script_class_get_base_from_anonymous_path(const String &p_path) const {
StringName name = ScriptServer::get_global_class_name(p_path);
if (name != StringName()) {
return nullptr;
}
Ref<Script> script = ResourceLoader::load(p_path, "Script");
if (script.is_null()) {
return nullptr;
}
do {
if (ScriptServer::get_global_class_name(script->get_path()) != StringName()) {
return script;
}
if (script->get_path().find("::") != -1) {
WARN_PRINT_ONCE("If you remove a built-in script that derives a script class, inheritance cannot be determined. The entire script is removed.");
}
script = script->get_base_script();
} while (script.is_valid());
return nullptr;
}
void EditorData::script_class_save_icon_paths() {
@ -960,8 +990,10 @@ void EditorData::script_class_save_icon_paths() {
Dictionary d;
for (List<StringName>::Element *E = keys.front(); E; E = E->next()) {
if (ScriptServer::is_global_class(E->get())) {
d[E->get()] = _script_class_icon_paths[E->get()];
StringName name = E->get();
String icon_path = _script_class_icon_paths[name];
if (ScriptServer::is_global_class(name)) {
d[name] = icon_path;
}
}
@ -992,11 +1024,8 @@ void EditorData::script_class_load_icon_paths() {
d.get_key_list(&keys);
for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
String name = E->get().operator String();
StringName name = E->get().operator StringName();
_script_class_icon_paths[name] = d[name];
String path = ScriptServer::get_global_class_path(name);
script_class_set_name(path, name);
}
}
}

View file

@ -142,7 +142,6 @@ private:
bool _find_updated_instances(Node *p_root, Node *p_node, Set<String> &checked_paths);
HashMap<StringName, String> _script_class_icon_paths;
HashMap<String, StringName> _script_class_file_to_path;
public:
EditorPlugin *get_editor(Object *p_object);
@ -211,17 +210,15 @@ public:
void notify_edited_scene_changed();
void notify_resource_saved(const Ref<Resource> &p_resource);
bool script_class_is_parent(const String &p_class, const String &p_inherits);
StringName script_class_get_base(const String &p_class) const;
Variant script_class_instance(const String &p_class);
bool class_equals_or_inherits(const StringName &p_class, const StringName &p_inherits) const;
bool script_class_is_parent(const StringName &p_class, const StringName &p_inherits) const;
StringName script_class_get_base(const StringName &p_class) const;
Variant script_class_instance(const StringName &p_class) const;
Ref<Script> script_class_load_script(const String &p_class) const;
Ref<Script> script_class_get_base_from_anonymous_path(const String &p_path) const;
StringName script_class_get_name(const String &p_path) const;
void script_class_set_name(const String &p_path, const StringName &p_class);
String script_class_get_icon_path(const String &p_class) const;
void script_class_set_icon_path(const String &p_class, const String &p_icon_path);
String script_class_get_icon_path(const StringName &p_class) const;
void script_class_set_icon_path(const StringName &p_class, const String &p_icon_path);
void script_class_clear_icon_paths() { _script_class_icon_paths.clear(); }
void script_class_save_icon_paths();
void script_class_load_icon_paths();

View file

@ -325,6 +325,7 @@ void EditorFileSystem::_save_filesystem_cache() {
void EditorFileSystem::_thread_func(void *_userdata) {
EditorFileSystem *sd = (EditorFileSystem *)_userdata;
sd->init_compiled_lang_script_class_file_cache();
sd->_scan_filesystem();
}
@ -1356,20 +1357,29 @@ Vector<String> EditorFileSystem::_get_dependencies(const String &p_path) {
String EditorFileSystem::_get_global_script_class(const String &p_type, const String &p_path, String *r_extends, String *r_icon_path) const {
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
if (ScriptServer::get_language(i)->handles_global_class_type(p_type)) {
String global_name;
String extends;
String icon_path;
global_name = ScriptServer::get_language(i)->get_global_class_name(p_path, &extends, &icon_path);
*r_extends = extends;
*r_icon_path = icon_path;
return global_name;
ScriptLanguage *lang = ScriptServer::get_language(i);
if (lang->handles_global_class_type(p_type)) {
if (lang->has_delayed_script_class_metadata() && compiled_lang_script_class_file_cache.has(p_path)) {
Dictionary d = compiled_lang_script_class_file_cache[p_path];
if (r_extends) {
*r_extends = d["base"].operator String();
}
if (r_icon_path) {
*r_icon_path = d.has("icon_path") ? d["icon_path"] : "";
}
return d["class"].operator String();
} else {
return lang->get_global_class_name(p_path, r_extends, r_icon_path);
}
}
}
*r_extends = String();
*r_icon_path = String();
return String();
if (r_extends) {
*r_extends = "";
}
if (r_icon_path) {
*r_icon_path = "";
}
return "";
}
void EditorFileSystem::_scan_script_classes(EditorFileSystemDirectory *p_dir) {
@ -1388,7 +1398,6 @@ void EditorFileSystem::_scan_script_classes(EditorFileSystemDirectory *p_dir) {
}
ScriptServer::add_global_class(files[i]->script_class_name, files[i]->script_class_extends, lang, p_dir->get_file_path(i));
EditorNode::get_editor_data().script_class_set_icon_path(files[i]->script_class_name, files[i]->script_class_icon_path);
EditorNode::get_editor_data().script_class_set_name(files[i]->file, files[i]->script_class_name);
}
for (int i = 0; i < p_dir->get_subdir_count(); i++) {
_scan_script_classes(p_dir->get_subdir(i));
@ -1418,6 +1427,56 @@ void EditorFileSystem::update_script_classes() {
ResourceSaver::add_custom_savers();
}
void EditorFileSystem::update_file_script_class_metadata(const String &p_path, const StringName &p_name, const StringName &p_base, const StringName &p_language, const String &p_icon_path) {
EditorFileSystemDirectory *fs = NULL;
int cpos = -1;
if (!_find_file(p_path, &fs, cpos)) {
if (!fs)
return;
}
EditorFileSystemDirectory::FileInfo *fi = fs->files[cpos];
fi->script_class_name = p_name;
fi->script_class_extends = p_base;
fi->script_class_icon_path = p_icon_path;
if (p_name != StringName() && !ScriptServer::is_global_class(p_name)) {
ScriptServer::add_global_class(p_name, p_base, p_language, p_path);
EditorNode::get_editor_data().script_class_set_icon_path(p_name, p_icon_path);
}
}
void EditorFileSystem::init_compiled_lang_script_class_file_cache() {
if (compiled_lang_script_class_file_cache.empty() && ProjectSettings::get_singleton()->has_setting("_global_script_classes")) {
Array script_classes = ProjectSettings::get_singleton()->get_setting("_global_script_classes");
Dictionary script_class_icons = ProjectSettings::get_singleton()->get_setting("_global_script_class_icons");
Set<StringName> compiled_language_names;
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
ScriptLanguage *lang = ScriptServer::get_language(i);
if (lang->has_delayed_script_class_metadata()) {
String n = lang->get_name();
compiled_language_names.insert(n);
}
}
for (int i = 0; i < script_classes.size(); i++) {
Dictionary d = script_classes[i];
StringName c = d["class"];
String p = d["path"];
StringName lg = d["language"];
if (compiled_language_names.has(lg)) {
String ip = script_class_icons[c];
d["icon_path"] = ip;
compiled_lang_script_class_file_cache[p] = d;
}
}
}
}
void EditorFileSystem::remove_compiled_lang_script_class_file_cache(const String &p_file) {
if (compiled_lang_script_class_file_cache.has(p_file)) {
compiled_lang_script_class_file_cache.erase(p_file);
}
}
void EditorFileSystem::_queue_update_script_classes() {
if (update_script_classes_queued.is_set()) {
return;

View file

@ -159,6 +159,8 @@ class EditorFileSystem : public Node {
void _save_late_updated_files();
HashMap<String, Dictionary> compiled_lang_script_class_file_cache; // keep track of script classes from compiled languages
EditorFileSystemDirectory *filesystem;
static EditorFileSystem *singleton;
@ -234,8 +236,8 @@ class EditorFileSystem : public Node {
void _scan_script_classes(EditorFileSystemDirectory *p_dir);
SafeFlag update_script_classes_queued;
void _queue_update_script_classes();
String _get_global_script_class(const String &p_type, const String &p_path, String *r_extends, String *r_icon_path) const;
String _get_global_script_class(const String &p_type, const String &p_path, String *r_extends = nullptr, String *r_icon_path = nullptr) const;
//String _get_global_class_name(String p_path, String *p_base = nullptr, String *p_icon_path = nullptr);
static Error _resource_import(const String &p_path);
@ -270,6 +272,9 @@ public:
void reimport_files(const Vector<String> &p_files);
void update_script_classes();
void update_file_script_class_metadata(const String &p_path, const StringName &p_name, const StringName &p_base, const StringName &p_language, const String &p_icon_path);
void remove_compiled_lang_script_class_file_cache(const String &p_file);
void init_compiled_lang_script_class_file_cache();
bool is_group_file(const String &p_path) const;
void move_group_file(const String &p_path, const String &p_new_path);

View file

@ -3857,9 +3857,9 @@ Ref<Script> EditorNode::get_object_custom_type_base(const Object *p_object) cons
// return name;
// should probably be deprecated in 4.x
StringName base = script->get_instance_base_type();
if (base != StringName() && EditorNode::get_editor_data().get_custom_types().has(base)) {
const Vector<EditorData::CustomType> &types = EditorNode::get_editor_data().get_custom_types()[base];
StringName native = script->get_instance_base_type();
if (native != StringName() && EditorNode::get_editor_data().get_custom_types().has(native)) {
const Vector<EditorData::CustomType> &types = EditorNode::get_editor_data().get_custom_types()[native];
Ref<Script> base_script = script;
while (base_script.is_valid()) {
@ -3887,7 +3887,7 @@ StringName EditorNode::get_object_custom_type_name(const Object *p_object) const
if (script.is_valid()) {
Ref<Script> base_script = script;
while (base_script.is_valid()) {
StringName name = EditorNode::get_editor_data().script_class_get_name(base_script->get_path());
StringName name = ScriptServer::get_global_class_name(base_script->get_path());
if (name != StringName()) {
return name;
}
@ -3953,7 +3953,7 @@ Ref<Texture> EditorNode::get_object_icon(const Object *p_object, const String &p
if (script.is_valid()) {
Ref<Script> base_script = script;
while (base_script.is_valid()) {
StringName name = EditorNode::get_editor_data().script_class_get_name(base_script->get_path());
StringName name = ScriptServer::get_global_class_name(base_script->get_path());
String icon_path = EditorNode::get_editor_data().script_class_get_icon_path(name);
Ref<ImageTexture> icon = _load_custom_class_icon(icon_path);
if (icon.is_valid()) {
@ -3993,14 +3993,15 @@ Ref<Texture> EditorNode::get_object_icon(const Object *p_object, const String &p
Ref<Texture> EditorNode::get_class_icon(const String &p_class, const String &p_fallback) const {
ERR_FAIL_COND_V_MSG(p_class.empty(), nullptr, "Class name cannot be empty.");
EditorData &ed = get_editor_data();
if (ScriptServer::is_global_class(p_class)) {
Ref<ImageTexture> icon;
Ref<Script> script = EditorNode::get_editor_data().script_class_load_script(p_class);
Ref<Script> script = ScriptServer::get_global_class_script(p_class);
StringName name = p_class;
while (script.is_valid()) {
name = EditorNode::get_editor_data().script_class_get_name(script->get_path());
String current_icon_path = EditorNode::get_editor_data().script_class_get_icon_path(name);
name = ScriptServer::get_global_class_name(script->get_path());
String current_icon_path = ed.script_class_get_icon_path(name);
icon = _load_custom_class_icon(current_icon_path);
if (icon.is_valid()) {
return icon;
@ -4015,7 +4016,7 @@ Ref<Texture> EditorNode::get_class_icon(const String &p_class, const String &p_f
return icon;
}
const Map<String, Vector<EditorData::CustomType>> &p_map = EditorNode::get_editor_data().get_custom_types();
const Map<String, Vector<EditorData::CustomType>> &p_map = ed.get_custom_types();
for (const Map<String, Vector<EditorData::CustomType>>::Element *E = p_map.front(); E; E = E->next()) {
const Vector<EditorData::CustomType> &ct = E->value();
for (int i = 0; i < ct.size(); ++i) {

View file

@ -309,6 +309,14 @@ bool EditorInterface::is_distraction_free_mode_enabled() const {
return EditorNode::get_singleton()->is_distraction_free_mode_enabled();
}
Ref<Texture> EditorInterface::get_class_icon(const String &p_class, const String &p_fallback) {
return EditorNode::get_singleton()->get_class_icon(p_class, p_fallback);
}
Ref<Texture> EditorInterface::get_object_icon(const Object *p_object, const String &p_fallback) {
return EditorNode::get_singleton()->get_object_icon(p_object, p_fallback);
}
void EditorInterface::_bind_methods() {
ClassDB::bind_method(D_METHOD("inspect_object", "object", "for_property", "inspector_only"), &EditorInterface::inspect_object, DEFVAL(String()), DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_selection"), &EditorInterface::get_selection);
@ -349,6 +357,9 @@ void EditorInterface::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_distraction_free_mode", "enter"), &EditorInterface::set_distraction_free_mode);
ClassDB::bind_method(D_METHOD("is_distraction_free_mode_enabled"), &EditorInterface::is_distraction_free_mode_enabled);
ClassDB::bind_method(D_METHOD("get_class_icon", "class", "fallback"), &EditorInterface::get_class_icon);
ClassDB::bind_method(D_METHOD("get_object_icon", "object", "fallback"), &EditorInterface::get_object_icon);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "distraction_free_mode"), "set_distraction_free_mode", "is_distraction_free_mode_enabled");
}

View file

@ -116,6 +116,9 @@ public:
void set_distraction_free_mode(bool p_enter);
bool is_distraction_free_mode_enabled() const;
Ref<Texture> get_class_icon(const String &p_name, const String &p_fallback);
Ref<Texture> get_object_icon(const Object *p_object, const String &p_fallback);
EditorInterface();
};

View file

@ -2953,13 +2953,14 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ
EditorPropertyResource *editor = memnew(EditorPropertyResource);
editor->setup(p_object, p_path, p_hint == PROPERTY_HINT_RESOURCE_TYPE ? p_hint_text : "Resource");
EditorData &ed = EditorNode::get_editor_data();
if (p_hint == PROPERTY_HINT_RESOURCE_TYPE) {
String open_in_new = EDITOR_GET("interface/inspector/resources_to_open_in_new_inspector");
for (int i = 0; i < open_in_new.get_slice_count(","); i++) {
String type = open_in_new.get_slicec(',', i).strip_edges();
for (int j = 0; j < p_hint_text.get_slice_count(","); j++) {
String inherits = p_hint_text.get_slicec(',', j);
if (ClassDB::is_parent_class(inherits, type)) {
if (ed.class_equals_or_inherits(inherits, type)) {
editor->set_use_sub_inspector(false);
}
}

View file

@ -565,6 +565,7 @@ class EditorPropertyResource : public EditorProperty {
bool _can_use_sub_inspector(const RES &p_resource);
void _open_editor_pressed();
void _fold_other_editors(Object *p_self);
void _update_property_bg();
protected:

View file

@ -60,7 +60,15 @@ void EditorResourcePicker::_update_resource() {
assign_button->set_text(edited_resource->get_path().get_file());
assign_button->set_tooltip(edited_resource->get_path());
} else {
assign_button->set_text(edited_resource->get_class());
String class_name = edited_resource->get_class();
Ref<Script> res_script = edited_resource->get_script();
if (res_script.is_valid()) {
String script_name = ScriptServer::get_global_class_name(res_script->get_path());
if (!script_name.empty()) {
class_name = script_name;
}
}
assign_button->set_text(class_name);
}
if (edited_resource->get_path().is_resource_file()) {
@ -118,9 +126,19 @@ void EditorResourcePicker::_file_selected(const String &p_path) {
if (base_type != "") {
bool any_type_matches = false;
StringName res_type = loaded_resource->get_class();
Ref<Script> res_script = loaded_resource->get_script();
if (res_script.is_valid()) {
StringName script_type = ScriptServer::get_global_class_name(res_script->get_path());
if (script_type != StringName()) {
res_type = script_type;
}
}
for (int i = 0; i < base_type.get_slice_count(","); i++) {
String base = base_type.get_slice(",", i);
if (loaded_resource->is_class(base)) {
if (EditorNode::get_editor_data().class_equals_or_inherits(res_type, base)) {
any_type_matches = true;
break;
}
@ -185,7 +203,9 @@ void EditorResourcePicker::_update_menu_items() {
paste_valid = true;
} else {
for (int i = 0; i < base_type.get_slice_count(","); i++) {
if (ClassDB::class_exists(cb->get_class()) && ClassDB::is_parent_class(cb->get_class(), base_type.get_slice(",", i))) {
StringName script_name = ScriptServer::get_global_class_name(cb->get_path());
StringName class_name = script_name != StringName() ? script_name : StringName(cb->get_class());
if (EditorNode::get_editor_data().class_equals_or_inherits(class_name, base_type.get_slice(",", i))) {
paste_valid = true;
break;
}
@ -213,12 +233,7 @@ void EditorResourcePicker::_update_menu_items() {
}
for (int i = 0; i < conversions.size(); i++) {
String what = conversions[i]->converts_to();
Ref<Texture> icon;
if (has_icon(what, "EditorIcons")) {
icon = get_icon(what, "EditorIcons");
} else {
icon = get_icon(what, "Resource");
}
Ref<Texture> icon = EditorNode::get_singleton()->get_class_icon(what, Resource::get_class_static());
edit_menu->add_icon_item(icon, vformat(TTR("Convert to %s"), what), CONVERT_BASE_ID + i);
}
@ -296,10 +311,20 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) {
propvalues.push_back(p);
}
String orig_type = edited_resource->get_class();
Object *inst = ClassDB::instance(orig_type);
Ref<Resource> unique_resource = Ref<Resource>(Object::cast_to<Resource>(inst));
ERR_FAIL_COND(unique_resource.is_null());
Ref<Resource> inst;
Ref<Script> res_script = edited_resource->get_script();
if (res_script.is_valid()) {
StringName script_name = ScriptServer::get_global_class_name(res_script->get_path());
if (ScriptServer::is_global_class(script_name)) {
inst = ScriptServer::instantiate_global_class(script_name);
}
}
if (inst.is_null()) {
inst = ClassDB::instance(edited_resource->get_class());
}
ERR_FAIL_COND_MSG(inst.is_null(), "Failed to instantiate resource during Make Unique.");
Ref<Resource> unique_resource = Ref<Resource>(inst);
ERR_FAIL_COND_MSG(unique_resource.is_null(), "Failed to copy resource reference during Make Unique.");
for (List<Pair<String, Variant>>::Element *E = propvalues.front(); E; E = E->next()) {
Pair<String, Variant> &p = E->get();
@ -360,13 +385,7 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) {
Variant obj;
if (ScriptServer::is_global_class(intype)) {
obj = ClassDB::instance(ScriptServer::get_global_class_native_base(intype));
if (obj) {
Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(intype));
if (script.is_valid()) {
((Object *)obj)->set_script(script.get_ref_ptr());
}
}
obj = ScriptServer::instantiate_global_class(intype);
} else {
obj = ClassDB::instance(intype);
}
@ -637,14 +656,18 @@ void EditorResourcePicker::drop_data_fw(const Point2 &p_point, const Variant &p_
for (Set<String>::Element *E = allowed_types.front(); E; E = E->next()) {
String at = E->get().strip_edges();
if (at == "SpatialMaterial" && ClassDB::is_parent_class(dropped_resource->get_class(), "Texture")) {
EditorData &ed = EditorNode::get_editor_data();
StringName script_name = ScriptServer::get_global_class_name(dropped_resource->get_path());
String class_name = script_name != StringName() ? script_name : StringName(dropped_resource->get_class());
if (at == "SpatialMaterial" && ed.class_equals_or_inherits(class_name, "Texture")) {
Ref<SpatialMaterial> mat = memnew(SpatialMaterial);
mat->set_texture(SpatialMaterial::TextureParam::TEXTURE_ALBEDO, dropped_resource);
dropped_resource = mat;
break;
}
if (at == "ShaderMaterial" && ClassDB::is_parent_class(dropped_resource->get_class(), "Shader")) {
if (at == "ShaderMaterial" && ed.class_equals_or_inherits(class_name, "Shader")) {
Ref<ShaderMaterial> mat = memnew(ShaderMaterial);
mat->set_shader(dropped_resource);
dropped_resource = mat;

View file

@ -1256,7 +1256,7 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p
Ref<Script> root_script = nullptr;
if (ScriptServer::is_global_class(root_type)) {
root_script = ResourceLoader::load(ScriptServer::get_global_class_path(root_type));
root_script = ScriptServer::get_global_class_script(root_type);
root_type = ScriptServer::get_global_class_base(root_type);
}

View file

@ -577,23 +577,64 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
return;
}
editor_data->get_undo_redo().create_action(TTR("Detach Script"));
editor_data->get_undo_redo().add_do_method(editor, "push_item", (Script *)nullptr);
Array update_array;
EditorData &ed = EditorNode::get_editor_data();
for (int i = 0; i < selection.size(); i++) {
Node *n = Object::cast_to<Node>(selection[i]);
Ref<Script> existing = n->get_script();
Ref<Script> empty = EditorNode::get_singleton()->get_object_custom_type_base(n);
if (existing != empty) {
editor_data->get_undo_redo().add_do_method(n, "set_script", empty);
editor_data->get_undo_redo().add_undo_method(n, "set_script", existing);
Ref<Script> base = nullptr;
if (existing.is_valid()) {
StringName script_class_name = ScriptServer::get_global_class_name(existing->get_path());
if (script_class_name != StringName()) {
update_array.clear();
print_error("Unable to remove script class '" + script_class_name + "'. Can only remove anonymous scripts.");
break;
}
base = ed.script_class_get_base_from_anonymous_path(existing->get_path());
if (base.is_null()) {
const Map<String, Vector<EditorData::CustomType>> &ct = EditorNode::get_editor_data().get_custom_types();
if (ct.has(n->get_class())) {
const Vector<EditorData::CustomType> &v = ct[n->get_class()];
String ct_name;
for (int j = 0; j < v.size() && !ct_name.empty(); j++) {
if (v[j].script == existing) {
ct_name = v[j].name;
}
}
if (!ct_name.empty()) {
update_array.clear();
print_error("Unable to remove custom type '" + ct_name + "'. Can only remove anonymous scripts.");
break;
}
}
base = EditorNode::get_singleton()->get_object_custom_type_base(n);
}
}
if (existing != base) {
Array an_update;
an_update.push_back(n);
an_update.push_back(base);
an_update.push_back(existing);
update_array.push_back(an_update);
}
}
if (update_array.size()) {
editor_data->get_undo_redo().create_action(TTR("Detach Script"));
editor_data->get_undo_redo().add_do_method(editor, "push_item", (Script *)NULL);
for (int i = 0; i < update_array.size(); i++) {
Array an_update = update_array[i];
Node *n = an_update[0];
Ref<Script> do_script = an_update[1];
Ref<Script> undo_script = an_update[2];
editor_data->get_undo_redo().add_do_method(n, "set_script", do_script);
editor_data->get_undo_redo().add_undo_method(n, "set_script", undo_script);
}
editor_data->get_undo_redo().add_do_method(this, "_update_script_button");
editor_data->get_undo_redo().add_undo_method(this, "_update_script_button");
editor_data->get_undo_redo().add_do_method(this, "_update_script_button");
editor_data->get_undo_redo().add_undo_method(this, "_update_script_button");
editor_data->get_undo_redo().commit_action();
editor_data->get_undo_redo().commit_action();
}
} break;
case TOOL_MOVE_UP:
case TOOL_MOVE_DOWN: {
@ -1225,6 +1266,7 @@ void SceneTreeDock::_notification(int p_what) {
button_add->set_icon(get_icon("Add", "EditorIcons"));
button_instance->set_icon(get_icon("Instance", "EditorIcons"));
button_create_script->set_icon(get_icon("ScriptCreate", "EditorIcons"));
button_extend_script->set_icon(get_icon("ScriptExtend", "EditorIcons"));
button_detach_script->set_icon(get_icon("ScriptRemove", "EditorIcons"));
filter->set_right_icon(get_icon("Search", "EditorIcons"));
@ -1301,6 +1343,7 @@ void SceneTreeDock::_notification(int p_what) {
button_add->set_icon(get_icon("Add", "EditorIcons"));
button_instance->set_icon(get_icon("Instance", "EditorIcons"));
button_create_script->set_icon(get_icon("ScriptCreate", "EditorIcons"));
button_extend_script->set_icon(get_icon("ScriptExtend", "EditorIcons"));
button_detach_script->set_icon(get_icon("ScriptRemove", "EditorIcons"));
button_2d->set_icon(get_icon("Node2D", "EditorIcons"));
button_3d->set_icon(get_icon("Spatial", "EditorIcons"));
@ -2082,29 +2125,49 @@ void SceneTreeDock::_delete_confirm(bool p_cut) {
void SceneTreeDock::_update_script_button() {
if (!profile_allow_script_editing) {
button_create_script->hide();
button_extend_script->hide();
button_detach_script->hide();
} else if (editor_selection->get_selection().size() == 0) {
button_create_script->hide();
button_extend_script->hide();
button_detach_script->hide();
} else if (editor_selection->get_selection().size() == 1) {
Node *n = editor_selection->get_selected_node_list()[0];
if (n->get_script().is_null()) {
button_create_script->show();
button_detach_script->hide();
Ref<Script> s = n->get_script();
if (s.is_valid()) {
if (ScriptServer::get_global_class_name(s->get_path()) != StringName()) {
button_create_script->hide();
button_extend_script->show();
button_detach_script->hide();
} else {
button_create_script->hide();
button_extend_script->hide();
button_detach_script->show();
}
} else {
button_create_script->hide();
button_detach_script->show();
button_create_script->show();
button_extend_script->hide();
button_detach_script->hide();
}
} else {
button_create_script->hide();
Array selection = editor_selection->get_selected_nodes();
for (int i = 0; i < selection.size(); i++) {
Node *n = Object::cast_to<Node>(selection[i]);
if (!n->get_script().is_null()) {
button_detach_script->show();
return;
Ref<Script> s = n->get_script();
if (s.is_valid()) {
if (ScriptServer::get_global_class_name(s->get_path()) != StringName()) {
button_extend_script->show();
button_detach_script->hide();
return;
} else {
button_extend_script->hide();
button_detach_script->show();
return;
}
}
}
button_extend_script->hide();
button_detach_script->hide();
}
}
@ -2295,6 +2358,11 @@ void SceneTreeDock::replace_node(Node *p_node, Node *p_by_node, bool p_keep_prop
List<PropertyInfo> pinfo;
n->get_property_list(&pinfo);
Ref<Script> s = n->get_script();
if (s.is_valid() && ScriptServer::get_global_class_name(s->get_path()) != StringName()) {
n->set_script(RefPtr());
}
for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) {
if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) {
continue;
@ -2681,7 +2749,7 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
menu->clear();
Ref<Script> existing_script;
bool exisiting_script_removable = true;
bool existing_script_removable = true;
if (selection.size() == 1) {
Node *selected = selection[0];
@ -2702,8 +2770,9 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
existing_script = selected->get_script();
if (EditorNode::get_singleton()->get_object_custom_type_base(selected) == existing_script) {
exisiting_script_removable = false;
if (EditorNode::get_singleton()->get_object_custom_type_base(selected) == existing_script ||
(existing_script.is_valid() && ScriptServer::get_global_class_name(existing_script->get_path()) != StringName())) {
existing_script_removable = false;
}
}
@ -2726,7 +2795,7 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
menu->add_icon_shortcut(get_icon("ScriptExtend", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/extend_script"), TOOL_EXTEND_SCRIPT);
}
}
if (existing_script.is_valid() && exisiting_script_removable) {
if (existing_script.is_valid() && existing_script_removable) {
add_separator = true;
menu->add_icon_shortcut(get_icon("ScriptRemove", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/detach_script"), TOOL_DETACH_SCRIPT);
} else if (full_selection.size() > 1) {
@ -2905,7 +2974,7 @@ void SceneTreeDock::attach_script_to_selected(bool p_extend) {
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
ScriptLanguage *l = ScriptServer::get_language(i);
if (l->get_type() == existing->get_class()) {
String name = l->get_global_class_name(existing->get_path());
String name = ScriptServer::get_global_class_name(existing->get_path());
if (ScriptServer::is_global_class(name) && EDITOR_GET("interface/editors/derive_script_globals_by_name").operator bool()) {
inherits = name;
} else if (l->can_inherit_from_file()) {
@ -3251,6 +3320,13 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel
filter_hbc->add_child(button_create_script);
button_create_script->hide();
button_extend_script = memnew(ToolButton);
button_extend_script->connect("pressed", this, "_tool_selected", make_binds(TOOL_EXTEND_SCRIPT, false));
button_extend_script->set_tooltip(TTR("Attach a new script extending the selected node's script."));
button_extend_script->set_shortcut(ED_GET_SHORTCUT("scene_tree/extend_script"));
filter_hbc->add_child(button_extend_script);
button_extend_script->hide();
button_detach_script = memnew(ToolButton);
button_detach_script->connect("pressed", this, "_tool_selected", make_binds(TOOL_DETACH_SCRIPT, false));
button_detach_script->set_tooltip(TTR("Detach the script from the selected node."));
@ -3380,6 +3456,7 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel
EDITOR_DEF("interface/editors/show_scene_tree_root_selection", true);
EDITOR_DEF("interface/editors/derive_script_globals_by_name", true);
EDITOR_DEF("interface/editors/create_dialog_hide_script_class_filepath", false);
EDITOR_DEF("_use_favorites_root_selection", false);
}

View file

@ -117,6 +117,7 @@ class SceneTreeDock : public VBoxContainer {
ToolButton *button_add;
ToolButton *button_instance;
ToolButton *button_create_script;
ToolButton *button_extend_script;
ToolButton *button_detach_script;
Button *button_2d;

View file

@ -208,15 +208,17 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent, bool p_scroll
Color accent = get_color("accent_color", "Editor");
Ref<Script> script = p_node->get_script();
if (!script.is_null() && EditorNode::get_singleton()->get_object_custom_type_base(p_node) != script) {
if (script.is_valid() &&
ScriptServer::get_global_class_name(script->get_path()) == StringName() &&
EditorNode::get_singleton()->get_object_custom_type_base(p_node) != script) {
//has script
item->add_button(0, get_icon("Script", "EditorIcons"), BUTTON_SCRIPT);
} else {
//has no script (or script is a custom type)
//has no script (or script is a custom type / script class)
item->set_custom_color(0, get_color("disabled_font_color", "Editor"));
item->set_selectable(0, false);
if (!script.is_null()) { // make sure to mark the script if a custom type
if (script.is_valid()) { // make sure to mark the script if a custom type or script class
item->add_button(0, get_icon("Script", "EditorIcons"), BUTTON_SCRIPT);
item->set_button_disabled(0, item->get_button_count(0) - 1, true);
}
@ -333,7 +335,8 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent, bool p_scroll
Ref<Script> script = p_node->get_script();
if (!script.is_null()) {
item->add_button(0, get_icon("Script", "EditorIcons"), BUTTON_SCRIPT, false, TTR("Open Script:") + " " + script->get_path());
if (EditorNode::get_singleton()->get_object_custom_type_base(p_node) == script) {
if (EditorNode::get_singleton()->get_object_custom_type_base(p_node) == script ||
ScriptServer::get_global_class_name(script->get_path()) != StringName()) {
item->set_button_color(0, item->get_button_count(0) - 1, Color(1, 1, 1, 0.5));
}
}

View file

@ -131,6 +131,7 @@ typedef struct {
const char **string_delimiters; // NULL terminated array
godot_bool has_named_classes;
godot_bool supports_builtin_mode;
godot_bool has_delayed_script_class_metadata;
godot_string (*get_template_source_code)(godot_pluginscript_language_data *p_data, const godot_string *p_class_name, const godot_string *p_base_class_name);
godot_bool (*validate)(godot_pluginscript_language_data *p_data, const godot_string *p_script, int *r_line_error, int *r_col_error, godot_string *r_test_error, const godot_string *p_path, godot_pool_string_array *r_functions);

View file

@ -146,6 +146,10 @@ bool PluginScriptLanguage::supports_builtin_mode() const {
return _desc.supports_builtin_mode;
}
bool PluginScriptLanguage::has_delayed_script_class_metadata() const {
return _desc.has_delayed_script_class_metadata;
}
int PluginScriptLanguage::find_function(const String &p_function, const String &p_code) const {
if (_desc.find_function) {
return _desc.find_function(_data, (godot_string *)&p_function, (godot_string *)&p_code);

View file

@ -80,6 +80,7 @@ public:
virtual bool has_named_classes() const;
virtual bool supports_builtin_mode() const;
virtual bool can_inherit_from_file() { return true; }
virtual bool has_delayed_script_class_metadata() const;
virtual int find_function(const String &p_function, const String &p_code) const;
virtual String make_function(const String &p_class, const String &p_name, const PoolStringArray &p_args) const;
virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_force, String &r_call_hint);

View file

@ -4628,19 +4628,41 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
Variant constant = static_cast<ConstantNode *>(subexpr)->value;
if (constant.get_type() == Variant::OBJECT) {
StringName class_name;
GDScriptNativeClass *native_class = Object::cast_to<GDScriptNativeClass>(constant);
if (native_class && ClassDB::is_parent_class(native_class->get_name(), "Resource")) {
if (native_class) {
if (ClassDB::is_parent_class(native_class->get_name(), "Resource")) {
class_name = native_class->get_name();
} else {
current_export = PropertyInfo();
_set_error("The export hint isn't a resource type.");
}
} else {
Ref<Script> res_script = constant;
StringName script_class;
if (res_script.is_valid()) {
script_class = res_script->get_language()->get_global_class_name(res_script->get_path());
if (ClassDB::is_parent_class(ScriptServer::get_global_class_native_base(script_class), "Resource")) {
class_name = script_class;
} else {
current_export = PropertyInfo();
_set_error("The exported script does not extend Resource.");
}
} else {
current_export = PropertyInfo();
_set_error("The exported script isn't a script class.");
}
}
if (class_name != StringName()) {
current_export.type = Variant::OBJECT;
current_export.hint = PROPERTY_HINT_RESOURCE_TYPE;
current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
current_export.hint_string = native_class->get_name();
current_export.class_name = native_class->get_name();
} else {
current_export = PropertyInfo();
_set_error("The export hint isn't a resource type.");
current_export.hint_string = class_name;
current_export.class_name = class_name;
}
} else if (constant.get_type() == Variant::DICTIONARY) {
// Enumeration
@ -4924,12 +4946,42 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
member._export.hint_string = member.data_type.native_type;
member._export.class_name = member.data_type.native_type;
} else {
_set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line);
_set_error(vformat("Invalid native export type. \"%s\" is not a Resource type.", member.data_type.native_type), member.line);
return;
}
} else if (member.data_type.kind == DataType::SCRIPT || member.data_type.kind == DataType::GDSCRIPT) {
if (member.data_type.script_type.is_null()) {
_set_error("Invalid script export type. Could not load member script value.", member.line);
return;
}
Ref<Script> s = member.data_type.script_type;
StringName class_name = s->get_language()->get_global_class_name(s->get_path());
if (class_name == StringName()) {
_set_error("Invalid script export type. The member is a script that has no global script class name.", member.line);
return;
}
if (!ClassDB::is_parent_class(ScriptServer::get_global_class_native_base(class_name), "Resource")) {
_set_error("Invalid script export type. The member is a script class that does not extend a Resource type.", member.line);
return;
}
member._export.type = Variant::OBJECT;
member._export.hint = PROPERTY_HINT_RESOURCE_TYPE;
member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
member._export.hint_string = class_name;
member._export.class_name = class_name;
} else if (member.data_type.kind == DataType::UNRESOLVED && ScriptServer::is_global_class(member.data_type.native_type)) {
StringName class_name = member.data_type.native_type;
if (ClassDB::is_parent_class(ScriptServer::get_global_class_native_base(class_name), "Resource")) {
member._export.type = Variant::OBJECT;
member._export.hint = PROPERTY_HINT_RESOURCE_TYPE;
member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
member._export.hint_string = class_name;
member._export.class_name = class_name;
}
#undef SETUP_SCRIPT_EXPORT
} else {
_set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line);
_set_error("Invalid export type. Only built-in types and native or script class Resource types can be exported.", member.line);
return;
}
}
@ -6011,7 +6063,22 @@ GDScriptParser::DataType GDScriptParser::_type_from_property(const PropertyInfo
ret.builtin_type = p_property.type;
if (p_property.type == Variant::OBJECT) {
ret.kind = DataType::NATIVE;
ret.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name;
ret.native_type = "Object";
if (p_property.class_name != StringName()) {
if (ScriptServer::is_global_class(p_property.class_name)) {
String p = ScriptServer::get_global_class_path(p_property.class_name);
ret.native_type = ScriptServer::get_global_class_native_base(p_property.class_name);
if (GDScriptLanguage::get_singleton()->get_extension() == p.get_extension()) {
ret.kind = DataType::GDSCRIPT;
ret.script_type = ResourceLoader::load(p, "GDScript");
} else {
ret.kind = DataType::SCRIPT;
ret.script_type = ResourceLoader::load(p, "Script");
}
} else {
ret.native_type = p_property.class_name;
}
}
} else {
ret.kind = DataType::BUILTIN;
}
@ -7914,13 +7981,25 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
}
// Check export hint
if (v.data_type.has_type && v._export.type != Variant::NIL) {
if (v._export.type != Variant::NIL) {
DataType export_type = _type_from_property(v._export);
if (!_is_type_compatible(v.data_type, export_type, true)) {
_set_error("The export hint's type (" + export_type.to_string() + ") doesn't match the variable's type (" +
v.data_type.to_string() + ").",
v.line);
return;
if (export_type.kind == DataType::GDSCRIPT || export_type.kind == DataType::SCRIPT) {
String class_name = v._export.class_name;
if (ScriptServer::is_global_class(class_name)) {
class_name = ScriptServer::get_global_class_native_base(class_name);
}
if (!ClassDB::is_parent_class(class_name, "Resource")) {
_set_error(vformat("Exported script-defined type (%s) must inherit from Resource.", export_type.to_string()), v.line);
return;
}
}
if (v.data_type.has_type) {
if (!_is_type_compatible(v.data_type, export_type, true)) {
_set_error(vformat("The export hint's type (%s) doesn't match the variable's type (%s).", export_type.to_string(), v.data_type.to_string()), v.line);
return;
}
}
}

View file

@ -522,6 +522,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;
}
@ -911,6 +942,11 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
#endif
script->signals_invalidated = true;
#ifdef TOOLS_ENABLED
EditorFileSystem::get_singleton()->remove_compiled_lang_script_class_file_cache(script->get_path());
EditorNode::get_editor_data().script_class_set_icon_path(script->get_script_class_name(), script->get_script_class_icon_path());
#endif
script->reload(p_soft_reload);
script->update_exports();
@ -1010,6 +1046,11 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
to_reload_state.push_back(script);
}
#ifdef TOOLS_ENABLED
ScriptServer::save_global_classes();
EditorNode::get_editor_data().script_class_save_icon_paths();
#endif
for (List<Ref<CSharpScript>>::Element *E = to_reload_state.front(); E; E = E->next()) {
Ref<CSharpScript> script = E->get();
@ -2583,7 +2624,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: '" +
@ -2604,7 +2647,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) {
GD_MONO_ASSERT_THREAD_ATTACHED;
if (p_variant_type == Variant::INT && p_type.type_encoding == MONO_TYPE_VALUETYPE && mono_class_is_enum(p_type.type_class->get_mono_ptr())) {
@ -2665,6 +2708,16 @@ int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, Manage
r_hint = PROPERTY_HINT_RESOURCE_TYPE;
r_hint_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.empty()) {
r_hint_string = p_given_hint_string;
}
} else if (p_allow_generics && p_variant_type == Variant::ARRAY) {
// Nested arrays are not supported in the inspector
@ -2690,7 +2743,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.");
@ -3209,6 +3262,16 @@ Error CSharpScript::reload(bool p_keep_state) {
load_script_signals(script_class, native);
_update_exports();
_update_global_script_class_settings();
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
// Force file cache update for compiled languages to update script class metadata
StringName n = script_class_name;
StringName b = script_class_base;
EditorFileSystem::get_singleton()->update_file_script_class_metadata(get_path(), n, b, get_language()->get_name(), script_class_icon_path);
}
#endif
}
return OK;
@ -3317,6 +3380,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.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.empty()) {
script_class_base = script_class->get_name();
}
break;
}
parent = parent->get_parent_class();
}
if (script_class_base.empty()) {
script_class_base = get_instance_base_type();
}
}
StringName CSharpScript::get_script_name() const {
return name;
}

View file

@ -103,6 +103,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;
struct Argument {
@ -140,7 +145,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_isref, Variant::CallError &r_error);
@ -202,6 +207,11 @@ public:
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();
StringName get_script_name() const;
CSharpScript();
@ -419,6 +429,11 @@ public:
virtual String _get_indentation() const;
/* TODO? */ virtual void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const {}
/* TODO */ virtual void add_global_constant(const StringName &p_variable, const Variant &p_value) {}
virtual bool has_delayed_script_class_metadata() const override { return true; }
/* 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 */
virtual String debug_get_error() const;

View file

@ -13,5 +13,20 @@ namespace Godot
this.hint = hint;
this.hintString = hintString;
}
public ExportAttribute(string className)
{
if (ClassDB.ClassExists(className) || ScriptServer.IsGlobalClass(className))
{
this.hint = PropertyHint.ResourceType;
this.hintString = className;
}
else
{
this.hint = PropertyHint.None;
this.hintString = "";
}
}
}
}

View file

@ -0,0 +1,17 @@
using System;
namespace Godot
{
[AttributeUsage(AttributeTargets.Class)]
public class GlobalAttribute : Attribute
{
private string name;
private string iconPath;
public GlobalAttribute(string name = "", string iconPath = "")
{
this.name = name;
this.iconPath = iconPath;
}
}
}

View file

@ -15,6 +15,7 @@
<Compile Include="Core\AABB.cs" />
<Compile Include="Core\Array.cs" />
<Compile Include="Core\Attributes\ExportAttribute.cs" />
<Compile Include="Core\Attributes\GlobalAttribute.cs" />
<Compile Include="Core\Attributes\GodotMethodAttribute.cs" />
<Compile Include="Core\Attributes\RPCAttributes.cs" />
<Compile Include="Core\Attributes\SignalAttribute.cs" />

View file

@ -132,6 +132,9 @@ void CachedData::clear_godot_api_cache() {
class_ExportAttribute = NULL;
field_ExportAttribute_hint = NULL;
field_ExportAttribute_hintString = NULL;
class_GlobalAttribute = NULL;
field_GlobalAttribute_name = NULL;
field_GlobalAttribute_iconPath = NULL;
class_SignalAttribute = NULL;
class_ToolAttribute = NULL;
class_RemoteAttribute = NULL;
@ -245,6 +248,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(RemoteAttribute, GODOT_API_CLASS(RemoteAttribute));

View file

@ -103,6 +103,9 @@ struct CachedData {
GDMonoClass *class_ExportAttribute;
GDMonoField *field_ExportAttribute_hint;
GDMonoField *field_ExportAttribute_hintString;
GDMonoClass *class_GlobalAttribute;
GDMonoField *field_GlobalAttribute_name;
GDMonoField *field_GlobalAttribute_iconPath;
GDMonoClass *class_SignalAttribute;
GDMonoClass *class_ToolAttribute;
GDMonoClass *class_RemoteAttribute;

View file

@ -165,6 +165,18 @@
Returns a node's position in pixels.
</description>
</method>
<method name="get_script_class_icon_path">
<return type="String" />
<description>
Returns this [VisualScript]'s global class icon path provided to the [ScriptServer].
</description>
</method>
<method name="get_script_class_name">
<return type="String" />
<description>
Returns this [VisualScript]'s global class name provided to the [ScriptServer].
</description>
</method>
<method name="get_variable_default_value" qualifiers="const">
<return type="Variant" />
<argument index="0" name="name" type="String" />
@ -334,6 +346,20 @@
Position a node on the screen.
</description>
</method>
<method name="set_script_class_icon_path">
<return type="void" />
<argument index="0" name="script_class_icon_path" type="String" />
<description>
Assigns the global class icon path that this [VisualScript] provides to the [ScriptServer].
</description>
</method>
<method name="set_script_class_name">
<return type="void" />
<argument index="0" name="script_class_name" type="String" />
<description>
Assigns the global class name that this [VisualScript] provides to the [ScriptServer].
</description>
</method>
<method name="set_variable_default_value">
<return type="void" />
<argument index="0" name="name" type="String" />

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="VisualScriptScriptClass" inherits="VisualScriptNode" version="3.5">
<brief_description>
Gets a global script class resource.
</brief_description>
<description>
This node returns a global script class with a given name. See the [ScriptServer] class for more information.
</description>
<tutorials>
</tutorials>
<methods>
</methods>
<members>
<member name="script_class" type="Script" setter="set_script_class" getter="get_script_class">
The global script class resource.
</member>
</members>
<constants>
</constants>
</class>

View file

@ -67,6 +67,7 @@ void register_visual_script_types() {
ClassDB::register_class<VisualScriptClassConstant>();
ClassDB::register_class<VisualScriptMathConstant>();
ClassDB::register_class<VisualScriptBasicTypeConstant>();
ClassDB::register_class<VisualScriptScriptClass>();
ClassDB::register_class<VisualScriptEngineSingleton>();
ClassDB::register_class<VisualScriptSceneNode>();
ClassDB::register_class<VisualScriptSceneTree>();

View file

@ -1112,6 +1112,13 @@ void VisualScript::_set_data(const Dictionary &p_data) {
}
}
if (d.has("script_class_name")) {
script_class_name = d["script_class_name"];
}
if (d.has("script_class_icon_path")) {
script_class_icon_path = d["script_class_icon_path"];
}
if (d.has("is_tool_script")) {
is_tool_script = d["is_tool_script"];
} else {
@ -1194,9 +1201,35 @@ Dictionary VisualScript::_get_data() const {
d["is_tool_script"] = is_tool_script;
d["vs_unify"] = true;
if (ScriptServer::is_global_class(script_class_name)) {
d["script_class_name"] = script_class_name;
d["script_class_icon_path"] = script_class_icon_path;
} else {
d["script_class_name"] = "";
d["script_class_icon_path"] = "";
}
return d;
}
String VisualScript::get_script_class_name() {
return script_class_name;
}
String VisualScript::get_script_class_icon_path() {
return script_class_icon_path;
}
void VisualScript::set_script_class_name(const String &p_name) {
if (p_name.is_valid_identifier()) {
script_class_name = p_name;
}
}
void VisualScript::set_script_class_icon_path(const String &p_path) {
script_class_icon_path = p_path;
}
void VisualScript::_bind_methods() {
ClassDB::bind_method(D_METHOD("_node_ports_changed"), &VisualScript::_node_ports_changed);
@ -1256,6 +1289,11 @@ void VisualScript::_bind_methods() {
ClassDB::bind_method(D_METHOD("_set_data", "data"), &VisualScript::_set_data);
ClassDB::bind_method(D_METHOD("_get_data"), &VisualScript::_get_data);
ClassDB::bind_method(D_METHOD("set_script_class_name", "script_class_name"), &VisualScript::set_script_class_name);
ClassDB::bind_method(D_METHOD("get_script_class_name"), &VisualScript::get_script_class_name);
ClassDB::bind_method(D_METHOD("set_script_class_icon_path", "script_class_icon_path"), &VisualScript::set_script_class_icon_path);
ClassDB::bind_method(D_METHOD("get_script_class_icon_path"), &VisualScript::get_script_class_icon_path);
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
ADD_SIGNAL(MethodInfo("node_ports_changed", PropertyInfo(Variant::STRING, "function"), PropertyInfo(Variant::INT, "id")));
@ -2306,6 +2344,27 @@ Error VisualScriptLanguage::execute_file(const String &p_path) {
void VisualScriptLanguage::finish() {
}
bool VisualScriptLanguage::handles_global_class_type(const String &p_type) const {
return p_type == "VisualScript";
}
String VisualScriptLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const {
Ref<VisualScript> script = ResourceLoader::load(p_path, "VisualScript");
if (script.is_null()) {
if (r_base_type)
*r_base_type = String();
if (r_icon_path)
*r_icon_path = String();
return String();
}
if (r_base_type)
*r_base_type = script->get_instance_base_type();
if (r_icon_path)
*r_icon_path = script->get_script_class_icon_path();
return script->get_script_class_name().is_valid_identifier() ? script->get_script_class_name() : "";
}
/* EDITOR FUNCTIONS */
void VisualScriptLanguage::get_reserved_words(List<String> *p_words) const {
}

View file

@ -241,6 +241,8 @@ private:
Map<Object *, VisualScriptInstance *> instances;
bool is_tool_script;
String script_class_name;
String script_class_icon_path;
#ifdef TOOLS_ENABLED
Set<PlaceHolderScriptInstance *> placeholders;
@ -360,6 +362,11 @@ public:
virtual bool are_subnodes_edited() const;
#endif
String get_script_class_name();
String get_script_class_icon_path();
void set_script_class_name(const String &p_name);
void set_script_class_icon_path(const String &p_path);
VisualScript();
~VisualScript();
};
@ -556,6 +563,9 @@ public:
virtual Error execute_file(const String &p_path);
virtual void finish();
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;
/* EDITOR FUNCTIONS */
virtual void get_reserved_words(List<String> *p_words) const;
virtual bool is_control_flow_keyword(String p_keyword) const;

View file

@ -1277,6 +1277,18 @@ void VisualScriptEditor::_member_edited() {
}
}
void VisualScriptEditor::_script_class_icon_btn_gui_input(Ref<InputEvent> p_event) {
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid() && !mb->is_pressed()) {
int index = mb->get_button_index();
if (index == BUTTON_LEFT) {
_script_class_icon_path_dialog->popup_centered_ratio();
} else if (index == BUTTON_RIGHT) {
_script_class_icon_btn_set_icon("");
}
}
}
void VisualScriptEditor::_create_function_dialog() {
function_create_dialog->popup_centered();
func_name_box->set_text("");
@ -2464,6 +2476,12 @@ void VisualScriptEditor::set_edited_resource(const RES &p_res) {
script->set_edited(true); //so that if a function was added it's saved
}
_script_class_name_edit->set_text(script->get_script_class_name());
String icon_path = script->get_script_class_icon_path();
Control *gui_base = EditorNode::get_singleton()->get_gui_base();
RES icon = FileAccess::exists(icon_path) ? ResourceLoader::load(icon_path, "Texture") : (RES)gui_base->get_icon("Nil", "EditorIcons");
_script_class_icon_btn->set_icon(icon);
_update_graph();
call_deferred("_update_members");
}
@ -4664,6 +4682,42 @@ void VisualScriptEditor::add_syntax_highlighter(SyntaxHighlighter *p_highlighter
void VisualScriptEditor::set_syntax_highlighter(SyntaxHighlighter *p_highlighter) {
}
void VisualScriptEditor::_script_class_icon_path_dialog_confirmed() {
const String &path = _script_class_icon_path_dialog->get_current_path();
_script_class_icon_btn_set_icon(path);
}
void VisualScriptEditor::_script_class_icon_path_dialog_file_selected(const String &p_path) {
_script_class_icon_btn_set_icon(p_path);
}
void VisualScriptEditor::_script_class_icon_btn_set_icon(const String &p_path) {
ERR_FAIL_COND(!_script_class_icon_btn);
ERR_FAIL_COND_MSG(p_path.length() && !FileAccess::exists(p_path), "The given script class icon path does not exist.");
RES icon = FileAccess::exists(p_path) ? ResourceLoader::load(p_path, "Texture") : (RES)EditorNode::get_singleton()->get_gui_base()->get_icon("Nil", "EditorIcons");
undo_redo->create_action("Set script class icon");
undo_redo->add_do_method(_script_class_icon_btn, "set_button_icon", icon);
undo_redo->add_undo_method(_script_class_icon_btn, "set_button_icon", _script_class_icon_btn->get_icon());
if (script.is_valid()) {
undo_redo->add_do_method(script.operator->(), "set_script_class_icon_path", p_path);
undo_redo->add_undo_method(script.operator->(), "set_script_class_icon_path", script->get_script_class_icon_path());
}
undo_redo->commit_action();
set_edited(true);
}
void VisualScriptEditor::_script_class_name_text_changed(const String &p_text) {
if (script.is_valid())
script->set_script_class_name(p_text);
set_edited(true);
}
void VisualScriptEditor::_script_class_icon_path_text_changed(const String &p_text) {
if (script.is_valid())
script->set_script_class_icon_path(p_text);
set_edited(true);
}
void VisualScriptEditor::_bind_methods() {
ClassDB::bind_method("_member_button", &VisualScriptEditor::_member_button);
ClassDB::bind_method("_member_edited", &VisualScriptEditor::_member_edited);
@ -4737,6 +4791,12 @@ void VisualScriptEditor::_bind_methods() {
ClassDB::bind_method("_draw_color_over_button", &VisualScriptEditor::_draw_color_over_button);
ClassDB::bind_method("_generic_search", &VisualScriptEditor::_generic_search);
ClassDB::bind_method("_script_class_icon_path_dialog_confirmed", &VisualScriptEditor::_script_class_icon_path_dialog_confirmed);
ClassDB::bind_method("_script_class_icon_path_dialog_file_selected", &VisualScriptEditor::_script_class_icon_path_dialog_file_selected);
ClassDB::bind_method("_script_class_name_text_changed", &VisualScriptEditor::_script_class_name_text_changed);
ClassDB::bind_method("_script_class_icon_path_text_changed", &VisualScriptEditor::_script_class_icon_path_text_changed);
ClassDB::bind_method("_script_class_icon_btn_gui_input", &VisualScriptEditor::_script_class_icon_btn_gui_input);
}
VisualScriptEditor::VisualScriptEditor() {
@ -4772,6 +4832,37 @@ VisualScriptEditor::VisualScriptEditor() {
members_section->add_child(tool_script_check);
tool_script_check->connect("pressed", this, "_toggle_tool_script");
HBoxContainer *hb = memnew(HBoxContainer);
hb->set_h_size_flags(SIZE_EXPAND_FILL);
Label *lbl = memnew(Label);
lbl->set_text(TTR("Class Name:"));
hb->add_child(lbl);
LineEdit *le = memnew(LineEdit);
le->set_tooltip(TTR("Script Class names must be valid identifiers. Alphanumeric characters and underscores not starting with a number."));
le->connect("text_changed", this, "_script_class_name_text_changed");
le->set_h_size_flags(SIZE_EXPAND_FILL);
hb->add_child(le);
_script_class_name_edit = le;
_script_class_icon_btn = memnew(Button);
_script_class_icon_btn->set_tooltip(TTR("Choose a 16x16 icon for the class. Right-click to clear."));
FileDialog *fd = memnew(FileDialog);
fd->set_mode(FileDialog::MODE_OPEN_FILE);
List<String> texture_type_exts;
ResourceLoader::get_recognized_extensions_for_type("Texture", &texture_type_exts);
for (List<String>::Element *E = texture_type_exts.front(); E; E = E->next()) {
fd->add_filter(vformat("*.%s", E->get()));
}
fd->set_enable_multiple_selection(false);
fd->set_show_hidden_files(false);
fd->set_title("Choose an image.");
fd->connect("confirmed", this, "_script_class_icon_path_dialog_confirmed");
fd->connect("file_selected", this, "_script_class_icon_path_dialog_file_selected");
_script_class_icon_btn->add_child(fd);
_script_class_icon_path_dialog = fd;
_script_class_icon_btn->connect("gui_input", this, "_script_class_icon_btn_gui_input");
hb->add_child(_script_class_icon_btn);
members_section->add_child(hb);
/// Members ///
members = memnew(Tree);

View file

@ -34,6 +34,7 @@
#include "editor/create_dialog.h"
#include "editor/plugins/script_editor_plugin.h"
#include "editor/property_editor.h"
#include "scene/gui/file_dialog.h"
#include "scene/gui/graph_edit.h"
#include "visual_script.h"
#include "visual_script_property_selector.h"
@ -172,6 +173,10 @@ class VisualScriptEditor : public ScriptEditorBase {
Vector2 port_action_pos;
int port_action_new_node;
LineEdit *_script_class_name_edit;
Button *_script_class_icon_btn;
FileDialog *_script_class_icon_path_dialog;
bool saved_pos_dirty;
Vector2 saved_position;
@ -202,6 +207,7 @@ class VisualScriptEditor : public ScriptEditorBase {
void _toggle_tool_script();
void _member_selected();
void _member_edited();
void _script_class_icon_btn_gui_input(Ref<InputEvent> p_event);
void _begin_node_move();
void _end_node_move();
@ -288,6 +294,12 @@ class VisualScriptEditor : public ScriptEditorBase {
void _member_rmb_selected(const Vector2 &p_pos);
void _member_option(int p_option);
void _script_class_icon_path_dialog_confirmed();
void _script_class_icon_path_dialog_file_selected(const String &p_path);
void _script_class_icon_btn_set_icon(const String &p_path);
void _script_class_name_text_changed(const String &p_text);
void _script_class_icon_path_text_changed(const String &p_text);
protected:
void _notification(int p_what);
static void _bind_methods();

View file

@ -2151,6 +2151,118 @@ VisualScriptMathConstant::VisualScriptMathConstant() {
constant = MATH_CONSTANT_ONE;
}
//////////////////////////////////////////
////////////////SCRIPTCLASS///////////////
//////////////////////////////////////////
int VisualScriptScriptClass::get_output_sequence_port_count() const {
return 0;
}
bool VisualScriptScriptClass::has_input_sequence_port() const {
return false;
}
int VisualScriptScriptClass::get_input_value_port_count() const {
return 0;
}
int VisualScriptScriptClass::get_output_value_port_count() const {
return 1;
}
String VisualScriptScriptClass::get_output_sequence_port_text(int p_port) const {
return String();
}
PropertyInfo VisualScriptScriptClass::get_input_value_port_info(int p_idx) const {
return PropertyInfo();
}
PropertyInfo VisualScriptScriptClass::get_output_value_port_info(int p_idx) const {
if (ScriptServer::is_global_class(script_class)) {
Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(script_class), "Script");
return PropertyInfo(Variant::OBJECT, script_class, PROPERTY_HINT_RESOURCE_TYPE, script->get_class(), PROPERTY_USAGE_DEFAULT, script->get_class());
}
return PropertyInfo(Variant::OBJECT, script_class, PROPERTY_HINT_RESOURCE_TYPE, "Script", PROPERTY_USAGE_DEFAULT, "Script");
}
String VisualScriptScriptClass::get_caption() const {
return "Script Class";
}
void VisualScriptScriptClass::set_script_class(const String &p_name) {
ERR_FAIL_COND(!ScriptServer::is_global_class(p_name));
script_class = p_name;
_change_notify();
ports_changed_notify();
}
const String &VisualScriptScriptClass::get_script_class() const {
return script_class;
}
class VisualScriptNodeInstanceScriptClass : public VisualScriptNodeInstance {
public:
Ref<Script> script;
virtual int step(const Variant **p_inputs, Variant **p_outputs, StartMode p_start_mode, Variant *p_working_mem, Variant::CallError &r_error, String &r_error_str) {
*p_outputs[0] = script;
return OK;
}
};
VisualScriptNodeInstance *VisualScriptScriptClass::instance(VisualScriptInstance *p_instance) {
VisualScriptNodeInstanceScriptClass *instance = memnew(VisualScriptNodeInstanceScriptClass);
instance->script = script_class != StringName() ? ResourceLoader::load(ScriptServer::get_global_class_path(script_class), "Script") : RES();
return instance;
}
bool VisualScriptScriptClass::_set(const StringName &p_name, const Variant &p_value) {
if (p_name == "script_class") {
set_script_class(p_value);
return true;
}
return false;
}
bool VisualScriptScriptClass::_get(const StringName &p_name, Variant &r_ret) const {
if (p_name == "script_class") {
r_ret = get_script_class();
return true;
}
return false;
}
void VisualScriptScriptClass::_get_property_list(List<PropertyInfo> *p_list) const {
String cc;
List<StringName> lst;
ScriptServer::get_global_class_list(&lst);
int i = 0;
for (List<StringName>::Element *E = lst.front(); E; E = E->next()) {
if (i > 0)
cc += ",";
i++;
cc += E->get();
}
p_list->push_back(PropertyInfo(Variant::STRING, "script_class", PROPERTY_HINT_ENUM, cc));
}
void VisualScriptScriptClass::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_script_class", "name"), &VisualScriptScriptClass::set_script_class);
ClassDB::bind_method(D_METHOD("get_script_class"), &VisualScriptScriptClass::get_script_class);
}
VisualScriptScriptClass::VisualScriptScriptClass() {
List<StringName> lst;
ScriptServer::get_global_class_list(&lst);
script_class = "";
if (lst.size()) {
StringName name = lst.front()->get();
script_class = name;
}
}
//////////////////////////////////////////
////////////////ENGINESINGLETON///////////
//////////////////////////////////////////
@ -3830,6 +3942,7 @@ void register_visual_script_nodes() {
VisualScriptLanguage::singleton->add_register_func("constants/class_constant", create_node_generic<VisualScriptClassConstant>);
VisualScriptLanguage::singleton->add_register_func("constants/global_constant", create_node_generic<VisualScriptGlobalConstant>);
VisualScriptLanguage::singleton->add_register_func("constants/basic_type_constant", create_node_generic<VisualScriptBasicTypeConstant>);
VisualScriptLanguage::singleton->add_register_func("constants/script_class", create_node_generic<VisualScriptScriptClass>);
VisualScriptLanguage::singleton->add_register_func("custom/custom_node", create_node_generic<VisualScriptCustomNode>);
VisualScriptLanguage::singleton->add_register_func("custom/sub_call", create_node_generic<VisualScriptSubCall>);

View file

@ -593,6 +593,41 @@ public:
VARIANT_ENUM_CAST(VisualScriptMathConstant::MathConstant)
class VisualScriptScriptClass : public VisualScriptNode {
GDCLASS(VisualScriptScriptClass, VisualScriptNode);
String script_class;
protected:
static void _bind_methods();
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
public:
virtual int get_output_sequence_port_count() const;
virtual bool has_input_sequence_port() const;
virtual String get_output_sequence_port_text(int p_port) const;
virtual int get_input_value_port_count() const;
virtual int get_output_value_port_count() const;
virtual PropertyInfo get_input_value_port_info(int p_idx) const;
virtual PropertyInfo get_output_value_port_info(int p_idx) const;
virtual String get_caption() const;
virtual String get_category() const { return "constants"; }
void set_script_class(const String &p_name);
const String &get_script_class() const;
virtual VisualScriptNodeInstance *instance(VisualScriptInstance *p_instance);
VisualScriptScriptClass();
};
class VisualScriptEngineSingleton : public VisualScriptNode {
GDCLASS(VisualScriptEngineSingleton, VisualScriptNode);