diff --git a/core/error/SCsub b/core/error/SCsub index dfd6248a941c..eb95be1f6184 100644 --- a/core/error/SCsub +++ b/core/error/SCsub @@ -4,4 +4,7 @@ Import("env") env_error = env.Clone() +if env["tests"]: + env_error.Append(CPPDEFINES=["TESTS_ENABLED"]) + env_error.add_source_files(env.core_sources, "*.cpp") diff --git a/core/error/error_macros.cpp b/core/error/error_macros.cpp index 8376c0aaf84c..e0331f06e430 100644 --- a/core/error/error_macros.cpp +++ b/core/error/error_macros.cpp @@ -83,6 +83,29 @@ void _err_print_error(const char *p_function, const char *p_file, int p_line, co // Main error printing function. void _err_print_error(const char *p_function, const char *p_file, int p_line, const char *p_error, const char *p_message, bool p_editor_notify, ErrorHandlerType p_type) { +#ifndef TESTS_ENABLED + _global_lock(); + // Eliminate duplicate errors to stop locking up the editor due to spam. + static int prev_line = INT_MAX; + static uint32_t prev_frame = UINT32_MAX; + static CharString prev_function; + static CharString prev_file; + static CharString prev_error; + + // Only de-duplicate on the current frame. + uint32_t curr_frame = Engine::get_singleton()->get_process_frames(); + if ((prev_frame == curr_frame) && (prev_line == p_line) && (prev_function == p_function) && (prev_file == p_file) && (prev_error == p_error)) { + _global_unlock(); + return; + } + prev_function = p_function; + prev_file = p_file; + prev_line = p_line; + prev_error = p_error; + prev_frame = curr_frame; + _global_unlock(); +#endif + if (OS::get_singleton()) { OS::get_singleton()->print_error(p_function, p_file, p_line, p_error, p_message, p_editor_notify, (Logger::ErrorType)p_type); } else { diff --git a/core/error/error_macros.h b/core/error/error_macros.h index c8182975d57b..44d36ec9507f 100644 --- a/core/error/error_macros.h +++ b/core/error/error_macros.h @@ -100,6 +100,10 @@ void _err_flush_stdout(); * Always try to return processable data, so the engine can keep running well. * Use the _MSG versions to print a meaningful message to help with debugging. * + * NOTE: Error and warning messages are deduplicated on a per-frame basis, to prevent + * spam. If you wish to output multiple similar errors / warnings, be sure to differentiate them. + * See _err_print_error(). + * * The `((void)0)` no-op statement is used as a trick to force us to put a semicolon after * those macros, making them look like proper statements. * The if wrappers are used to ensure that the macro replacement does not trigger unexpected diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 60e2d539f88d..b2da3edd29ae 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -162,6 +162,16 @@ bool CharString::operator<(const CharString &p_right) const { return is_str_less(get_data(), p_right.get_data()); } +bool CharString::operator==(const char *p_right) const { + // nullptr and null terminator translate to the same CharString, length 0. + if (p_right == nullptr || p_right[0] == 0) { + return length() == 0; + } else if (length() == 0) { + return false; + } + return strcmp(ptr(), p_right) == 0; +} + bool CharString::operator==(const CharString &p_right) const { if (length() == 0) { // True if both have length 0, false if only p_right has a length diff --git a/core/string/ustring.h b/core/string/ustring.h index 897b06fc6dc6..1bb40eacd974 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -157,6 +157,7 @@ class CharString { void operator=(const char *p_cstr); bool operator<(const CharString &p_right) const; bool operator==(const CharString &p_right) const; + bool operator==(const char *p_right) const; CharString &operator+=(char p_char); int length() const { return size() ? size() - 1 : 0; } const char *get_data() const;