feat: lua script inheritance & feat: is_abstract#225
Conversation
|
@gilzoide hello! I'm in the making of this PR, seems almost done, I'll try finishing it this week. If you have free time feel free to check it and give feedback. I've tested it and all in-editor stuff seems to work (signal inheritance, node selection, method/property inheritance, exported property inheritance) Btw I've short-sightedly branched it from my main instead of upstream, so it also shows changes from the other PR, sorry for the inconvenience |
|
@gilzoide hello! I've finished this PR, added an inheritance.lua test, tested all the in-editor functionality, everything seems to work fine Things that can be improved:
I think this is ready to merge as is (unless I missed smth), but if you feel that something else is required pls let me know |
| } | ||
|
|
||
| StringName LuaScript::_get_instance_base_script_type() const { | ||
| return _get_base_script() != nullptr ? metadata.base_class : ""; |
There was a problem hiding this comment.
This doesn't look right. Shouldn't you return something from _get_base_script() in case _get_base_script() != nullptr?
There was a problem hiding this comment.
This doesn't look right. Shouldn't you return something from
_get_base_script()in case_get_base_script() != nullptr?
I thought leaving empty string as a false indicating that there's no base_script makes more sense since the method name specifies script_type. I can change it if you think returning base_type when script doesn't extend another script makes more sense
| if (script.is_valid() && script->_is_valid()) { | ||
| result["name"] = script->_get_global_name(); | ||
| result["base_type"] = script->_get_instance_base_type(); | ||
| result["base_type"] = script->_get_instance_base_script_type().is_empty() ? script->get_instance_base_type() : script->_get_instance_base_script_type(); |
There was a problem hiding this comment.
Why not stick to script->_get_instance_base_type() here, since the implementation already handles base scripts correctly?
There was a problem hiding this comment.
Why not stick to
script->_get_instance_base_type()here, since the implementation already handles base scripts correctly?
I don't think I understand the question. If I get base_type here in script_class_cache all the scripts would inherit from that type, not base script. Did you mean stick to _get_instance_base_script_type? If so then yes, if I don't return "" this would get much cleaner, I should do it that way
| if (extends_script == nullptr) { | ||
| WARN_PRINT(String("Specified base class '%s' does not exist, using RefCounted") % Array::make(extends)); | ||
| } else { | ||
| base_class = extends; |
There was a problem hiding this comment.
Why not get the extends_script->get_metadata().base_class here instead and let LuaScript::_get_instance_base_type just return it? So that base_class always has the name of a valid native GDCLASS.
Remember that in this branch extends is the result of extends_script->to_string().
There was a problem hiding this comment.
Didn't want to deal with cross inclusions at that moment and then forgot about it, so yeah, I should fix it
| Ref<Script> get_class_script(const String& class_name) { | ||
| Array global_classes = ProjectSettings::get_singleton()->get_global_class_list(); | ||
| for (int i = 0; i < global_classes.size(); i++) { | ||
| Dictionary cls = global_classes[i]; | ||
| if (String(cls["class"]) == class_name) { | ||
| return ResourceLoader::get_singleton()->load(cls["path"], "Script"); | ||
| } | ||
| } | ||
|
|
||
| return nullptr; | ||
| } |
There was a problem hiding this comment.
This implementation only accepts global classes declared with class_name. I think this feature is unfinished without the support for loading scripts by path. You should at least support paths relative to res:// and uid://, although supporting relative paths from the current script would be a nice touch, such as is supported by GDScript.
There was a problem hiding this comment.
This implementation only accepts global classes declared with
class_name. I think this feature is unfinished without the support for loading scripts by path. You should at least support paths relative tores://anduid://, although supporting relative paths from the current script would be a nice touch, such as is supported by GDScript.
I don't really understand this. If I'm trying to get class's script how can it return a script which isn't declared with class_name? Or are you talking about some code which uses this function?
There was a problem hiding this comment.
I just googled and understood what you were talking about, didn't know that you can extend scripts by their path. I agree that this should be implemented, I'll think about how I can do it
There was a problem hiding this comment.
I think the inheritance test should be more comprehensive. You should at least test that overriding methods in the derived class supersedes methods from the base class. Also test that get_script_property_list, get_script_method_list and the like return members from both Base and Derived classes.
Co-authored-by: Gil Reis <gilzoide@gmail.com>
Co-authored-by: Gil Reis <gilzoide@gmail.com>
Co-authored-by: Gil Reis <gilzoide@gmail.com>
|
@gilzoide hello! I have fixed everything from the feedback (apart from this, didn't understand the comment). Most notably:
I haven't moved get_property from LuaScript for reasons explained in the reply, but if you still think it's not fit there pls explain how should it be realized |
|
also for some reason with luajit accessing godot Array after retrieving a callable results in a crash with no error. Have no idea why this happens, but only happens on this branch and with luajit. that's why CI test is failing. I'll take a look at why this might happen, LuaScript::get_property is my suspect, but can't be sure, extremely weird case local node = Control:new()
-- Comment any of the 2 lines or reverse order = no crash, but this exact setup = crash
local methods = node.queue_free
local a = #Array({})
-- terminate called without an active exception
-- make: *** [Makefile:51: test] Aborted (core dumped) |
gilzoide
left a comment
There was a problem hiding this comment.
Hey @illarn, thanks a lot for taking the time to update the code. Sorry it took me so long to review again, these last weeks have been busy here and I didn't want to rush this. There are still things that should be addressed, I've added comments in the relevant sections.
Again, thanks a lot for taking the time to implement this feature! Please let me know if there's any help I can further provide. I'll try to debug the crash with LuaJIT you mentioned, maybe I can find something that could help.
| WARN_PRINT(String("Specified base class '%s' does not exist, using RefCounted") % Array::make(extends)); | ||
| } else { | ||
| base_class = extends; | ||
| base_class = static_cast<Ref<LuaScript>>(extends_script)->get_metadata().base_class; |
There was a problem hiding this comment.
Wouldn't this crash in case extends_script is a Script but not a LuaScript?
If base scripts for Lua should always be a LuaScript and not just a plain Script (such as an instance of GDScript), make explicit checks and add error messages if necessary.
There was a problem hiding this comment.
you're right, I can just get_instance_base_type() on base script and it will work with any script, fixed
| if (Ref<LuaScript> base = get_base_script(); base != nullptr) | ||
| if (const Variant property = base->_get_property_default_value(p_property)) | ||
| return property; | ||
| return property; |
There was a problem hiding this comment.
You should still call base->_get_property_default_value(p_property) here.
|
@gilzoide Hello! Thank you for the feedback, I've returned |
There was a problem hiding this comment.
Hey @illarn, thanks a lot for the updates, and sorry again for taking so long to review. I think we are really close now!
Apart from the inline comments, another thing I'd like to comment overall is about LuaScriptMetadata::base_script being a Ref<Script>. If pretty much every feature in LuaScript that traverses the base script chain casts the base script to Ref<LuaScript>, shouldn't we force that Lua scripts only extend other Lua scripts? Both GDScript and CSharpScript support only themselves as base and Godot doesn't traverse the script chain in general, it's a responsibility for the script / script instance.
All that said, I'd say we should either:
- Force that Lua scripts extend only Lua scripts (or native classes, of course)
- Add real support for extending scripts of other languages
The simplest is option 1, the official script languages already do this, so I don't think we need to bother trying to support the general case. But if you want to try option 2, it's up to you.
| Ref<Script> current = script; | ||
| while (current.is_valid()) { | ||
| if (current == metadata.base_script) | ||
| return true; | ||
| current = current->get_base_script(); | ||
| } | ||
|
|
||
| return false; |
There was a problem hiding this comment.
This implementation is incorrect, we should iterate over the base script chain for this instead of script's. As a reference, take a look at GDScript::inherits_script.
| const LuaScriptMetadata metadata = p_instance->script->get_metadata(); | ||
|
|
||
| // a) try calling `_get` | ||
| if (const LuaScriptMethod *_get = metadata.methods.getptr(string_names->_get)) { |
There was a problem hiding this comment.
We should use LuaScript::get_method here, in case the _get method is defined in a super class.
Goal
Make it possible to inherit from lua classes (#164) and add abstract lua script property (since now with script inheritance it has effect)
Changes
base_script(reference to the script being extended) in the LuaScriptMetadata