New diamond recursion defects (O(2^D) → O(N)): - godot-0009: Font::_is_cyclic no visited set — CJK fallback diamond, 2648x at F=4,D=8 - godot-0010: Font::_update_rids_fb no visited set — duplicate RIDs + O(N^2) hot path - meson-0002: get_internal_static_libraries_recurse link_whole guard missing — 132x at D=10 - typescript-0003: hasBaseType inner check() no visited set — 1024x at D=10; hot on instanceof New O(N²) defects: - typeorm-0004: SubjectTopologicalSorter Array.indexOf dedup — 200x at N=400 - typeorm-0005: DepGraph.createDFS result.indexOf + addDependency edge dedup — 300x at N=600 CLEAN confirmed (diamond recursion sweep): bazel, cargo, cmake, composer, dgl, diesel, doctrine-orm, efcore, helm, mybatis, networkx-deeper, ninja, npm-arborist, peewee, pip, rubygems, seaorm, sqlalchemy, swift UNDF: 571→578 assigned; MOAD count: 629→635
99 lines
3.5 KiB
Diff
99 lines
3.5 KiB
Diff
# UNDF: UNDF-2026-000000405
|
|
--- a/scene/resources/font.h
|
|
+++ b/scene/resources/font.h
|
|
@@ -95,7 +95,8 @@ class Font : public Resource {
|
|
|
|
static void _bind_methods();
|
|
|
|
- virtual void _update_rids_fb(const Font *p_f, int p_depth) const;
|
|
+ // FIX godot-0010: pass visited set to avoid O(N^2) re-traversal of shared fallback fonts
|
|
+ virtual void _update_rids_fb(const Font *p_f, int p_depth, HashSet<const Font *> *r_visited = nullptr) const;
|
|
virtual void _update_rids() const;
|
|
virtual void reset_state() override;
|
|
--- a/scene/resources/font.cpp
|
|
+++ b/scene/resources/font.cpp
|
|
@@ -103,14 +103,22 @@ void Font::_update_rids_fb(const Font *p_f, int p_depth) const {
|
|
-void Font::_update_rids_fb(const Font *p_f, int p_depth) const {
|
|
- ERR_FAIL_COND(p_depth > MAX_FALLBACK_DEPTH);
|
|
- if (p_f != nullptr) {
|
|
- RID rid = p_f->_get_rid();
|
|
- if (rid.is_valid()) {
|
|
- rids.push_back(rid);
|
|
- }
|
|
- const TypedArray<Font> &_fallbacks = p_f->get_fallbacks();
|
|
- for (int i = 0; i < _fallbacks.size(); i++) {
|
|
- Ref<Font> fb_font = _fallbacks[i];
|
|
- _update_rids_fb(fb_font.ptr(), p_depth + 1);
|
|
- }
|
|
- }
|
|
-}
|
|
+// FIX godot-0010: was O(N^2) — shared fonts in diamond fallback topology were
|
|
+// re-traversed once per path, bloating rids[] with duplicates and wasting render time.
|
|
+// Now O(N) with HashSet visited guard; rids[] contains each font exactly once.
|
|
+void Font::_update_rids_fb(const Font *p_f, int p_depth, HashSet<const Font *> *r_visited) const {
|
|
+ ERR_FAIL_COND(p_depth > MAX_FALLBACK_DEPTH);
|
|
+ if (p_f == nullptr) {
|
|
+ return;
|
|
+ }
|
|
+ if (r_visited != nullptr && r_visited->has(p_f)) {
|
|
+ return; // already collected this font's RID; skip to avoid duplicates
|
|
+ }
|
|
+ if (r_visited != nullptr) {
|
|
+ r_visited->insert(p_f);
|
|
+ }
|
|
+ RID rid = p_f->_get_rid();
|
|
+ if (rid.is_valid()) {
|
|
+ rids.push_back(rid);
|
|
+ }
|
|
+ const TypedArray<Font> &_fallbacks = p_f->get_fallbacks();
|
|
+ for (int i = 0; i < _fallbacks.size(); i++) {
|
|
+ Ref<Font> fb_font = _fallbacks[i];
|
|
+ _update_rids_fb(fb_font.ptr(), p_depth + 1, r_visited);
|
|
+ }
|
|
+}
|
|
|
|
void Font::_update_rids() const {
|
|
rids.clear();
|
|
- _update_rids_fb(this, 0);
|
|
+ HashSet<const Font *> visited;
|
|
+ _update_rids_fb(this, 0, &visited); // FIX godot-0010: O(N) visited guard
|
|
dirty_rids = false;
|
|
}
|
|
|
|
+// FontVariation::_update_rids — also needs the visited guard
|
|
+void FontVariation::_update_rids() const {
|
|
+ Ref<Font> f = _get_base_font_or_default();
|
|
+ rids.clear();
|
|
+ HashSet<const Font *> visited;
|
|
+ if (fallbacks.is_empty() && f.is_valid()) {
|
|
+ RID rid = _get_rid();
|
|
+ if (rid.is_valid()) { rids.push_back(rid); }
|
|
+ const TypedArray<Font> &base_fallbacks = f->get_fallbacks();
|
|
+ for (int i = 0; i < base_fallbacks.size(); i++) {
|
|
+ Ref<Font> fb_font = base_fallbacks[i];
|
|
+ _update_rids_fb(fb_font.ptr(), 0, &visited); // FIX godot-0010
|
|
+ }
|
|
+ } else {
|
|
+ _update_rids_fb(this, 0, &visited); // FIX godot-0010
|
|
+ }
|
|
+ dirty_rids = false;
|
|
+}
|
|
+
|
|
+// SystemFont::_update_rids — also needs the visited guard
|
|
+void SystemFont::_update_rids() const {
|
|
+ Ref<Font> f = _get_base_font_or_default();
|
|
+ rids.clear();
|
|
+ HashSet<const Font *> visited;
|
|
+ if (fallbacks.is_empty() && f.is_valid()) {
|
|
+ RID rid = _get_rid();
|
|
+ if (rid.is_valid()) { rids.push_back(rid); }
|
|
+ const TypedArray<Font> &base_fallbacks = f->get_fallbacks();
|
|
+ for (int i = 0; i < base_fallbacks.size(); i++) {
|
|
+ Ref<Font> fb_font = base_fallbacks[i];
|
|
+ _update_rids_fb(fb_font.ptr(), 0, &visited); // FIX godot-0010
|
|
+ }
|
|
+ } else {
|
|
+ _update_rids_fb(this, 0, &visited); // FIX godot-0010
|
|
+ }
|
|
+ dirty_rids = false;
|
|
+}
|