Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[3.x] Allow exporting custom resources from/to any scripting language (GDScript, VisualScript, C#, NativeScript, PluginScript) #44879

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions core/bind/core_bind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,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
Expand Down Expand Up @@ -3005,6 +3006,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);
}
Expand Down
27 changes: 27 additions & 0 deletions core/bind/core_bind.h
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,33 @@ class _ClassDB : public Object {
~_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);

Expand Down
8 changes: 7 additions & 1 deletion core/io/resource_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}

Expand Down
6 changes: 6 additions & 0 deletions core/register_core_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,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"

Expand All @@ -87,6 +88,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;

Expand Down Expand Up @@ -223,6 +225,7 @@ void register_core_types() {
_classdb = memnew(_ClassDB);
_marshalls = memnew(_Marshalls);
_json = memnew(_JSON);
_script_server = memnew(_ScriptServer);
}

void register_core_settings() {
Expand Down Expand Up @@ -252,6 +255,7 @@ void register_core_singletons() {
ClassDB::register_class<_JSON>();
ClassDB::register_class<Expression>();
ClassDB::register_class<Time>();
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()));
Expand All @@ -267,6 +271,7 @@ void register_core_singletons() {
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("Time", Time::get_singleton()));
Engine::get_singleton()->add_singleton(Engine::Singleton("ScriptServer", _ScriptServer::get_singleton()));
}

void unregister_core_types() {
Expand All @@ -277,6 +282,7 @@ void unregister_core_types() {
memdelete(_classdb);
memdelete(_marshalls);
memdelete(_json);
memdelete(_script_server);

memdelete(_geometry);

Expand Down
67 changes: 62 additions & 5 deletions core/script_language.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
Expand Down
12 changes: 9 additions & 3 deletions core/script_language.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "core/resource.h"

class ScriptLanguage;
class Script;

typedef void (*ScriptEditRequestFunction)(const String &p_path);

Expand All @@ -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;
Expand All @@ -81,9 +83,12 @@ class ScriptServer {
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();

Expand Down Expand Up @@ -283,6 +288,7 @@ class ScriptLanguage {
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; }

Expand Down
3 changes: 3 additions & 0 deletions doc/classes/@GlobalScope.xml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@
<member name="Time" type="Time" setter="" getter="">
The [Time] singleton.
</member>
<member name="ScriptServer" type="ScriptServer" setter="" getter="">
The [ScriptServer] singleton.
</member>
<member name="TranslationServer" type="TranslationServer" setter="" getter="">
The [TranslationServer] singleton.
</member>
Expand Down
18 changes: 18 additions & 0 deletions doc/classes/EditorInterface.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,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>
Expand Down Expand Up @@ -88,6 +97,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>
Expand Down
Loading