From fd4e2e1af0a884966081f62fcaad6eb91b48eeaa Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Sat, 8 Jun 2024 17:34:17 -0400 Subject: [PATCH] GH-384 Support script documentation --- .../plugins/orchestrator_editor_plugin.cpp | 4 +- src/orchestration/orchestration.cpp | 18 +++ src/orchestration/orchestration.h | 18 +++ src/script/function.cpp | 24 ++++ src/script/function.h | 9 ++ src/script/language.cpp | 4 +- .../nodes/functions/function_terminator.cpp | 20 ++++ src/script/script.cpp | 13 ++- src/script/script.h | 4 + src/script/script_docdata.cpp | 106 ++++++++++++++++++ src/script/script_docdata.h | 71 ++++++++++++ src/script/signals.cpp | 22 +++- src/script/signals.h | 9 ++ 13 files changed, 314 insertions(+), 8 deletions(-) create mode 100644 src/script/script_docdata.cpp create mode 100644 src/script/script_docdata.h diff --git a/src/editor/plugins/orchestrator_editor_plugin.cpp b/src/editor/plugins/orchestrator_editor_plugin.cpp index 28dd271c..0c82a22c 100644 --- a/src/editor/plugins/orchestrator_editor_plugin.cpp +++ b/src/editor/plugins/orchestrator_editor_plugin.cpp @@ -61,8 +61,8 @@ void OrchestratorPlugin::_notification(int p_what) // Register the plugin's icon for CreateScript Dialog Ref theme = ThemeDB::get_singleton()->get_default_theme(); - if (theme.is_valid() && !theme->has_icon(_get_plugin_name(), "EditorIcons")) - theme->set_icon(_get_plugin_name(), "EditorIcons", _get_plugin_icon()); + if (theme.is_valid() && !theme->has_icon(OScript::get_class_static(), "EditorIcons")) + theme->set_icon(OScript::get_class_static(), "EditorIcons", _get_plugin_icon()); _window_wrapper = memnew(OrchestratorWindowWrapper); _window_wrapper->set_window_title(vformat("Orchestrator - Godot Engine")); diff --git a/src/orchestration/orchestration.cpp b/src/orchestration/orchestration.cpp index 67faf9e7..1b4a3139 100644 --- a/src/orchestration/orchestration.cpp +++ b/src/orchestration/orchestration.cpp @@ -276,6 +276,24 @@ void Orchestration::validate_and_build(BuildLog& p_log) } } +void Orchestration::set_brief_description(const String& p_description) +{ + if (!_brief_description.match(p_description)) + { + _brief_description = p_description; + _self->emit_changed(); + } +} + +void Orchestration::set_description(const String& p_description) +{ + if (!_description.match(p_description)) + { + _description = p_description; + _self->emit_changed(); + } +} + void Orchestration::add_node(const Ref& p_graph, const Ref& p_node) { ERR_FAIL_COND_MSG(_has_instances(), "Cannot add node, instances exist."); diff --git a/src/orchestration/orchestration.h b/src/orchestration/orchestration.h index 0b09a155..45428ba2 100644 --- a/src/orchestration/orchestration.h +++ b/src/orchestration/orchestration.h @@ -63,6 +63,8 @@ class Orchestration HashMap> _signals; //! Map of all user-defined signals HashMap> _graphs; //! Map of all defined graphs Resource* _self; //! Reference to the outer resource type + String _brief_description; //! The brief description + String _description; //! The description //~ Begin Serialization Interface TypedArray _get_nodes_internal() const; @@ -158,6 +160,22 @@ class Orchestration /// @param p_log the build log virtual void validate_and_build(BuildLog& p_log); + /// Get the brief description for the orchestration + /// @return the brief description + virtual String get_brief_description() const { return _brief_description; } + + /// Set the brief description + /// @param p_description the brief description + virtual void set_brief_description(const String& p_description); + + /// Get the description for the orchestration + /// @return the description + virtual String get_description() const { return _description; } + + /// Set the description for the orchestration + /// @param p_description the description + virtual void set_description(const String& p_description); + //~ Begin Node Interface void add_node(const Ref& p_graph, const Ref& p_node); void remove_node(int p_node_id); diff --git a/src/script/function.cpp b/src/script/function.cpp index d928533e..e319afeb 100644 --- a/src/script/function.cpp +++ b/src/script/function.cpp @@ -27,6 +27,7 @@ void OScriptFunction::_get_property_list(List *r_list) const r_list->push_back(PropertyInfo(Variant::DICTIONARY, "method")); r_list->push_back(PropertyInfo(Variant::BOOL, "user_defined")); r_list->push_back(PropertyInfo(Variant::INT, "id")); + r_list->push_back(PropertyInfo(Variant::STRING, "description", PROPERTY_HINT_MULTILINE_TEXT)); } bool OScriptFunction::_get(const StringName &p_name, Variant &r_value) @@ -51,6 +52,11 @@ bool OScriptFunction::_get(const StringName &p_name, Variant &r_value) r_value = _user_defined; return true; } + else if (p_name.match("description")) + { + r_value = _description; + return true; + } return false; } @@ -78,6 +84,15 @@ bool OScriptFunction::_set(const StringName &p_name, const Variant &p_value) _user_defined = p_value; result = true; } + else if (p_name.match("description")) + { + _description = p_value; + + if (_orchestration) + _orchestration->get_self()->emit_changed(); + + result = true; + } if (result) emit_changed(); @@ -261,3 +276,12 @@ void OScriptFunction::set_has_return_value(bool p_has_return_value) emit_changed(); } } + +void OScriptFunction::set_description(const String& p_description) +{ + if (_description != p_description) + { + _description = p_description; + emit_changed(); + } +} diff --git a/src/script/function.h b/src/script/function.h index 133da361..ae8daf2c 100644 --- a/src/script/function.h +++ b/src/script/function.h @@ -53,6 +53,7 @@ class OScriptFunction : public Resource bool _user_defined{ false }; //! Whether function is user-defined int _owning_node_id{ -1 }; //! Owning node id bool _returns_value{ false }; //! Whether the function returns a value + String _description; //! Function description protected: //~ Begin Wrapped Interface @@ -170,6 +171,14 @@ class OScriptFunction : public Resource /// Sets whether the function has a return value /// @param p_has_return_value value true if the function has a return value, false otherwise void set_has_return_value(bool p_has_return_value); + + /// Get the description + /// @return the description + String get_description() const { return _description; } + + /// Sets the description + /// @param p_description the description + void set_description(const String& p_description); }; #endif // ORCHESTRATOR_SCRIPT_FUNCTION_H \ No newline at end of file diff --git a/src/script/language.cpp b/src/script/language.cpp index a3b0e012..a721e63d 100644 --- a/src/script/language.cpp +++ b/src/script/language.cpp @@ -79,7 +79,7 @@ String OScriptLanguage::_get_name() const String OScriptLanguage::_get_type() const { - return TYPE; + return OScript::get_class_static(); } String OScriptLanguage::_get_extension() const @@ -104,7 +104,7 @@ bool OScriptLanguage::_supports_builtin_mode() const bool OScriptLanguage::_supports_documentation() const { - return false; + return true; } bool OScriptLanguage::_is_using_templates() diff --git a/src/script/nodes/functions/function_terminator.cpp b/src/script/nodes/functions/function_terminator.cpp index 06451bb1..06ef399a 100644 --- a/src/script/nodes/functions/function_terminator.cpp +++ b/src/script/nodes/functions/function_terminator.cpp @@ -33,6 +33,8 @@ void OScriptNodeFunctionTerminator::_get_property_list(List* r_lis r_list->push_back(PropertyInfo(Variant::STRING, "function_id", PROPERTY_HINT_NONE, "", read_only_serialize)); r_list->push_back(PropertyInfo(Variant::STRING, "function_name", PROPERTY_HINT_NONE, "", read_only_editor)); + r_list->push_back(PropertyInfo(Variant::STRING, "description", PROPERTY_HINT_MULTILINE_TEXT)); + if (function.is_valid()) { if (_supports_return_values()) @@ -113,6 +115,15 @@ bool OScriptNodeFunctionTerminator::_get(const StringName& p_name, Variant& r_va return true; } } + else if (p_name.match("description")) + { + Ref function = get_function(); + if (function.is_valid()) + { + r_value = function->get_description(); + return true; + } + } return false; } @@ -176,6 +187,15 @@ bool OScriptNodeFunctionTerminator::_set(const StringName& p_name, const Variant return true; } } + else if (p_name.match("description")) + { + Ref function = get_function(); + if (function.is_valid()) + { + function->set_description(p_value); + return true; + } + } return false; } diff --git a/src/script/script.cpp b/src/script/script.cpp index 084c833e..35279afe 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -20,6 +20,7 @@ #include "script/instances/script_instance.h" #include "script/instances/script_instance_placeholder.h" #include "script/nodes/script_nodes.h" +#include "script_docdata.h" #include #include @@ -74,6 +75,14 @@ void OScript::_bind_methods() ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "graphs", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "_set_graphs", "_get_graphs"); + ClassDB::bind_method(D_METHOD("_set_brief_description", "description"), &OScript::_set_brief_description); + ClassDB::bind_method(D_METHOD("_get_brief_description"), &OScript::_get_brief_description); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "brief_description", PROPERTY_HINT_MULTILINE_TEXT), "_set_brief_description", "_get_brief_description"); + + ClassDB::bind_method(D_METHOD("_set_description", "description"), &OScript::_set_description); + ClassDB::bind_method(D_METHOD("_get_description"), &OScript::_get_description); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "description", PROPERTY_HINT_MULTILINE_TEXT), "_set_description", "_get_description"); + ADD_SIGNAL(MethodInfo("connections_changed", PropertyInfo(Variant::STRING, "caller"))); ADD_SIGNAL(MethodInfo("functions_changed")); ADD_SIGNAL(MethodInfo("variables_changed")); @@ -209,9 +218,7 @@ Error OScript::_reload(bool p_keep_state) TypedArray OScript::_get_documentation() const { - // todo: see how to generate it from the script/node contents - // see doc_data & script_language_extension - return {}; + return OScriptDocData::create_documentation(Ref(this)); } bool OScript::_has_static_method(const StringName& p_method) const diff --git a/src/script/script.h b/src/script/script.h index 9a8d2c4a..afcc4e5f 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -71,6 +71,10 @@ class OScript : public ScriptExtension, public Orchestration void _set_variables(const TypedArray& p_variables) { _set_variables_internal(p_variables); } TypedArray _get_signals() const { return _get_signals_internal(); } void _set_signals(const TypedArray& p_signals) { _set_signals_internal(p_signals); } + void _set_brief_description(const String& p_description) { set_brief_description(p_description); } + String _get_brief_description() const { return get_brief_description(); } + void _set_description(const String& p_description) { set_description(p_description); } + String _get_description() const { return get_description(); } //~ End Serialization API /// Update export placeholders diff --git a/src/script/script_docdata.cpp b/src/script/script_docdata.cpp new file mode 100644 index 00000000..c26740a6 --- /dev/null +++ b/src/script/script_docdata.cpp @@ -0,0 +1,106 @@ +// This file is part of the Godot Orchestrator project. +// +// Copyright (c) 2023-present Vahera Studios LLC and its contributors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "script/script_docdata.h" + +Dictionary OScriptDocData::_create_property_documentation(const PropertyInfo& p_property) +{ + Dictionary property; + property["name"] = p_property.name; + property["type"] = p_property.type == Variant::NIL ? "Variant" : Variant::get_type_name(p_property.type); + return property; +} + +TypedArray OScriptDocData::_get_method_arguments_documentation(const std::vector& p_properties) +{ + TypedArray data; + for (const PropertyInfo& property : p_properties) + data.push_back(_create_property_documentation(property)); + + return data; +} + +String OScriptDocData::_get_method_return_type(const MethodInfo& p_method) +{ + if (p_method.return_val.type == Variant::NIL) + return p_method.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT ? "Variant" : "void"; + + return Variant::get_type_name(p_method.return_val.type); +} + +Dictionary OScriptDocData::_method_info_documentation(const MethodInfo& p_method, const String& p_description) +{ + Dictionary data; + data["name"] = p_method.name; + data["description"] = p_description; + data["return_type"] = _get_method_return_type(p_method); + data["arguments"] = _get_method_arguments_documentation(p_method.arguments); + return data; +} + +TypedArray OScriptDocData::_create_properties_documentation(const Ref& p_script) +{ + TypedArray data; + for (const Ref& variable : p_script->get_variables()) + { + Dictionary property_data = _create_property_documentation(variable->get_info()); + property_data["description"] = variable->get_description(); + + data.push_back(property_data); + } + return data; +} + +TypedArray OScriptDocData::_create_signals_documentation(const Ref& p_script) +{ + TypedArray data; + for (const Ref& signal : p_script->get_custom_signals()) + { + const MethodInfo& mi = signal->get_method_info(); + data.push_back(_method_info_documentation(mi, signal->get("description"))); + } + return data; +} + +TypedArray OScriptDocData::_create_functions_documentation(const Ref& p_script) +{ + TypedArray data; + for (const Ref& function : p_script->get_functions()) + { + const MethodInfo& mi = function->get_method_info(); + data.push_back(_method_info_documentation(mi, function->get("description"))); + } + return data; +} + +TypedArray OScriptDocData::create_documentation(const Ref& p_script) +{ + Dictionary data; + data["name"] = vformat("\"%s\"", p_script->get_path().replace("res://", "")); + data["inherits"] = p_script->get_base_type(); + data["brief_description"] = p_script->get_brief_description(); + data["description"] = p_script->get_description(); + data["methods"] = _create_functions_documentation(p_script); + data["signals"] = _create_signals_documentation(p_script); + data["properties"] = _create_properties_documentation(p_script); + data["is_deprecated"] = false; + data["is_experimental"] = false; + data["is_script_doc"] = true; + data["script_path"] = p_script->get_path(); + + // We currently only support 1 class per Orchestration + return Array::make(data); +} diff --git a/src/script/script_docdata.h b/src/script/script_docdata.h new file mode 100644 index 00000000..25031e87 --- /dev/null +++ b/src/script/script_docdata.h @@ -0,0 +1,71 @@ +// This file is part of the Godot Orchestrator project. +// +// Copyright (c) 2023-present Vahera Studios LLC and its contributors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef ORCHESTRATOR_SCRIPT_DOC_DATA_H +#define ORCHESTRATOR_SCRIPT_DOC_DATA_H + +#include "script/script.h" + +/// Helper class that creates documentation for an Orchestration script +class OScriptDocData +{ + /// Get the property documentation + /// @param p_property the property + /// @return the property documentation + static Dictionary _create_property_documentation(const PropertyInfo& p_property); + + /// Get the method argument documentation + /// @param p_properties the properties + /// @return the arguments documentation + static TypedArray _get_method_arguments_documentation(const std::vector& p_properties); + + /// Get the method return type + /// @param p_method the method information + /// @return the return type + static String _get_method_return_type(const MethodInfo& p_method); + + /// Creates documentation for a given MethodInfo. + /// @param p_method the method information + /// @param p_description the custom method description + /// @return the method documentation + static Dictionary _method_info_documentation(const MethodInfo& p_method, const String& p_description); + + /// Creates documentation for the script's properties + /// @param p_script the script to create documentation about + /// @return the properties documentation + static TypedArray _create_properties_documentation(const Ref& p_script); + + /// Creates documentation for the script's signals + /// @param p_script the script to create documentation about + /// @return the signals documentation + static TypedArray _create_signals_documentation(const Ref& p_script); + + /// Creates documentation for the script's functions + /// @param p_script the script to create documentation about + /// @return the function documentation + static TypedArray _create_functions_documentation(const Ref& p_script); + + /// Intentially private + OScriptDocData() = default; + +public: + /// Creates the documentation + /// @param p_script the script to create documentation about + /// @return typed array of documentation data + static TypedArray create_documentation(const Ref& p_script); +}; + +#endif // ORCHESTRATOR_SCRIPT_DOC_DATA_H \ No newline at end of file diff --git a/src/script/signals.cpp b/src/script/signals.cpp index 56faebff..d725bcfd 100644 --- a/src/script/signals.cpp +++ b/src/script/signals.cpp @@ -30,6 +30,7 @@ void OScriptSignal::_get_property_list(List* r_list) const // Editor-only properties int32_t read_only = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY; r_list->push_back(PropertyInfo(Variant::STRING, "signal_name", PROPERTY_HINT_NONE, "", read_only)); + r_list->push_back(PropertyInfo(Variant::STRING, "description", PROPERTY_HINT_MULTILINE_TEXT)); r_list->push_back(PropertyInfo(Variant::INT, "argument_count", PROPERTY_HINT_RANGE, "0,32", PROPERTY_USAGE_EDITOR)); for (size_t i = 1; i <= _method.arguments.size(); i++) { @@ -74,6 +75,11 @@ bool OScriptSignal::_get(const StringName &p_name, Variant &r_value) return true; } } + else if (p_name.match("description")) + { + r_value = _description; + return true; + } return false; } @@ -114,6 +120,11 @@ bool OScriptSignal::_set(const StringName &p_name, const Variant &p_value) return true; } } + else if (p_name.match("description")) + { + _description = p_value; + return true; + } return false; } @@ -189,4 +200,13 @@ void OScriptSignal::set_argument_name(size_t p_index, const StringName& p_name) _method.arguments[p_index].name = p_name; emit_changed(); } -} \ No newline at end of file +} + +void OScriptSignal::set_description(const String& p_description) +{ + if (_description != p_description) + { + _description = p_description; + emit_changed(); + } +} diff --git a/src/script/signals.h b/src/script/signals.h index 6bc08349..8906708c 100644 --- a/src/script/signals.h +++ b/src/script/signals.h @@ -40,6 +40,7 @@ class OScriptSignal : public Resource Orchestration* _orchestration{ nullptr }; //! Owning Orchestration MethodInfo _method; //! The signal definition + String _description; //! Signal description protected: //~ Begin Wrapped Interface @@ -96,6 +97,14 @@ class OScriptSignal : public Resource /// @param p_index the argument list index to change /// @param p_type the new argument type void set_argument_type(size_t p_index, Variant::Type p_type); + + /// Get the description + /// @return the description + String get_description() const { return _description; } + + /// Sets the description + /// @param p_description the description + void set_description(const String& p_description); }; #endif // ORCHESTRATOR_SCRIPT_SIGNALS_H