Across 18,642 functions in bevyengine/bevy, 293 are in the critical band — and every single one of the top five hotspots sits in the ‘fire’ quadrant, meaning high structural complexity combined with active commit churn right now. The top-ranked function by activity-weighted risk, load_gltf, carries a cyclomatic complexity of 69 and a fan-out of 72 — the highest in this dataset — giving it a risk score of 20.29. I’d start there and with derive_as_bind_group before any rendering sprint this week — these aren’t cleanup items deferred to a backlog. They are live regression risks on code that is changing today.
The table below ranks functions by activity-weighted risk — a score that multiplies structural complexity by recent commit frequency. A function that is both hard to understand (high cyclomatic complexity) and actively changing is a higher priority than one that is complex but untouched. CC = cyclomatic complexity (independent execution paths); ND = max nesting depth; FO = fan-out (distinct callees).
Top 5 Hotspots
| Function | File | Risk | CC | ND | FO |
|---|---|---|---|---|---|
load_gltf | crates/bevy_gltf/src/loader/mod.rs | 20.3 | 69 | 7 | 72 |
derive_as_bind_group | crates/bevy_render/macros/src/as_bind_group.rs | 19.4 | 83 | 7 | 21 |
assign_objects_to_clusters | crates/bevy_light/src/cluster/assign.rs | 19.4 | 70 | 8 | 46 |
main | tools/example-showcase/src/main.rs | 17.9 | 52 | 5 | 33 |
prepare_lights | crates/bevy_pbr/src/render/light.rs | 16.9 | 57 | 4 | 46 |
Large Repo Analysis
bevy is a large repository. To stay within memory constraints, this analysis used hybrid touch mode: structural complexity — CC, ND, FO — is measured precisely for every function. Git activity is tracked at the function level (via git log -L) only for files with 5 or more commits in the last 30 days; other files use a file-level approximation. Rankings therefore surface functions that are both structurally complex and in the most actively-changing parts of the codebase. Dormant code with high structural complexity will rank lower than it would under a full per-function analysis — to surface it, run hotspots analyze . --per-function-touches on a machine with sufficient memory.
Quadrant overview
The quadrant picture for bevy is unusually clean in one direction and alarming in another. There are no ‘debt’ or ‘ok’ functions in the dataset — everything is either actively moving or low-complexity and being watched.
18,642 functions analyzed
That means 1,438 functions combine meaningful structural complexity with recent commit activity. The five I’m focusing on are the ones where the structural complexity is high enough — cyclomatic complexity in the 52–83 range — that any one of those commits represents real regression risk.
High cyclomatic complexity — many independent execution paths, each a potential bug surface and required test case.God Function×5God Function
Calls an unusually large number of distinct functions (high fan-out), making it the structural centre of gravity for a subsystem.Long Function×5Long Function
Function body is too long to review in a single pass; likely contains multiple distinct responsibilities.Deeply Nested×4Deeply Nested
Control structures nested 4+ levels deep, making it hard to reason about the full execution state at inner branches.Exit Heavy×4Exit Heavy
Multiple return or throw paths dispersed through the body — each exit needs separate test coverage.
Every top hotspot carries the god_function and long_function patterns simultaneously. That combination — broad coupling through high fan-out plus sheer length — is what makes these hard to test in isolation and hard to review in a pull request. Four of the five also exhibit deeply nested control flow, and four carry multiple exit paths that add to the test-coverage burden.
load_gltf — mod.rs
load_gltf is the top activity-weighted risk function in the repository at 20.29. It is the async entry point for parsing an entire glTF file — buffers, textures, animations, scenes, nodes, skins — and translating that into bevy’s asset graph. The source excerpt reveals its structure: it first validates or skips validation based on settings, dispatches to registered extensions via on_root, resolves the file path, loads buffers asynchronously, computes linear texture sets, and then — conditionally compiled behind #[cfg(feature = "bevy_animation")] — walks every scene’s node tree to collect animation paths, followed by a second conditional block that processes animation channels, samplers, and keyframe data using a deeply nested reader pattern.
A fan-out of 72 is the highest in this dataset and the sharpest signal here. This function directly calls 72 distinct functions, meaning a change to any one of those callees — a buffer loader, a coordinate converter, an animation curve type — can produce a behavioral change inside load_gltf that only manifests for a specific combination of glTF features. The nesting depth of 7 compounds this: the animation channel processing visible in the excerpt nests iterator chains inside conditional feature blocks inside match arms.
The file was touched once in the last 30 days, five days ago, and its sole recent commit was a bug fix. With 72 call sites and 69 independent paths, even a targeted fix has a wide blast radius.
Recommendation: The most tractable first cut is to extract animation loading — everything inside the #[cfg(feature = "bevy_animation")] blocks — into a dedicated load_animations function. That single extraction removes a large conditional subtree and meaningfully reduces the path count. After that, buffer loading, texture setup, and scene graph traversal are each natural boundaries for further extraction. The goal is for load_gltf to read as an orchestration function that delegates to named sub-loaders rather than implementing all of them inline.
derive_as_bind_group — as_bind_group.rs
This is a procedural macro that derives the AsBindGroup trait — the mechanism by which materials declare their GPU resource bindings at compile time. Its job is to parse struct-level and field-level attributes and emit the token stream that wires textures, samplers, uniform buffers, and bindless resource tables into the render pipeline. Based on the source excerpt, it does all of that in a single function body: reads manifest paths, accumulates binding state vectors for both bindless and non-bindless layouts, processes a first attribute pass to detect #[bindless] and its nested modifiers (limit, index_table, range, binding), then continues into per-field binding logic.
A cyclomatic complexity of 83 means there are 83 independent paths through this macro. Each path corresponds to a different combination of attribute configurations a downstream user might write, and each is a required test case for correctness. The nesting depth of 7 — visible in the excerpt where parse_nested_meta calls contain inner parse_nested_meta calls — means a reviewer tracing any one path must hold seven levels of context simultaneously. The fan-out of 21 is moderate compared to some functions below, but in a procedural macro context it represents 21 distinct syn/quote API calls whose argument order and type expectations must all be correct for the emitted code to compile.
Two commits have touched this file in the last 30 days, the most recent four days ago. The file’s historical commit mix shows about half of its commits have been bug-fix tagged, which is consistent with the complexity: when a macro this branchy gets a new attribute variant, adjacent paths regress.
Recommendation: Extract each top-level attribute variant (#[bindless], #[bind_group_data], field-level binding types) into its own parsing function that returns a typed intermediate representation. The main derive function should then orchestrate those parsed results into code generation rather than interleaving parsing and emission. That decomposition alone would reduce CC by routing the 83 paths into smaller, independently testable units — and would make adding the next bindless modifier a matter of touching one self-contained parser rather than extending the monolith.
assign_objects_to_clusters — assign.rs
This function clusters point lights, spot lights, light probes, and decals into the tile/cluster grid used for forward+ rendering. Its signature alone — visible in the excerpt — is a declaration of scope: it accepts five separate ECS queries (views, point lights, spot lights, light probes, decals), three Local state values for persisted scratch buffers, and a global cluster settings resource. The doc comment even includes a scheduling constraint: it must run before update_point_light_frusta.
The nesting depth of 8 is the highest in this dataset and a strong refactoring signal on its own. Eight levels of nested control structures means the innermost logic — the actual cluster assignment math — is surrounded by eight layers of guard conditions, iterator adapters, and type-dispatch branches that a reader must parse before reaching the computation. The cyclomatic complexity of 70 reflects the combinatorial product of light types, visibility states, render layer membership, and GPU clustering mode. Fan-out of 46 means this function reaches broadly into the rendering infrastructure.
One commit touched this file in the last 30 days, five days ago, tagged entirely as a bug fix. Single-author ownership over that period means there is limited shared context on this function’s invariants across the team.
Recommendation: The GPU/CPU clustering branch (if global_cluster_settings.gpu_clustering.is_none()) is the most natural seam: extract CPU-path collection into a collect_clusterable_objects_cpu helper and GPU-path collection into its counterpart. Within each, the per-light-type extend calls are further extraction candidates. Reducing the nesting depth from 8 to 4–5 is the primary goal — each level removed makes the scheduling constraint and the render-layer visibility logic independently reviewable.
main — main.rs
The main function of the example-showcase tool is the CLI entry point that orchestrates running bevy examples for screenshot capture and CI validation. From the excerpt, it parses arguments, validates mutual constraints between --page and --per-page, then dispatches into a large match cli.action block. The Action::Run arm alone destructures eleven fields and contains further nested match logic over combinations of stop_frame, screenshot_frame, and auto_stop_frame — each arm writing a different .ron configuration file and assembling --features flags before launching examples.
A cyclomatic complexity of 52 for a CLI main function is high. CLI argument parsing typically keeps CC low by delegating to subcommand handlers; here, dispatch, validation, file I/O, and example-launch logic are all co-located. The exit-heavy pattern — four out of five top functions share it — is particularly notable here: the excerpt shows multiple .exit() calls on error paths, meaning test coverage requires exercising each argument validation branch through a real process boundary or significant mocking.
This is a tooling file rather than a runtime crate, so its blast radius on bevy users is lower. That said, a CI system that depends on this tool for screenshot validation will feel any regression immediately.
Recommendation: Extract each Action variant’s logic into a dedicated handler function — run_examples(...), generate_showcase(...), and so on. The main function should resolve to argument parsing plus a dispatch table of at most one level of match depth. This is the standard extract-method refactoring for CLI entry points, and it would bring CC below 10 while making each action independently testable without invoking the full binary.
prepare_lights — light.rs
prepare_lights is the render-world system that takes extracted light data and writes GPU buffers: it allocates shadow map textures, classifies point and directional lights, enforces per-view limits (with Local<bool> warning flags for when maximums are exceeded), manages RetainedViewEntity sets for live shadow mapping, and feeds the results into the clusterable object metadata and light meta resources. The signature spans three grouped parameter tuples and seven queries — a layout that reflects how much state this system must coordinate in a single pass.
This function has the highest recent activity of any hotspot here: three commits in the last 30 days, the most recent two days ago, from three distinct authors in the last 90 days. That combination — CC 57, fan-out 46, three recent committers — is the definition of live regression risk. When multiple authors are independently modifying a function with 57 execution paths, the probability that any one change invalidates an assumption held by another is non-trivial. Two of its three recent commits are tagged as bug fixes, which reinforces that pattern.
The nesting depth of 4 is the lowest among the top five, which actually makes this the most tractable starting point for incremental decomposition: the branching is wide rather than deep, so the paths are enumerable without first unpacking layers of nesting.
Recommendation: Given three active authors, I’d prioritize this one for an explicit decomposition PR before any further feature work. Extract shadow texture allocation into allocate_shadow_textures, directional light limit enforcement into enforce_directional_light_limits, and the per-view light classification loop into classify_lights_for_view. Each extracted function reduces the shared mutable state surface that concurrent contributors need to reason about and gives the three active authors clearer ownership boundaries going forward.
Patterns Found
Antipatterns detected across the top functions in this snapshot:
| Pattern | Occurrences |
|---|---|
complex_branching | 5 |
god_function | 5 |
long_function | 5 |
deeply_nested | 4 |
exit_heavy | 4 |
These labels belong to two tiers — Tier 1 (structural): complex_branching, deeply_nested, exit_heavy, long_function, god_function. Tier 2 (relational/temporal): hub_function, cyclic_hub, middle_man, neighbor_risk, stale_complex, churn_magnet, shotgun_target, volatile_god.
See more analyses with these patterns: complex_branching, deeply_nested, exit_heavy, god_function, long_function.
Reproduce This Analysis
git clone https://github.com/bevyengine/bevy
cd bevy
git checkout 9f4ff89c1a6aa49efe0ade126ed67c948121a30b
hotspots analyze . --mode snapshot --explain-patterns --force --hybrid-touches 5
To run the same analysis on your own codebase, run hotspots analyze . --mode snapshot in any local git repo — no configuration required.
I use Hotspots to highlight structural and activity risk — not “bad code.” I treat these findings as a prioritization aid, not a bug predictor. Editorial policy →