Skip to content

perf(scene): split PrefabComponent/TagComponent/AudioSourceComponent into hot/cold halves#578

Merged
drsnuggles8 merged 3 commits into
masterfrom
feature/hotcold-component-split
Jul 7, 2026
Merged

perf(scene): split PrefabComponent/TagComponent/AudioSourceComponent into hot/cold halves#578
drsnuggles8 merged 3 commits into
masterfrom
feature/hotcold-component-split

Conversation

@drsnuggles8

@drsnuggles8 drsnuggles8 commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Summary

Issue #444 — move editor-only / cold data out of hot runtime ECS components to shrink their per-instance memory footprint.

  • PrefabComponent — the three std::unordered_set<std::string> override-tracking sets (overridden/added/removed) move behind a lazily-allocated PrefabOverrideSets (std::unique_ptr, null when a prefab instance has no overrides — the common case). sizeof(PrefabComponent): ~184-208 bytes → 24 bytes.
  • TagComponent — the transient renaming editor-only flag moves out to SceneHierarchyPanel::m_RenamingEntityUUID (panel-local state; only one entity can be mid-rename at a time, so it doesn't need to live on the runtime component).
  • AudioSourceComponent — playback config, event wiring, and the SoundConfig asset handle move behind an always-allocated AudioSourceColdData blob, leaving only Source and ActiveEventID inline (the only fields Scene::UpdateAudio's per-frame position-sync loop touches). sizeof(AudioSourceComponent): ~150+ bytes → 24 bytes.

Both PrefabComponent and AudioSourceComponent are now automatically classified non-trivial by OloHeaderTool's private-member detection (their cold data lives behind a private std::unique_ptr), so they fall out of the generated AllComponents/SceneSerializer codegen paths with no exclusion-set edits needed. PrefabComponent's scene YAML serializer is hand-written to preserve the exact on-disk shape the codegen'd version had (same top-level keys, same sorted-sequence emit) — no scene migration required. All call sites (SceneSerializer, SaveGame serializer, Scene/Prefab/SceneStreamer, C#/Lua script glue, the editor panel, and tests) now go through the new accessor methods (GetConfig(), GetOverriddenComponents(), SetSoundConfigHandle(), etc.).

static_assert(sizeof(...) <= 32) guards PrefabComponent and AudioSourceComponent against regressing back into hot-array bloat.

Closes #444

Test plan

  • GenerateBindings rebuilt — all generated .inl files reported "(unchanged)", confirming the codegen classifier already excludes both split components via private-member auto-detection.
  • Full OloEngine-Tests suite: 4182 passed, 5 skipped (no GL context / launch smoke — expected in this environment), 0 failed.
  • Targeted suites verified green: ComponentTupleCoverageTest, ComponentSerializerCoverageTest, ComponentHandlerCoverageTest, SaveGameComponentSerializerCoverageTest, PrefabOverrideTest, ComponentRoundTrip.*, AudioEventsSceneIntegrationTest, LuaBindingTest.
  • OloEditor builds clean.
  • sizeof() verified via a temporary throwaway test (not committed): PrefabComponent=24, AudioSourceComponent=24 (both previously well over 150 bytes). TagComponent stays 40 in this Debug build because MSVC's debug-iterator std::string overhead already pads to a multiple of 8 regardless of the trailing bool; the real win shows up in Release builds where std::string is 32 bytes (40→32).
  • /code-review high — 8 independent finder angles (line-by-line, removed-behavior audit, cross-file call-site tracer, reuse, simplification, efficiency, altitude, CLAUDE.md conventions) found zero correctness bugs. A few low-severity duplication notes (repeated accessor triples, repeated YAML sort/emit blocks) were surfaced but not acted on — they mirror the project's explicit anti-over-engineering guidance ("three similar lines is better than a premature abstraction") and an existing pattern already present elsewhere in SceneSerializer.cpp.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Scene hierarchy renaming now stays consistently tied to the active entity, reducing incorrect/lingering rename behavior.
    • Audio source configuration (including sound preset links and event-system start behavior) is now handled more uniformly across the editor, scripting, and runtime.
  • Bug Fixes

    • Prefab override apply/revert is more reliable, and prefab override data round-trips through serialization more consistently.
    • Legacy saves load missing sound preset data more safely and preserve expected audio startup behavior.

…into hot/cold halves

Moves editor- and config-only data out of three per-instance ECS components
to shrink their hot-array footprint (issue #444):

- PrefabComponent: the three unordered_set<string> override-tracking sets
  move behind a lazily-allocated PrefabOverrideSets (null when a prefab
  instance has no overrides, the common case). 184-208 bytes -> 24 bytes.
- TagComponent: the transient `renaming` editor flag moves to
  SceneHierarchyPanel::m_RenamingEntityUUID (panel-local rename state,
  since only one entity can be mid-rename at a time).
- AudioSourceComponent: playback config, event wiring, and the SoundConfig
  handle move behind an always-allocated AudioSourceColdData blob, leaving
  only Source and ActiveEventID inline (the fields Scene::UpdateAudio's
  per-frame position-sync loop actually touches). ~150+ bytes -> 24 bytes.

Both PrefabComponent and AudioSourceComponent are now classified
non-trivial by OloHeaderTool's private-member auto-detection (their cold
data lives behind a private std::unique_ptr), so they fall out of the
generated AllComponents/SceneSerializer codegen automatically — no
exclusion-set edits needed. PrefabComponent's scene YAML serializer is
hand-written to keep the on-disk shape identical to the codegen'd version
it replaces (same top-level keys, same sorted-sequence emit), so no scene
migration is required. All call sites (SceneSerializer, SaveGame
serializer, Scene/Prefab/SceneStreamer, C#/Lua script glue, editor panel,
and tests) now go through the new accessor methods.

static_assert(sizeof(...) <= 32) guards PrefabComponent and
AudioSourceComponent against regressing back into hot-array bloat.

Closes #444

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@drsnuggles8, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 35 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ec2b52fe-7293-452c-8db1-fca0464316d7

📥 Commits

Reviewing files that changed from the base of the PR and between 1dd0786 and 633e3d5.

📒 Files selected for processing (2)
  • OloEditor/src/Panels/SceneHierarchyPanel.cpp
  • OloEngine/src/OloEngine/Scene/SceneSerializer.cpp
📝 Walkthrough

Walkthrough

This PR moves PrefabComponent and AudioSourceComponent state behind accessor-backed storage, then updates scene, editor, serializer, scripting, and test code to use the new APIs. It also replaces panel-local rename tracking in the hierarchy UI.

Changes

Component data-shape refactor and call-site migration

Layer / File(s) Summary
PrefabComponent and AudioSourceComponent contracts
OloEngine/src/OloEngine/Scene/Components.h
PrefabComponent override tracking moves to a lazily allocated PrefabOverrideSets; AudioSourceComponent moves editor-visible fields into AudioSourceColdData; equality and size guards are updated.
Prefab instantiation and scene audio paths
OloEngine/src/OloEngine/Scene/Prefab.cpp, OloEngine/src/OloEngine/Scene/Scene.cpp, OloEngine/src/OloEngine/Scene/Streaming/SceneStreamer.cpp
Prefab instantiation copies the full prefab component, override cleanup uses component methods, and audio runtime, streaming, and gizmo code read AudioSource state through accessors.
Prefab and AudioSource serialization
OloEngine/src/OloEngine/Scene/SceneSerializer.cpp, OloEngine/src/OloEngine/SaveGame/SaveGameComponentSerializer.cpp
YAML and save-game serializers read and write PrefabComponent override sets and AudioSourceComponent data through accessors, with legacy handling for missing sound-config handles.
AudioSource scripting bindings
OloEngine/src/OloEngine/Scripting/C#/ScriptGlue.cpp, OloEngine/src/OloEngine/Scripting/Lua/LuaScriptGlue.cpp
C# and Lua bindings switch AudioSource property access and event playback control to GetConfig(), GetUseEventSystem(), and GetStartCommandID().
Scene hierarchy panel updates
OloEditor/src/Panels/SceneHierarchyPanel.cpp, OloEditor/src/Panels/SceneHierarchyPanel.h
The hierarchy panel stores rename state locally, and the prefab override menu and AudioSource inspector use accessor-based component state and command resolution.
Test updates for accessor APIs
OloEngine/tests/PrefabOverrideTest.cpp, OloEngine/tests/ComponentRoundTripTest.cpp, OloEngine/tests/Audio/AudioEventsSceneIntegrationTest.cpp, OloEngine/tests/Lua/LuaBindingTest.cpp
Prefab and AudioSource tests now configure and assert state through the new getters, setters, and override helpers.

Sequence Diagram(s)

sequenceDiagram
  participant Scene as Scene::InitAudioRuntime
  participant Component as AudioSourceComponent
  participant AssetManager as AssetManager
  participant Source as AudioSource

  Scene->>Component: GetSoundConfigHandle()
  alt preset handle present
    Scene->>AssetManager: Load SoundConfigAsset
    AssetManager-->>Scene: preset config
    Scene->>Component: GetConfig() / apply preset
  end
  Scene->>Component: GetUseEventSystem() / GetStartCommandID()
  alt event system enabled
    Scene->>Scene: post trigger or start playback
  else
    Scene->>Source: set params from GetConfig()
    Scene->>Source: play on awake
  end
Loading

Possibly related issues

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: hot/cold splitting of PrefabComponent, TagComponent, and AudioSourceComponent.
Linked Issues check ✅ Passed The PR satisfies #444 by moving prefab override data, rename state, and audio config out of hot components and adding size guards/tests.
Out of Scope Changes check ✅ Passed The changed files stay focused on the hot/cold split and the necessary call-site, serialization, and test updates.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

coderabbitai[bot]

This comment was marked as outdated.

- AudioSourceComponent: add move ctor/assignment so relocating it in the
  ECS pool no longer deep-copies the cold data behind the unique_ptr.
- SceneSerializer: sanitize every AudioSourceConfig float on the scene-YAML
  load path (volume/pitch/rolloff/gain/distance/cone/doppler), mirroring
  the ranges SaveGameComponentSerializer already enforces, so a corrupted
  or hand-edited .olo file can't inject NaN/Inf into the audio engine.
- SceneHierarchyPanel: extract the duplicated start-event CommandID
  resolution logic (checkbox toggle + InputText handler) into a single
  ResolveStartCommandID helper.

Addresses CodeRabbit review on PR #578 (issue #444).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@OloEngine/src/OloEngine/Scene/SceneSerializer.cpp`:
- Around line 1735-1749: `SceneSerializer` is using mismatched editor and
serialization limits for audio cone angles and gain, causing save/load drift.
Update the `TrySetDsp` calls for `ConeInnerAngle` and `ConeOuterAngle` to use
radians consistently with `AudioSourceConfig`, and align `MaxGain`’s allowed
range with the editor so it matches the serializer’s clamp. Keep the
ranges/units consistent in the scene serialization path where `config` is
sanitized.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: cc7e2fde-9134-4c66-a656-1c3c738f1ea8

📥 Commits

Reviewing files that changed from the base of the PR and between 1292b95 and 1dd0786.

📒 Files selected for processing (3)
  • OloEditor/src/Panels/SceneHierarchyPanel.cpp
  • OloEngine/src/OloEngine/Scene/Components.h
  • OloEngine/src/OloEngine/Scene/SceneSerializer.cpp

Comment thread OloEngine/src/OloEngine/Scene/SceneSerializer.cpp
@sonarqubecloud

sonarqubecloud Bot commented Jul 6, 2026

Copy link
Copy Markdown

The AudioListenerComponent deserialize path used raw TrySet with no
sanitization, unlike the AudioSourceComponent path, so out-of-range or
non-finite cone values loaded straight through. Clamp cone angles to
[0, 2pi] radians and ConeOuterGain to [0, 1] on load, matching the
AudioSource path and AudioListenerConfig defaults.

Editor drift fixes (SceneHierarchyPanel):
- AudioSource Max Gain slider allowed 0..2 while every serializer clamps
  to 0..1, so 1.x collapsed to 1.0 on reload; range now 0..1.
- Audio Source/Listener cone sliders drove the radians-valued cone fields
  with a 0..360 degrees range, storing e.g. 60 as 60 radians (clamped to
  ~6.28 on reload). Now edited in degrees with glm::degrees/radians
  conversion so the 0..360 UI maps to the radians storage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@drsnuggles8 drsnuggles8 merged commit 16f33e7 into master Jul 7, 2026
2 of 8 checks passed
@drsnuggles8 drsnuggles8 deleted the feature/hotcold-component-split branch July 7, 2026 06:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Perf/DOD: move editor-only / cold data out of hot runtime components (prefab override sets, tag strings)

1 participant