Skip to content

Commit

Permalink
CraterCrashGH-444 Improve pin type safety & validation
Browse files Browse the repository at this point in the history
  • Loading branch information
Naros committed Jul 9, 2024
1 parent f4203d8 commit 87bea46
Show file tree
Hide file tree
Showing 120 changed files with 2,482 additions and 967 deletions.
9 changes: 9 additions & 0 deletions cmake/extension_db.cpp.in
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,15 @@ namespace godot
return ExtensionDB::_singleton->_global_enums[p_name];
}

EnumInfo ExtensionDB::get_global_enum_by_value(const StringName& p_name)
{
for (const KeyValue<StringName, EnumInfo>& E : ExtensionDB::_singleton->_global_enums)
for (const EnumValue& ev : E.value.values)
if (ev.name.match(p_name))
return E.value;
return {};
}

EnumValue ExtensionDB::get_global_enum_value(const StringName& p_name)
{
for (const KeyValue<StringName, EnumInfo>& E : ExtensionDB::_singleton->_global_enums)
Expand Down
1 change: 1 addition & 0 deletions cmake/generate_godot_extension_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ def create_db_header():
print_indent("static PackedStringArray get_global_enum_names();")
print_indent("static PackedStringArray get_global_enum_value_names();")
print_indent("static EnumInfo get_global_enum(const StringName& p_enum_name);")
print_indent("static EnumInfo get_global_enum_by_value(const StringName& p_name);")
print_indent("static EnumValue get_global_enum_value(const StringName& p_enum_value_name);")
print_indent("")
print_indent("static PackedStringArray get_math_constant_names();")
Expand Down
9 changes: 9 additions & 0 deletions src/api/extension_db.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4907,6 +4907,15 @@ namespace godot
return ExtensionDB::_singleton->_global_enums[p_name];
}

EnumInfo ExtensionDB::get_global_enum_by_value(const StringName& p_name)
{
for (const KeyValue<StringName, EnumInfo>& E : ExtensionDB::_singleton->_global_enums)
for (const EnumValue& ev : E.value.values)
if (ev.name.match(p_name))
return E.value;
return {};
}

