-
-
Notifications
You must be signed in to change notification settings - Fork 21.8k
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
Optimize classnames enumeration #101489
Optimize classnames enumeration #101489
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -269,10 +269,10 @@ void ScriptServer::init_languages() { | |||||||||||
|
||||||||||||
for (const Variant &script_class : script_classes) { | ||||||||||||
Dictionary c = script_class; | ||||||||||||
if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base")) { | ||||||||||||
if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base") || !c.has("is_abstract") || !c.has("is_tool")) { | ||||||||||||
continue; | ||||||||||||
} | ||||||||||||
add_global_class(c["class"], c["base"], c["language"], c["path"]); | ||||||||||||
add_global_class(c["class"], c["base"], c["language"], c["path"], c["is_abstract"], c["is_tool"]); | ||||||||||||
} | ||||||||||||
ProjectSettings::get_singleton()->clear("_global_script_classes"); | ||||||||||||
} | ||||||||||||
|
@@ -281,10 +281,10 @@ void ScriptServer::init_languages() { | |||||||||||
Array script_classes = ProjectSettings::get_singleton()->get_global_class_list(); | ||||||||||||
for (const Variant &script_class : script_classes) { | ||||||||||||
Dictionary c = script_class; | ||||||||||||
if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base")) { | ||||||||||||
if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base") || !c.has("is_abstract") || !c.has("is_tool")) { | ||||||||||||
continue; | ||||||||||||
} | ||||||||||||
add_global_class(c["class"], c["base"], c["language"], c["path"]); | ||||||||||||
add_global_class(c["class"], c["base"], c["language"], c["path"], c["is_abstract"], c["is_tool"]); | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
|
@@ -390,7 +390,7 @@ void ScriptServer::global_classes_clear() { | |||||||||||
inheriters_cache.clear(); | ||||||||||||
} | ||||||||||||
|
||||||||||||
void ScriptServer::add_global_class(const StringName &p_class, const StringName &p_base, const StringName &p_language, const String &p_path) { | ||||||||||||
void ScriptServer::add_global_class(const StringName &p_class, const StringName &p_base, const StringName &p_language, const String &p_path, bool p_is_abstract, bool p_is_tool) { | ||||||||||||
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."); | ||||||||||||
GlobalScriptClass *existing = global_classes.getptr(p_class); | ||||||||||||
if (existing) { | ||||||||||||
|
@@ -399,6 +399,8 @@ void ScriptServer::add_global_class(const StringName &p_class, const StringName | |||||||||||
existing->base = p_base; | ||||||||||||
existing->path = p_path; | ||||||||||||
existing->language = p_language; | ||||||||||||
existing->is_abstract = p_is_abstract; | ||||||||||||
existing->is_tool = p_is_tool; | ||||||||||||
inheriters_cache_dirty = true; | ||||||||||||
} | ||||||||||||
} else { | ||||||||||||
|
@@ -407,6 +409,8 @@ void ScriptServer::add_global_class(const StringName &p_class, const StringName | |||||||||||
g.language = p_language; | ||||||||||||
g.path = p_path; | ||||||||||||
g.base = p_base; | ||||||||||||
g.is_abstract = p_is_abstract; | ||||||||||||
g.is_tool = p_is_tool; | ||||||||||||
global_classes[p_class] = g; | ||||||||||||
inheriters_cache_dirty = true; | ||||||||||||
} | ||||||||||||
|
@@ -480,6 +484,16 @@ StringName ScriptServer::get_global_class_native_base(const String &p_class) { | |||||||||||
return base; | ||||||||||||
} | ||||||||||||
|
||||||||||||
bool ScriptServer::is_global_class_abstract(const String &p_class) { | ||||||||||||
ERR_FAIL_COND_V(!global_classes.has(p_class), false); | ||||||||||||
return global_classes[p_class].is_abstract; | ||||||||||||
Comment on lines
+488
to
+489
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Same below. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unresolved. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I forgot. However, on this new inspection, I realize why I did it like that: for consistency, to match how all the other |
||||||||||||
} | ||||||||||||
|
||||||||||||
bool ScriptServer::is_global_class_tool(const String &p_class) { | ||||||||||||
ERR_FAIL_COND_V(!global_classes.has(p_class), false); | ||||||||||||
return global_classes[p_class].is_tool; | ||||||||||||
} | ||||||||||||
|
||||||||||||
void ScriptServer::get_global_class_list(List<StringName> *r_global_classes) { | ||||||||||||
List<StringName> classes; | ||||||||||||
for (const KeyValue<StringName, GlobalScriptClass> &E : global_classes) { | ||||||||||||
|
@@ -507,12 +521,15 @@ void ScriptServer::save_global_classes() { | |||||||||||
get_global_class_list(&gc); | ||||||||||||
Array gcarr; | ||||||||||||
for (const StringName &E : gc) { | ||||||||||||
const GlobalScriptClass &global_class = global_classes[E]; | ||||||||||||
Dictionary d; | ||||||||||||
d["class"] = E; | ||||||||||||
d["language"] = global_classes[E].language; | ||||||||||||
d["path"] = global_classes[E].path; | ||||||||||||
d["base"] = global_classes[E].base; | ||||||||||||
d["language"] = global_class.language; | ||||||||||||
d["path"] = global_class.path; | ||||||||||||
d["base"] = global_class.base; | ||||||||||||
d["icon"] = class_icons.get(E, ""); | ||||||||||||
d["is_abstract"] = global_class.is_abstract; | ||||||||||||
d["is_tool"] = global_class.is_tool; | ||||||||||||
gcarr.push_back(d); | ||||||||||||
} | ||||||||||||
ProjectSettings::get_singleton()->store_global_class_list(gcarr); | ||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -672,7 +672,7 @@ class ScriptLanguageExtension : public ScriptLanguage { | |
|
||
GDVIRTUAL1RC_REQUIRED(Dictionary, _get_global_class_name, const String &) | ||
|
||
virtual String get_global_class_name(const String &p_path, String *r_base_type = nullptr, String *r_icon_path = nullptr) const override { | ||
virtual String get_global_class_name(const String &p_path, String *r_base_type = nullptr, String *r_icon_path = nullptr, bool *r_is_abstract = nullptr, bool *r_is_tool = nullptr) const override { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm always—inexplicably—fuzzy about this GDExtension compatibility topic, like I used to be with GDNative... So, let's see if I'm getting this straight. This is the only related binding I'm being able to find: Given it only cares about the first parameter, looks like we are lucky that second parameter onwards don't have an impact. Is that right? |
||
Dictionary ret; | ||
GDVIRTUAL_CALL(_get_global_class_name, p_path, ret); | ||
if (!ret.has("name")) { | ||
|
@@ -684,6 +684,12 @@ class ScriptLanguageExtension : public ScriptLanguage { | |
if (r_icon_path != nullptr && ret.has("icon_path")) { | ||
*r_icon_path = ret["icon_path"]; | ||
} | ||
if (r_is_abstract != nullptr && ret.has("is_abstract")) { | ||
*r_is_abstract = ret["is_abstract"]; | ||
} | ||
if (r_is_tool != nullptr && ret.has("is_tool")) { | ||
*r_is_tool = ret["is_tool"]; | ||
} | ||
return ret["name"]; | ||
} | ||
}; | ||
|
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -983,20 +983,6 @@ 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); | ||||
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 script->get_language()->get_global_class_name(base_script->get_path()); | ||||
} | ||||
|
||||
Variant EditorData::script_class_instance(const String &p_class) { | ||||
if (ScriptServer::is_global_class(p_class)) { | ||||
Ref<Script> script = script_class_load_script(p_class); | ||||
|
@@ -1026,22 +1012,25 @@ void EditorData::script_class_set_icon_path(const String &p_class, const String | |||
_script_class_icon_paths[p_class] = p_icon_path; | ||||
} | ||||
|
||||
String EditorData::script_class_get_icon_path(const String &p_class) const { | ||||
if (!ScriptServer::is_global_class(p_class)) { | ||||
return String(); | ||||
} | ||||
|
||||
String EditorData::script_class_get_icon_path(const String &p_class, bool *r_valid) const { | ||||
String current = p_class; | ||||
String ret = _script_class_icon_paths[current]; | ||||
while (ret.is_empty()) { | ||||
current = script_class_get_base(current); | ||||
while (true) { | ||||
if (!ScriptServer::is_global_class(current)) { | ||||
// If the classnames chain has a native class ancestor, we're done with success. | ||||
if (r_valid) { | ||||
*r_valid = ClassDB::class_exists(current); | ||||
} | ||||
return String(); | ||||
} | ||||
ret = _script_class_icon_paths.has(current) ? _script_class_icon_paths[current] : String(); | ||||
HashMap<StringName, String>::ConstIterator E = _script_class_icon_paths.find(current); | ||||
if ((bool)E) { | ||||
if (r_valid) { | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This sets There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How so? If the path is not found, godot/core/templates/hash_map.h Line 542 in 394508d
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||
*r_valid = true; | ||||
} | ||||
return E->value; | ||||
} | ||||
current = ScriptServer::get_global_class_base(current); | ||||
} | ||||
|
||||
return ret; | ||||
} | ||||
|
||||
StringName EditorData::script_class_get_name(const String &p_path) const { | ||||
|
@@ -1126,58 +1115,67 @@ Ref<Texture2D> EditorData::_load_script_icon(const String &p_path) const { | |||
return nullptr; | ||||
} | ||||
|
||||
Ref<Texture2D> EditorData::get_script_icon(const Ref<Script> &p_script) { | ||||
Ref<Texture2D> EditorData::get_script_icon(const String &p_script_path) { | ||||
// Take from the local cache, if available. | ||||
if (_script_icon_cache.has(p_script)) { | ||||
if (_script_icon_cache.has(p_script_path)) { | ||||
// Can be an empty value if we can't resolve any icon for this script. | ||||
// An empty value is still cached to avoid unnecessary attempts at resolving it again. | ||||
return _script_icon_cache[p_script]; | ||||
return _script_icon_cache[p_script_path]; | ||||
} | ||||
|
||||
// Fast path in case the whole hierarchy is made of global classes. | ||||
StringName class_name = script_class_get_name(p_script_path); | ||||
{ | ||||
if (class_name != StringName()) { | ||||
bool icon_valid = false; | ||||
String icon_path = script_class_get_icon_path(class_name, &icon_valid); | ||||
if (icon_valid) { | ||||
Ref<Texture2D> icon = _load_script_icon(icon_path); | ||||
_script_icon_cache[p_script_path] = icon; | ||||
return icon; | ||||
} | ||||
} | ||||
} | ||||
|
||||
Ref<Script> base_scr = p_script; | ||||
Ref<Script> base_scr = ResourceLoader::load(p_script_path, "Script"); | ||||
while (base_scr.is_valid()) { | ||||
// Check for scripted classes. | ||||
String icon_path; | ||||
StringName class_name = script_class_get_name(base_scr->get_path()); | ||||
if (base_scr->is_built_in() || class_name == StringName()) { | ||||
StringName base_class_name = script_class_get_name(base_scr->get_path()); | ||||
if (base_scr->is_built_in() || base_class_name == StringName()) { | ||||
icon_path = base_scr->get_class_icon_path(); | ||||
} else { | ||||
icon_path = script_class_get_icon_path(class_name); | ||||
icon_path = script_class_get_icon_path(base_class_name); | ||||
} | ||||
|
||||
Ref<Texture2D> icon = _load_script_icon(icon_path); | ||||
if (icon.is_valid()) { | ||||
_script_icon_cache[p_script] = icon; | ||||
_script_icon_cache[p_script_path] = icon; | ||||
return icon; | ||||
} | ||||
|
||||
// Check for legacy custom classes defined by plugins. | ||||
// TODO: Should probably be deprecated in 4.x | ||||
const EditorData::CustomType *ctype = get_custom_type_by_path(base_scr->get_path()); | ||||
if (ctype && ctype->icon.is_valid()) { | ||||
_script_icon_cache[p_script] = ctype->icon; | ||||
_script_icon_cache[p_script_path] = ctype->icon; | ||||
return ctype->icon; | ||||
} | ||||
|
||||
// Move to the base class. | ||||
base_scr = base_scr->get_base_script(); | ||||
} | ||||
|
||||
// No custom icon was found in the inheritance chain, so check the base | ||||
// class of the script instead. | ||||
String base_type; | ||||
p_script->get_language()->get_global_class_name(p_script->get_path(), &base_type); | ||||
|
||||
// Check if the base type is an extension-defined type. | ||||
Ref<Texture2D> ext_icon = extension_class_get_icon(base_type); | ||||
Ref<Texture2D> ext_icon = extension_class_get_icon(class_name); | ||||
if (ext_icon.is_valid()) { | ||||
_script_icon_cache[p_script] = ext_icon; | ||||
_script_icon_cache[p_script_path] = ext_icon; | ||||
return ext_icon; | ||||
} | ||||
|
||||
// If no icon found, cache it as null. | ||||
_script_icon_cache[p_script] = Ref<Texture>(); | ||||
return nullptr; | ||||
_script_icon_cache[p_script_path] = Ref<Texture2D>(); | ||||
return Ref<Texture2D>(); | ||||
} | ||||
|
||||
void EditorData::clear_script_icon_cache() { | ||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have a particular suggestion for improvement, but I can't help wonder if there won't be other types of qualifiers we need to keep track on in the future, leading to further boolean parameters. It's probably fine, though the GDExtension compatibility might need some care.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since I've added just two, I've kept them as separate args here and as separate fields in the classnames cache. For future extensions, I'd say moving to some kind of bitset or similar would be advisable. I'll check the GDExtension compat.