EnumValue ExtensionDB::get_global_enum_value(const StringName& p_name)
{
for (const KeyValue<StringName, EnumInfo>& E : ExtensionDB::_singleton->_global_enums)
Expand Down
1 change: 1 addition & 0 deletions src/api/extension_db.h
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ namespace godot
static PackedStringArray get_global_enum_names();
static PackedStringArray get_global_enum_value_names();
static EnumInfo get_global_enum(const StringName& p_enum_name);
static EnumInfo get_global_enum_by_value(const StringName& p_name);
static EnumValue get_global_enum_value(const StringName& p_enum_value_name);

static PackedStringArray get_math_constant_names();
Expand Down
87 changes: 87 additions & 0 deletions src/common/property_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,83 @@
//
#include "common/property_utils.h"

#include "godot_cpp/templates/hash_map.hpp"
#include "string_utils.h"

namespace PropertyUtils
{
static HashMap<uint32_t, String> get_property_usage_name_map()
{
HashMap<uint32_t, String> names;
names[PROPERTY_USAGE_NONE] = "None";
names[PROPERTY_USAGE_STORAGE] = "Storage";
names[PROPERTY_USAGE_EDITOR] = "Editor";
names[PROPERTY_USAGE_CLASS_IS_BITFIELD] ="ClassIsBitfield";
names[PROPERTY_USAGE_CLASS_IS_ENUM] = "ClassIsEnum";
names[PROPERTY_USAGE_NIL_IS_VARIANT] = "NilIsVariant";
names[PROPERTY_USAGE_DEFAULT] = "Default";
return names;
}

bool are_equal(const PropertyInfo& p_left, const PropertyInfo& p_right)
{
return p_left.type == p_right.type &&
p_left.hint == p_right.hint &&
p_left.hint_string == p_right.hint_string &&
p_left.usage == p_right.usage &&
p_left.class_name == p_right.class_name;
}

PropertyInfo as(const String& p_name, const PropertyInfo& p_property)
{
PropertyInfo new_property = p_property;
new_property.name = p_name;
return new_property;
}

PropertyInfo make_exec(const String& p_name)
{
return make_typed(p_name, Variant::NIL);
}

PropertyInfo make_variant(const String& p_name)
{
return { Variant::NIL, p_name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT };
}

PropertyInfo make_object(const String& p_name, const String& p_class_name)
{
return { Variant::OBJECT, p_name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT, p_class_name };
}

PropertyInfo make_file(const String& p_name, const String& p_filters)
{
return { Variant::STRING, p_name, PROPERTY_HINT_FILE, p_filters, PROPERTY_USAGE_DEFAULT };
}

PropertyInfo make_typed(const String& p_name, Variant::Type p_type, bool p_variant_on_nil)
{
if (p_variant_on_nil && p_type == Variant::NIL)
return make_variant(p_name);

return { p_type, p_name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT };
}

PropertyInfo make_multiline(const String& p_name)
{
return { Variant::STRING, p_name, PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT };
}

PropertyInfo make_enum_class(const String& p_name, const String& p_class_name)
{
return { Variant::INT, p_name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_CLASS_IS_ENUM, p_class_name };
}

PropertyInfo make_class_enum(const String& p_name, const String& p_class_name, const String& p_enum_name)
{
return make_enum_class(p_name, vformat("%s.%s", p_class_name, p_enum_name));
}

bool is_nil_no_variant(const PropertyInfo& p_property)
{
return is_nil(p_property) && !(p_property.usage & PROPERTY_USAGE_NIL_IS_VARIANT);
Expand All @@ -36,4 +111,16 @@ namespace PropertyUtils

return Variant::get_type_name(p_property.type);
}

String usage_to_string(uint32_t p_usage)
{
static HashMap<uint32_t, String> usage_names = get_property_usage_name_map();

PackedStringArray values;
for (const KeyValue<uint32_t, String>& E : usage_names)
if (E.key & p_usage)
values.push_back(E.value);

return StringUtils::join(", ", values);
}
}
77 changes: 77 additions & 0 deletions src/common/property_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,68 @@ namespace PropertyUtils
{
using namespace godot;

/// Checks whether two property info structures are identical (Excluding name).
/// @param p_left a property
/// @param p_right a property
/// @return true if the two are identical, false otherwise
bool are_equal(const PropertyInfo& p_left, const PropertyInfo& p_right);

/// Constructs a new property info with a new name from an existing property
/// @param p_name the new property name
/// @param p_property the property to source data from
/// @return the newly constructed property with the new name
PropertyInfo as(const String& p_name, const PropertyInfo& p_property);

/// Create a simple exec property
/// @param p_name the execution pin name
/// @return the execution property
PropertyInfo make_exec(const String& p_name);

/// Make a variant-based property
/// @param p_name the property pin name
/// @return the newly constructed property with the specified type
PropertyInfo make_variant(const String& p_name);

/// Make an object-based property for a given class type
/// @param p_name the property pin name
/// @param p_class_name the class name, if unspecified the pin accepts any Object
/// @return the newly constructed property with the specified class type
PropertyInfo make_object(const String& p_name, const String& p_class_name = "Object");

/// Make a file property
/// @param p_name the property pin name
/// @param p_filters the file filters, defaults to none
/// @return the newly constructed property
PropertyInfo make_file(const String& p_name, const String& p_filters = String());

/// Makes a simple typed property.
///
/// @note This should not be used to make complex types such as objects, enums, or bitfields, nor
/// to construct properties that represent various hinted types such as files or multilined text.
/// @param p_name the property pin name
/// @param p_type the basic type
/// @param p_variant_on_nil whether the property should be a variant if the type is NIL
/// @return the newly constructed property with the specified type
PropertyInfo make_typed(const String& p_name, Variant::Type p_type, bool p_variant_on_nil = false);

/// Make a multiline text property.
/// @param p_name the property pin name
/// @return the newly constructed property
PropertyInfo make_multiline(const String& p_name);

/// Creates a property info for a global enum type (class has enum name)
/// @param p_name the property name
/// @param p_class_name the global enum class name
/// @return the property info structure
PropertyInfo make_enum_class(const String& p_name, const String& p_class_name);

/// Creates a property info for a class-specific enumeration type
/// @param p_name the property name
/// @param p_class_name the class that owns the enum
/// @param p_enum_name the name of the enumeration
/// @return the property info structure
PropertyInfo make_class_enum(const String& p_name, const String& p_class_name, const String& p_enum_name);

/// Checks whether the property type is <code>NIL</code>
/// @param p_property the property to check
/// @return true if the property is NIL, false otherwise
Expand All @@ -48,6 +110,16 @@ namespace PropertyUtils
/// @return true if the property is a bitfield, false otherwise
_FORCE_INLINE_ bool is_bitfield(const PropertyInfo& p_property) { return p_property.hint == PROPERTY_HINT_FLAGS || p_property.usage & PROPERTY_USAGE_CLASS_IS_BITFIELD; }

/// Checks whether the property is a class enum.
/// @param p_property the property to check
/// @return true if the property usage has <code>PROPERTY_USAGE_CLASS_IS_ENUM</code>
_FORCE_INLINE_ bool is_class_enum(const PropertyInfo& p_property) { return p_property.usage & PROPERTY_USAGE_CLASS_IS_ENUM; }

/// Checks whether the property is a class bitfield.
/// @param p_property the property to check
/// @return true if the property usage has <code>PROPERTY_USAGE_CLASS_IS_BITFIELD</code>
_FORCE_INLINE_ bool is_class_bitfield(const PropertyInfo& p_property) { return p_property.usage & PROPERTY_USAGE_CLASS_IS_BITFIELD; }

/// Checks whether the property type is <code>NIL</code> but the variant flag is not set.
/// @param p_property the property to check
/// @return true if the property is <code>NIL</code> but has no variant flag set
Expand All @@ -57,6 +129,11 @@ namespace PropertyUtils
/// @param p_property the property
/// @return the property type name
String get_property_type_name(const PropertyInfo& p_property);

/// Converts a property info's <code>usage</code> bitfield to a string.
/// @param p_usage the property usage flags bitfield value
/// @return comma-separated string of property usage flags
String usage_to_string(uint32_t p_usage);
}

#endif // ORCHESTRATOR_PROPERTY_UTILS_H
14 changes: 14 additions & 0 deletions src/editor/editor_panel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,19 @@ void OrchestratorEditorPanel::_add_script_function(Object* p_object, const Strin
_files_context.get_selected()->viewport->add_script_function(p_object, p_function_name, p_args);
}

void OrchestratorEditorPanel::_focus_viewport(OrchestratorEditorViewport* p_viewport)
{
for (int i = 0; i < _files_context.open_files.size(); i++)
{
if (_files_context.open_files[i].viewport == p_viewport)
{
_files_context.show(_files_context.open_files[i].file_name);
_update_file_list();
break;
}
}
}

#if GODOT_VERSION >= 0x040300
void OrchestratorEditorPanel::_goto_script_line(const Ref<Script>& p_script, int p_line)
{
Expand Down Expand Up @@ -796,6 +809,7 @@ void OrchestratorEditorPanel::edit_script(const Ref<OScript>& p_script)
}

OrchestratorScriptEditorViewport* viewport = memnew(OrchestratorScriptEditorViewport(p_script));
viewport->connect("focus_requested", callable_mp(this, &OrchestratorEditorPanel::_focus_viewport).bind(viewport));
_viewport_container->add_child(viewport);

OrchestrationFile file;
Expand Down
1 change: 1 addition & 0 deletions src/editor/editor_panel.h
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ class OrchestratorEditorPanel : public PanelContainer
void _file_moved(const String& p_old_file_name, const String& p_new_file_name);
void _folder_removed(const String& p_folder_name);
void _add_script_function(Object* p_object, const String& p_function_name, const PackedStringArray& p_args);
void _focus_viewport(OrchestratorEditorViewport* p_viewport);

#if GODOT_VERSION >= 0x040300
void _goto_script_line(const Ref<Script>& p_script, int p_line);
Expand Down
41 changes: 37 additions & 4 deletions src/editor/editor_viewport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "editor/editor_viewport.h"

#include "common/scene_utils.h"
#include "common/string_utils.h"
#include "editor/graph/graph_edit.h"
#include "orchestration/orchestration.h"
#include "plugins/orchestrator_editor_debugger_plugin.h"
Expand Down Expand Up @@ -188,7 +189,10 @@ void OrchestratorEditorViewport::_meta_clicked(const Variant& p_meta)

const Dictionary value = JSON::parse_string(String(p_meta));
if (value.has("goto_node"))
{
emit_signal("focus_requested");
goto_node(String(value["goto_node"]).to_int());
}
}

void OrchestratorEditorViewport::apply_changes()
Expand Down Expand Up @@ -251,16 +255,44 @@ bool OrchestratorEditorViewport::build(bool p_show_success)
BuildLog log;
_orchestration->validate_and_build(log);

const bool has_errors = log.has_errors() || log.has_warnings();
const Vector<BuildLog::Failure> failures = log.get_failures();

_build_errors->clear();
_build_errors->append_text(vformat("[b]File:[/b] %s\n\n", _resource->get_path()));

if (has_errors)
if (!failures.is_empty())
{
_build_errors_dialog->set_title("Orchestration Build Errors");
for (const String& E : log.get_messages())
_build_errors->append_text(vformat("* %s\n", E));
for (const BuildLog::Failure& failure : failures)
{
String preamble;
switch (failure.type)
{
case BuildLog::FailureType::FT_Warning:
preamble = "[color=yellow]WARNING[/color]";
break;
default:
preamble = "[color=#a95853]ERROR[/color]";
break;
}

String message = failure.message;
if (failure.pin.is_valid())
{
String pin_name = StringUtils::default_if_empty(failure.pin->get_label(), failure.pin->get_pin_name().capitalize());
if (!pin_name.is_empty())
message = vformat("Pin '%s' : %s", pin_name, message);
}

_build_errors->append_text(
vformat("* [b]%s[/b] : Node #[url={\"goto_node\":\"%d\",\"script\":\"%s\"}]%d - %s[/url]\n\t%s\n\n",
preamble,
failure.node->get_id(),
failure.node->get_orchestration()->get_self()->get_path(),
failure.node->get_id(),
failure.node->get_node_title(),
message));
}

_build_errors_dialog->popup_centered_ratio(0.5);
return false;
Expand Down Expand Up @@ -373,6 +405,7 @@ void OrchestratorEditorViewport::_notification(int p_what)

void OrchestratorEditorViewport::_bind_methods()
{
ADD_SIGNAL(MethodInfo("focus_requested"));
}

OrchestratorEditorViewport::OrchestratorEditorViewport(const Ref<Resource>& p_resource) : _resource(p_resource)
Expand Down
9 changes: 8 additions & 1 deletion src/editor/graph/actions/default_action_registrar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -477,10 +477,17 @@ void OrchestratorDefaultGraphActionRegistrar::_register_class_methods(const Orch
const StringName& p_class_name)
{
TypedArray<Dictionary> methods;
StringName class_name;
if (p_context.filter->has_target_object())
{
methods = p_context.filter->target_object->get_target_method_list();
class_name = p_context.filter->target_object->get_class();
}
else
{
methods = ClassDB::class_get_method_list(p_class_name, true);
class_name = p_class_name;
}

for (int i = 0; i < methods.size(); i++)
{
Expand All @@ -501,7 +508,7 @@ void OrchestratorDefaultGraphActionRegistrar::_register_class_methods(const Orch
if (OScriptNodeEvent::is_event_method(mi))
handler_ptr = memnew(OrchestratorGraphNodeSpawnerEvent(mi));
else
handler_ptr = memnew(OrchestratorGraphNodeSpawnerCallMemberFunction(mi));
handler_ptr = memnew(OrchestratorGraphNodeSpawnerCallMemberFunction(mi, class_name));

Ref<OrchestratorGraphActionHandler> handler(handler_ptr);
p_context.list->push_back(memnew(OrchestratorGraphActionMenuItem(spec, handler)));
Expand Down
Loading

0 comments on commit 87bea46

Please sign in to comment.